很久没写C++,决定过一遍runoob,只记录认为需要记录或个人印象不清晰的内容,唤醒一下沉睡的记忆
标准库
核心语言构建块、C++标准库、STL构成
基本语法
三字符组
用于表示键盘上没有的字符,如??=来表示#,大部分编译器都不会自动替换三字符组,但g++默认支持三字符组,当程序中需要两个连续的问号时,需要写成 "...?""?..."或转义为"...?\?..."
数据类型
1 字节(byte) 8 位(bit),
char 占用 1 字节,int 占用 4 字节,long 占用 4 字节,long long 占用 8 字节,float 占用 4 字节,double 占用 8 字节
以上类型的范围只是 C++ 标准规定的最小要求,实际上,许多系统上这些类型可能占用更多的字节,例如,很多现代计算机上 int 通常占用 4 字节,而 long 可能占用 8 字节。
float a;
double b;
a = 77777.7777777;
b = 77777.777777777;
打印出a和b后显示a为77777.781250,b为77777.777778,float 是 32 位浮点数,有 7 位有效数字。double 是 64 位浮点数,有 16 位有效数字。一般编译器规定小数后面最多保留6位数,但这个1250是怎么来的呢?
十进制数 77777.7777777 转换为32位二进制浮点数,在四舍五入后再转换回十进制时,得到了77777.781250
typedef 声明
为一个已有的类型取一个新的名字
typedef type newname;
类型转换
包含四种类型转换:静态转换、动态转换、常量转换、重新解释转换
静态转换
通常用于较相似对象间的转换,不进行任何类型检查
int i = 10;
float f = static_cast<float>(i);
动态转换
通常用于将一个基类指针或引用转换为派生类指针或引用,在运行时进行类型检查
class Base {};
class Derived : public Base {};
Base* ptr_base = new Derived;
Derived* ptr_derived = dynamic_cast<Derived*>(ptr_base); // 将基类指针转换为派生类指针
常量转换
用于将 const 类型的对象转换为非 const 类型的对象,只操作 const 属性,不会改变对象类型
const int i = 10;
int& r = const_cast<int&>(i);
重新解释转换
将一个数据类型的值重新解释为另一个数据类型的值,通常用于在不同的数据类型之间进行转换,不进行任何类型检查。静态转换无法处理没有关联性的指针类型转换,也不能在多态类型之间实现转换,这时候就需要重新解释转换出手了。
// 将int类型转换为float类型
int i = 10;
float f = reinterpret_cast<float&>(i);
// 将整数指针转换为字符指针
int* intPtr = new int(42);
char* charPtr = reinterpret_cast<char*>(intPtr);
C++常量
注意把常量定义为大写字母,要有良好的编程习惯
cont int LENGTH = 10;
不应该把布尔值 true 和 false 看成 1 和 0 ,应只看成 真 和 假
C++修饰符
有符号整数和无符号整数修饰符有很大的差别,而不仅仅是体现在符号上。
#include <iostream>
using namespace std;
int main()
{
short int i;
short unsigned int j;
j = 50000;
i = j;
cout << i << " " << j;
return 0;
}
当上面的程序运行时,会输出下列结果:
-15536 50000
对于 unsigned short 类型,所有的位都被解释为数值位。因此,二进制 1100001101010000 直接被解释为50000。对于 short 类型,有符号整数使用二进制补码表示法,其中最高位(左)是符号位。所以最终解释为-15536了
C++类型限定符、存储类
| 类型限定符/存储类 | 定义 |
|---|---|
| const | 常量,不能被修改 |
| volatile | 该变量的值可能被当前程序以外的因素改变 |
| restrict | 用于限定和约束指针,并表明该指针是访问这块内存数据对象的唯一且初始的方式 |
| mutable | 类中的成员变量可以在 const 成员函数中被修改 |
| static | 静态变量,该变量的作用域仅限于当前文件或当前函数内,不会被其他文件或函数访问 |
| register | 寄存器变量,表示该变量被频繁使用,可以存储在CPU的寄存器中,以提高程序的运行效率 |
| extern | 相当于提供一个全局变量的引用,可以理解为在另一个文件中声明一个全局变量或函数 |
| thread_local | 线程本地存储变量,该变量仅可在它在其上创建的线程上访问,避免数据竞争和同步问题,在创建线程时创建,并在销毁线程时销毁 |
C++运算符
算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符、杂项运算符
| 杂项运算符 | 描述 |
|---|---|
| sizeof | 返回变量字节数 |
| A ? X:Y | 条件运算 |
| , | 逗号运算符,按顺序求解表达式,整个逗号表达式的值是最后一个表达式的值,其余值被丢弃 |
| .和-> | 成员运算符,用于引用类、结构和共用体的成员 |
| Cast | 强转 |
| &和* | 指针运算符,返回变量地址和指向一个变量 |
C++函数
Lambda函数与表达式
Lambda 表达式把函数看作对象。Lambda 表达式可以像对象一样使用,比如可以将它们赋给变量和作为参数传递,还可以像函数一样对其求值。具体形式如下:
[capture](parameters)->return-type{body}
其中:
- capture:捕获值,用于指定Lambda表达式可以访问的外部变量,以及是按值还是按引用的方式访问。
- parameter:参数列表。
- return type:返回值类型。
- function body:函数体
捕获方式
- 值捕获:在捕获列表中使用变量名,表示将该变量的值拷贝到 Lambda 表达式中。值捕获的变量默认不能在 Lambda 表达式中修改,除非使用
mutable关键字。
int x = 10;
auto f = [x] (int y) -> int { return x + y; }; // 值捕获 x
x = 20; // 修改外部的 x
cout << f(5) << endl; // 输出 15,不受外部 x 的影响
- 引用捕获:在捕获列表中使用
&加变量名,表示将该变量的引用传递到 Lambda 表达式中,作为一个数据成员。引用捕获的变量在 Lambda 表达式调用时才确定,会随着外部变量的变化而变化。引用捕获的变量可以在 Lambda 表达式中修改,但要注意生命周期的问题,避免悬空引用的出现。
int x = 10;
auto f = [&x] (int y) -> int { return x + y; }; // 引用捕获 x
x = 20; // 修改外部的 x
cout << f(5) << endl; // 输出 25,受外部 x 的影响
- 隐式捕获:在捕获列表中使用
=或&,表示按值或按引用捕获 Lambda 表达式中使用的所有外部变量。这种方式可以简化捕获列表的书写,避免过长或遗漏。隐式捕获可以和显式捕获混合使用,但不能和同类型的显式捕获一起使用。
int x = 10;
int y = 20;
auto f = [=, &y] (int z) -> int { return x + y + z; }; // 隐式按值捕获 x,显式按引用捕获 y
x = 30; // 修改外部的 x
y = 40; // 修改外部的 y
cout << f(5) << endl; // 输出 55,不受外部 x 的影响,受外部 y 的影响
- 初始化捕获:使用初始化表达式,从而在捕获列表中创建并初始化一个新的变量,而不是捕获一个已存在的变量。这个初始化变量在Lambda表达式创建时就会确定。可以捕获
this指针。
int x = 10;
auto f = [z = x + 5] (int y) -> int { return z + y; }; // 初始化捕获 z,相当于值捕获 x + 5
x = 20; // 修改外部的 x
cout << f(5) << endl; // 输出 20,不受外部 x 的影响
class Test
{
public:
Test(int n) : num(n) {} // 构造函数,初始化 num
void add(int x) // 成员函数,增加 num
{
auto f = [*this] () { return num + x; };
cout << f() << endl;
}
private:
int num; // 成员变量,存储一个整数
};
对于[=]或[&]的形式,lambda 表达式可以直接使用 this 指针。但是,对于[]的形式,如果要使用 this 指针,必须显式传入.
C++ 数字
生成随机数
rand()只返回一个伪随机数。生成随机数之前必须先调用srand()函数。不然运行多次时每次生成的随机数序列都是相同的。
C++ 数组
传递数组给函数
三种方式
void myFunction(int *param){}
void myFunction(int param[10]){}
void myFunction(int param[]){}
数组类型会自动转换为指针类型,其实都是传的地址。
从函数返回数组
C++不允许返回数组,在这种情况下可以返回一个指针。但不能简单地返回指向局部数组的指针,因为当函数结束时,局部数组将被销毁,所以需要将其定义为static变量:
int* myFunction()
{
static int myArray[3] = {1, 2, 3};
return myArray;
}
动态分配new出来的数组就不用static了,不过要记得delete[]
C++ 指针
指针的算数运算
ptr++
执行 ptr++ 后,指针 ptr 会向前移动 n 个字节,指向下一个元素的地址。这是由于指针算术运算会根据指针的类型和大小来决定移动的距离。如整数占据 4 个字节, ptr++ 会将指针 ptr 向前移动 4 个字节,指向下一个整型元素的地址。如字符指针占据 1 个字节,因此 ptr++ 会将 ptr 的值增加 1,执行后 ptr 指向地址 1001。
指针与指针之间的减法运算:得到两个指针之间的元素个数。
C++ 日期和时间
在 C++ 程序中引用ctime头文件
有四个与时间相关的类型:clock_t、time_t、size_t 和 tm。类型 clock_t、size_t 和 time_t 能够把系统时间和日期表示为某种整数。结构类型 tm 把日期和时间以 C 结构的形式保存。
当前日期和时间
#include <iostream>
#include <ctime>
using namespace std;
int main( )
{
// 基于当前系统的当前日期/时间
time_t now = time(0);
// 把 now 转换为字符串形式
char* dt = ctime(&now);
}
C++ 基本输入输出
- 标准输出流cout
- 标准输人流cin
- 标准错误流cerr
- 标准日志流clog
cerr不经过缓冲直接输出,能够比cout更迅速输出错误信息,在紧急情况下还能得到输出功能的支持。所以尽量使用 cerr 流来显示错误消息,而其他的日志消息则使用 clog 流来输出。
C++ 结构体
struct语句
struct type_name {
member_type1 member_name1;
member_type2 member_name2;
member_type3 member_name3;
...
} object_names; // object_names是顺便声明的实例
成员访问运算符.
指针访问运算符->
struct Books
{
...
}Book1;
Book1.book_id = 12345;
struct Books* struct_pointer;
struct_pointer = &Book1;
struct_pointer->title;
typedef 关键字
typedef struct student{
char name[20];int num;
float math,yuwen,wuli,mean;
} st;
st st1, st2, st3;
其实就是结合 typedef,方便后续变量的定义。
C++ 类 & 对象
类成员函数
使用范围解析运算符 :: 来定义
类访问修饰符
标记公有成员、受保护成员、私有成员
实际操作中,我们一般会在私有区域定义数据,在公有区域定义相关的函数,以便在类的外部也可以调用这些函数
构造函数、析构函数
构造函数可用于为某些成员变量设置初始值。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
构造函数使用初始化列表来初始化字段
Line::Line( double len): length(len)
{
cout << "Object is being created, length = " << len << endl;
}
// 相当于
Line::Line( double len)
{
length = len;
cout << "Object is being created, length = " << len << endl;
}
拷贝构造函数
是一种特殊的构造函数,在创建对象时使用同一类中之前创建的对象来初始化新创建的对象。如果不写拷贝构造函数,C++只会简单的复制上一个对象的指针,而并非真正构造了新的对象,即只实现浅拷贝。相当于多配了一把钥匙,而非新盖了一栋楼。
使用拷贝构造函数的三种情况:
- 当用一个对象去初始化同类的另一个对象时
- 当某函数的一个参数是类A的对象,那么该函数被调用时,类A的拷贝构造函数将被调用
- 如果函数的返回值是类A的对象,则返回时A的拷贝构造函数被调用
拷贝构造函数实现的是深拷贝。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。
类的拷贝构造函数原型通常是这样:
类名(const 类名& 原对象)
拷贝构造函数的几个特点:
1、函数名和类名相同,因为它也是构造函数的一种;
2、第一个参数必须是一个自身类类型的引用,且其他参数都有默认值。因为如果不是引用就需要拷贝实参(原对象),但拷贝实参又需要调用拷贝构造函数(打不过钢龙怎么办?那你穿钢龙套啊!)
友元函数
类的友元函数通常申明在类内部(具体public,protected,private无所谓),定义在类外部,但有权访问类的private成员和protected成员。友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
- 使用关键字
friend
class Box
{
double width;
public:
double length;
friend void printWidth( Box box );
};
内联函数
如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方,这样不用再调用函数,提高函数的执行效率。但如果对内联函数进行任何修改,都需要重新编译函数的所有客户端。通常是函数体较小又调用频繁的函数。
- 使用关键字
inline
C++ 的 this 指针
在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址,它指向当前对象的实例。
#include <iostream>
using namespace std;
class Box
{
public:
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"调用构造函数。" << endl;
length = l;
breadth = b;
height = h;
}
double Volume()
{
return length * breadth * height;
}
int compare(Box box)
{
return this->Volume() > box.Volume();
}
private:
double length; // 宽度
double breadth; // 长度
double height; // 高度
};
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // 声明 box1
Box Box2(8.5, 6.0, 2.0); // 声明 box2
if(Box1.compare(Box2))
{
cout << "Box2 的体积比 Box1 小" <<endl;
}
else
{
cout << "Box2 的体积大于或等于 Box1" <<endl;
}
return 0;
}
指向类的指针
- 与指向结构的指针类似,需要使用成员访问运算符
-> - 可用于动态分配内存,创建类对象
MyClass *ptr = new MyClass;
类的静态成员
可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。
不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化。
class Box
{
public:
static int objectCount;
Box(...)
{
...
}
...
private:
...
};
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
int main(void)
{
...;
return 0;
}
静态成员函数
函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,只要使用类名加范围解析运算符 :: 就可以访问。
静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。
C++ 继承
// 基类
class Animal {
...
};
class Dog {
...
};
//派生类
class Labrador : public Animal, public Dog {
...
};
访问控制和继承
派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。
一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。
继承类型
几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:
- 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
- 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
- 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
重载运算符和重载函数
参数列表和定义(实现)不同,编译器通过比较参数类型选用最适合的定义。
运算符重载
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。
返回值类型 operator重载的运算符(参数列表)
{
...
}
类内重载时,重载函数作为类的成员函数,可以只传入一个参数(另一个参数由this运算符进行访问);类外重载时,用友元函数的方法实现(在类内声明为友元函数,在类外定义)
#include <iostream>
using namespace std;
class Box
{
public:
...
// 重载 + 运算符,用于把两个 Box 对象相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 程序的主函数
int main( )
{
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
Box Box3; // 声明 Box3,类型为 Box
...
Box3 = Box1 + Box2;
...
}
- 注意:返回值当左值得时候,返回的是一个引用
C++ 多态
基类中的函数在子类中有其分别的独立实现。
如果想执行子类中的实现,需要在基类中使用关键字virtual进行声明,将其定为虚函数。如果没有virtual声明,无论执行基类方法还是子类方法,函数会被调用为基类中的版本,这就是所谓的静态多态,或静态链接。
在基类中不能对虚函数给出有意义的实现,用到纯虚函数virtual ... = 0。
class Shape {
public:
Shape( int a=0, int b=0)
{
...;
}
// pure virtual function
virtual int area() = 0;
};
= 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数。
C++ 数据抽象
只向外界提供关键信息(接口),隐藏其后台的实现细节。
C++数据封装
通过将数据和操作数据的函数封装在一个类中来实现。确保了数据的私有性和完整性。
C++ 接口(抽象类)
如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。C++ 接口是使用抽象类来实现的。
设计抽象类(ABC)的目的,是为了给其他类提供一个可以继承的适当的基类。抽象类不能被用于实例化对象,它只能作为接口使用。
C++ 文件和流
文件流标准库fstream定义了三种新的数据结构:
| 数据类型 | 描述 |
|---|---|
| ofstream | 表示输出文件流,用于创建文件并向文件写入信息 |
| ifstream | 表示输入文件流,用于从文件读取信息 |
| fstream | 表示文件流,且同时具有 ofstream 和 ifstream 两种功能 |
C++ 异常处理
- throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
- catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
- try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。
try
{
// 保护代码
}catch( ExceptionName e1 )
{
// catch ExceptionName e1 类型的异常
}catch( ExceptionName e2 )
{
// catch ExceptionName e2 类型的异常
}catch( ... )
{
// 省略号表示 catch 任何类型的异常
}
ExceptionName是异常声明,用于指定捕捉的异常类型
也可以定义新的异常:
#include <iostream>
#include <exception>
using namespace std;
struct MyException : public exception
{
const char * what () const throw ()
{
return "C++ Exception";
}
};
int main()
{
try
{
throw MyException();
}
catch(MyException& e)
{
std::cout << "MyException caught" << std::endl;
std::cout << e.what() << std::endl;
}
catch(std::exception& e)
{
//其他的错误
}
}
what() 是异常类提供的一个公共方法,它已被所有子异常类重载。这将返回异常产生的原因。
预处理器
条件编译
有选择地对部分程序源代码进行编译
#ifdef DEBUG
cerr << "Variable x = " << x << endl;
#endif
#if 0
不进行编译的代码
#endif
等等

120

被折叠的 条评论
为什么被折叠?



