-
面向对象:C++支持面向对象编程(OOP)的四大特性:封装、继承、多态和抽象。通过类和对象,程序员能够创建模块化的代码,更容易地进行维护和扩展。
-
泛型编程:C++支持模板编程,允许编写与数据类型无关的代码。模板是实现泛型编程的关键工具,它们提高了代码的复用性。
-
直接内存管理:C++提供了对内存的直接操作能力,允许程序员手动管理内存分配和释放,这是C++的一个强大特性,也是需要谨慎使用的地方,因为不当的内存管理可能会导致资源泄露和其他问题。
-
性能:C++编写的程序通常有很高的执行效率,这是因为C++提供了与底层硬件直接对话的能力。这使得C++成为开发要求性能的系统软件(如操作系统、游戏引擎)的理想选择。
-
C语言兼容:大部分C语言程序可以在C++编译器上编译并运行。这一特性简化了从C到C++的过渡。
-
多编程范式支持:除了面向对象和泛型编程外,C++还支持过程式编程和函数式编程等范式,使其成为一个多样化的工具,能适应不同的编程需求。
类型
基础类型
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
||
|
|
||
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int a; // 声明未初始化,使用前建议手动初始化
char b = 'a';
float c = 1.0f; // C++中默认小数是double类型,加上f可以指定为 float
double d = 2.0;
auto e = 20; // 编译器自动推导auto为 int 类型
基础类型隐式转换
-
安全的隐式转换:
-
整型提升:小的整型(如 char、short)会自动转换成较大的整型(如 int)。
-
算术转换:例如,当 int 和 double 混合运算时,int 会转换为 double。
-
存在隐患的隐式转换:
-
窄化转换:大的整数类型转换到小的整数类型,或者浮点数转换到整数,可能会造成数据丢失或截断。
-
指针转换:例如,将 void* 转换为具体类型的指针时,如果转换不正确,会导致未定义行为。
结构体(struct)
// 结构体定义
struct Person {
std::string name;
int age;
}
// 结构体初始化,
Person person = {"Jim", 20};
Person person2; // 创建另一个实例
person2 = person; // 将person中的值复制到person2中,默认是浅拷贝,在有指针的情况下有潜在风险
枚举(enum)
enum Color { RED, GREEN, BLUE };
// 使用
Color myColor = RED;
enum class Color {
RED,
GREEN,
BLUE
};
Color myColor = Color::RED; // 使用作用域解析运算符(::)访问枚举值
联合体(union)
// 联合体的定义
union Data {
int intValue;
float floatValue;
char charValue;
}
// 联合体一次只能保存一种类型的数据,每次赋值都会覆盖内存中之前的值
// 因此联合体一般是配合结构体来使用,下面是一个示例
// 定义数据类型的枚举
enum DataType {
INT,
FLOAT,
CHAR
};
// 定义一个结构体,它包含一个联合体和一个枚举标签
struct SafeUnion {
// 标记当前联合体中存储的数据类型
DataType type;
// 定义联合体
union {
int intValue;
float floatValue;
char charValue;
} data;
};
// 赋值操作
SafeUnion su;
su.type = FLOAT;
su.data.floatValue = 1.0f;
// 使用时,通过type判断类型然后访问联合体对应的成员变量
switch(su.type) {
case FLOAT:
cout endl;
break;
}
类(class)
class Person {
public:
void doWork(); // 方法,类对外提供的一系列操作实例的函数
private:
std::string name; // 成员变量,封装到类中的属性,保存内部状态信息
int age;
};
列表初始化
// 基础类型
int a{0};
double b{3.14};
// 结构体
struct MyStruct {
int x;
double y;
};
MyStruct s{1, 2.0};
// 类
class MyClass {
public:
MyClass(int a, double b) : a_(a), b_(b) {}
private:
int a_;
double b_;
};
MyClass obj{5, 3.14}; // MyClass 必须有一个匹配这个参数列表的构造函数
// 数组
int arr[3]{1, 2, 3};
// 上面介绍的都是现代C++推荐写法,省略 =
// 下面的2种写法绝大多数情况下是等价的
float arr[2]{1, 2}; // 写法1
float arr[2] = {1, 2}; // 写法2
// 编译器对这两种写法的处理是一致的,方法2并不会产生临时变量和拷贝赋值,包括类的声明
int a = 7.7; // 编译能通过,但是有warning
int b = {1.0}; // 编译器拒绝通过,因为浮点到整形的转换会丢失精度
-
零初始化
数组
数组的声明
Typename arrayName[Size];
// 基本类型
int arr[10];
char charArr[30];
// 复杂类型
struct Point {
int x;
int y;
}
Point points[10];
数组的初始化
int arr[] = {1, 2, 3, 4, 5}; // 数组大小为5,编译器自动确定
int arr[10] = {1, 2, 3}; // 数组前三项确定为1,2,3,其余被初始化为0
i nt arr[10] = {0}; // 整个数组全部为0
数组的使用
int arr[10] = {}; // 零初始化
arr[0] = 10; // 修改数组第一个元素值为10
-
数组在声明后(无论静态声明还是动态声明),数组的大小即固定,不可更改;
-
数组不提供任何内置的方法来获取其大小,通常需要额外保存数组的大小,或者使用特殊标记结束元素(C风格的字符串使用’�’表示数组结束);
-
数组不提供边界检查,越界访问的代码是可以通过编译的(静态数组编译器会给出警告),可能导致很多潜在问题。
int arr[10] = {0};
int a = arr[10]; // 最大的有效索引是9,这里出现越界,但编译器能顺利编译通过(有警告)
// a中的值是不确定的,没有实际意义的,这里是读取,危害可能有限
arr[10] = 99;
// 可怕的是该语句也能通过编译,但这里进行了更加危险的操作,
// 越界访问了一块内存并修改了其内容,这很可能导致程序崩溃
多维数组
二维数组
Typename arrayName[Rows][Columns]; // Rows是行数, Columns是列数, 必须常量
// 实际示例
int arr[10][10]; // 定义了一个10*10的二维数组
// 完全初始化
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// 部分初始化
int matrix[2][3] = {
{1, 2}, // 第一行的最后一个元素将被初始化为 0
{4} // 第二行的第二个和第三个元素将被初始化为 0
};
// 单行初始化
int matrix[2][3] = {1, 2, 3}; // 只初始化第一行,其他行将默认初始化为0
// 自动推断,和一维数组一样,编译器会根据数组推断二维数组第一维的大小
int matrix[][3] = {
{1, 2, 3},
{4, 5, 6}
};
多维数组
Typename arrayName[Depth][Rows][Columns];
数组的替代
-
std::vector: 可变大小的数组。提供对元素的快速随机访问,并能高效地在尾部添加和删除元素。
-
std::list 双向链表。支持在任何位置快速插入和删除元素,但不支持快速随机访问。
-
std::deque: 双端队列。类似于std::vector,但提供在头部和尾部快速添加和删除元素的能力。
-
std::array (C++11): 固定大小的数组。提供对元素的快速随机访问,并且其大小在编译时确定。
-
std::forward_list (C++11): 单向链表。提供在任何位置快速插入和删除元素,但不支持快速随机访问。
-
std::stack: 栈容器适配器。提供后进先出(LIFO)的数据结构。
-
std::queue: 队列容器适配器。提供先进先出(FIFO)的数据结构。
-
std::priority_queue: 优先队列容器适配器。元素按优先级出队,通常使用堆数据结构实现。
-
std::set: 一个包含排序唯一元素的集合。基于红黑树实现。
-
std::multiset: 一个包含排序元素的集合,元素可以重复。基于红黑树实现。
-
std::unordered_set (C++11): 一个包含唯一元素的集合,但不排序。基于散列函数实现。
-
std::unordered_multiset (C++11): 一个包含元素的集合,元素可以重复,但不排序。基于散列函数实现。
指针
理解指针
-
a是一个整形,占用4个字节(一般int类型占用4字节),0xffffffffffffecdc是其首地址,内存中的值是2568,即代码中的赋值(具体的存储细节可以搜索 大端序、小端序)
-
p是一个整形指针,占用8个字节(64位系统),0xffffffffffffece0是其首地址,内存中的值是a变量内存的首地址,即0xffffffffffffecdc。
指针的定义
Typename * ptrName;
// 指针定义风格,下面的声明都正确
int *p; // C风格,旨在强调 (*p)是一个整形值
int* p; // 经典C++风格,只在强调 p是一个整形指针类型(int*)
// 集团推荐的风格,指针、引用都是居中,两边留空格
int * p; // 指针
int & a = xx; // 左值引用
int && a = xx; // 右值引用
int a = 123; // 假设 a 地址为 0xfffff100
int * p = &a; // 此时 p 中存储的值为 0xfffff100
p = p + 1; // 此时 p 中存储的值为 0xfffff104 (0xfffff100偏移4个字节,即int变量占用的大小)
指针初始化和访问
int a = 5;
int * p = &a; // & 取地址运算符
// * 用在指针这里是解引用运算符,可以获取指针指向的地址的值
cout endl; // 输出 5
int b = 10;
p = &b; // 指针变量可以修改其指向地址
cout endl; // 输出 10
常量指针 vs 指针常量
常量指针
int value = 5;
const int * p = &value; // p是一个常量指针
int const * q = &value; // 和上面的声明等价
*p = 10; // 非法,*p是常量不能修改
int a = 6;
p = &a; // 合法,p本身不是常量,可以重新赋值
指针常量
int value = 5;
int * const p = &value; // p是常量
*p = 6; // 合法
int a = 7;
p = &a; // 非法
指针与数组
指针和数组名的异同
int arr[5] = {1, 2, 3, 4, 5};
int * p1 = arr; // arr 被当做指向数组首元素的指针
int * p2 = &arr[0]; // 取arr首个元素的地址
// 这种情况下 p1 和 p2 是等价的
if (p1 == P2) { // 检测会通过
cout "p1,p2是等价的" endl;
cout endl; // 打印 1
cout endl; // 打印 1
}
// 使用指针访问数组
// 指针方式
cout 1) endl; // 访问数组第二个元素,这种方式符合指针的计算规则
// 类似数组名的使用方式
cout 1] endl;// p1虽然是指针,索引访问方式依然有效,本质是*(p1 + 1)的语法糖
int arr[5] = {1, 2, 3, 4, 5};
int * p1 = arr;
cout sizeof(arr) endl; // 打印结果:20
cout sizeof(p1) endl; // 打印结果:8
// sizeof(arr)为数组本身的大小,这里是 5个int占用20字节
// sizeof(p1)为指针本身大小,64位系统中占用8个字节
cout endl; // 0x16b98aa40
cout 1 endl; // 0x16b98aa54
cout 0] endl; // 0x16b98aa40
cout 0] + 1 endl; // 0x16b98aa44
// 可以看出 &arr 和 &arr[0] 的值是一样的,但是指针偏移1后
// (&arr + 1) 在 &arr 的基础上偏移了20(0x14)个字节
// (&arr[0] + 1) 在 &arr[0] 的基础上偏移了4个字节
-
&arr 得到的类型是 int (*)[5] ,这是一个指向包含5个整数数组的指针 -
&arr[0]得到的类型是 int *,这是一个整型指针
动态数组
int * arr = new int[10]; // new操作符在堆内存中申请10个int类型大小的连续空间,并返回首地址
arr[0] = 1;
arr[1] = 2;
// ...
delete[] arr; // new操作符申请的内存需要使用delete操作符释放,数组使用delete[]
// 二维数组的动态创建 & 释放
int rows = 5; // 行数
int cols = 3; // 列数
// 动态创建二维数组
int ** array = new int*[rows]; // 创建行指针
for (int i = 0; i
array[i] = new int[cols]; // 为每行分配内存
}
// 初始化二维数组
for (int i = 0; i
for (int j = 0; j
array[i][j] = i * cols + j; // 或者任何其他的赋值逻辑
}
}
// 使用二维数组,例如打印它
for (int i = 0; i
for (int j = 0; j
std::cout array[i][j] ' ';
}
std::cout std::endl;
}
// 动态释放二维数组
for (int i = 0; i
delete[] array[i]; // 释放每行的内存
}
delete[] array; // 释放行指针数组的内存
int * p[10]; // p是一个包含10个int变量的数组
int (*p)[10]; // p是一个指向拥有10个int变量的数组的指针
// * [] 两个运算符的优先级不同,[]的优先级更高
// 第一个语句声明了 p[10], int * 是类型
// 第二个语句有括号改变了优先级,因此 p 是一个指针,剩下的部分定义了类型
// 下面函数指针也会有类似的定义
int (*pf)(int, int); // pf是指向形如 int func(int, int) 的函数指针
函数
函数基础
-
返回类型:函数可能返回的值的数据类型。如果函数不返回任何值,则使用关键字 void。
-
函数名:用于识别函数的唯一名称。
-
参数列表:括号内的变量列表,用于从调用者那里接收输入值。如果函数不接受任何参数,则参数列表为空。
-
函数体:花括号 {} 内包含的代码块,当函数被调用时将执行这些代码。
// 这是一个简单函数定义
int add(int a, int b) {
return a + b;
}
// 调用
int sum = add(3, 7); // sum值为10
// 下面是一个函数原型的定义
int draw(int, int); // 函数原型的参数列表可以省略参数名
int draw(int width, int height); // 建议加上参数名,可以更直观的了解参数含义
参数传递
-
值传递 -
指针传递 -
引用传递(特指左值) -
右值传递
传递方式
值传递
void swap(int a, int b) {
int tmp = a;
a = b;
b = tmp;
}
int x = 5;
int y = 7;
swap(x, y); // x = 5 y = 7
指针传递
void swap(int * a, int * b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
int x = 5;
int y = 7;
swap(&x, &y); // x = 7 y = 5
int sum(int arr[], int size);// 定义1,这种定义的好处是清晰,调用者一看就知道传递数组指针
int sum(int * arr, int size);// 定义2,这种定义更符合数组传参的本质
// 特殊说明
// 不管定义1还是定义2,通过参数传递数组指针后,数组指针(前面介绍过,即数组名)会退化为首个元素
// 的地址指针,因此一定要通过size参数传递数组的大小给函数
引用传递(左值传递)
int a = 5;
int & ra = a; // ra的类型是 int&(引用),必须声明时立即初始化
int b = 6;
ra = b; // 非法,引用变量不支持重新赋值
void swap(int & a, int & b) {
int tmp = a;
a = b;
b = tmp;
}
int x = 5;
int y = 7;
swap(x, y); // x = 7 y = 5
右值传递
左值(lvalue): 左值是指表达式结束后依然存在的持久性对象,可以出现在赋值语句的左边。 左值可以被取地址,即可以通过取地址运算符&获取其地址。 通常,变量、数组元素、引用、返回左值引用的函数等都是左值。 右值(rvalue): 右值是指表达式结束后不再存在的临时对象,不能出现在赋值语句的左边。 右值不能被取地址,即不能通过取地址运算符&获取其地址。 通常,字面量、临时对象、返回右值引用的函数等都是右值。
-
纯右值 -
将亡值 -
泛左值 -
std::move -
类型萃取
参数修改保护
// 下面指针传递示例
void printInfo(int arr[], int size); // 内部可修改arr
void printInfo(const int arr[], int size); // 内部不可修改arr
// 下面是引用传递示例
void printInfo(std::string& info); // 内部可以修改info
void printInfo(const std::string& info); // 内部不可以修改info
传参方式选择原则
-
如果数据对象很小,如内置数据类型或者小型结构,这按值传递。
-
如果数据对象是数组,这使用指针,因为这是唯一的选择,并将指针声明为常量指针。
-
如果数据对象较大的结构,则使用常量指针或者const引用,可以节省复制结构所需要的时间和空间,提高程序的效率。
-
如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用。这是C++增加引用的主要原因。因此传递类对参数的标准方式是按引用传递。
-
如果数据对象是内置数据类型,则使用指针。 -
如果数据对象是数组,则只能使用指针。 -
如果数据对象是结构,则使用指针或者引用。 -
如果数据对象是类对象,则使用引用。
函数重载
// 下面是一组重载函数,同样是计算两个数的和,针对不同类型提供了不同的定义
int add(int a, int b) { // 版本1
return a + b;
}
float add(float a, float b) { // 版本2
return a + b;
}
double add(double a, double b) { // 版本3
return a + b;
}
add(1, 2); // 匹配版本1
add(1.0f, 2.0f); // 匹配版本2
add(1.0, 2.0); // 匹配版本3
add(1.0f, 2.0) // 匹配 ??? (匹配版本3,原因可以搜索 ”重载解析“)
重载规则
-
函数名称 -
参数列表:包括参数的类型、数量和顺序。注意一个参数是否使用引用并不能作为签名不同的依据。参数是否是 const 能作为不同的依据
-
函数签名必须不同 -
作用域必须相同:重载的函数必须处于同一个作用域,否则它们被视为不同作用域中的不相关函数。 -
最佳实践是尽量保持重载函数的明确性,避免产生容易混淆的重载集合。
// 下面两个版本的函数不算重载,因为两者调用时的表达式都是 add(x, y), 编译器无法区分
int add(int a, int b);
int add(int & a, int & b);
// 下面两个版本算重载,编译器会根据实参是否是常量来匹配更合适的版本
int add(const int a, const int b);
int add(int a, int b);
-
重载函数匹配规则
函数模板
函数模板的声明
template typename T>
T add(T a, T b) {
return a + b;
}
// 多类型的定义
template typename T1, typename T2>
void funcName(T1 a, T2 b);
using namespace std;
// 函数原型
template typename T>
T add(T a, T b);
int main() {
cout 1, 2) endl; // 3
cout 1.0f, 2.1f) endl; // 3.1
cout 1.0, 3.2) endl; // 4.2
return 0;
}
template typename T>
T add(T a, T b) {
return a + b;
}
重载的模板
// 函数原型
template typename T>
T add(T a, T b);
template class T> // 声明模板时 typename 和 class 等价
T add(T a, T b, T c);
// 函数定义略
模板的局限
template typename T1, typename T2>
void funcName(T1 x, T2 y) {
...
?type? temp = x + y;
...
}
template typename T1, typename T2>
void funcName(T1 x, T2 y) {
...
decltype(x + y) temp = x + y;
...
}
template typename T1, typename T2>
?type? funcName(T1 x, T2 y) {
...
return x + y;
}
// 正常函数声明
int add(int a, int b);
// 返回类型后置声明
auto add(int a, int b) -> int;
// 利用该语法可以这么声明上面的函数(推荐C+11中使用)
template
auto funcName(T1 x, T2 y) -> decltype(x + y) {
...
return x + y;
}
// C++14及以后得标准拓展了auto的类型推导能力
auto funcName(T1 x, T2 y) {
...
return x + y;
}
-
模板函数具体化、全特化 (Full Specialization) -
auto类型推导规则(区分C+11、C++14) -
decltype类型推导规则 -
模板元编程
回调函数
函数指针
// 返回类型 (*指针变量名)(参数列表);
// 示例
int add(int a, int b) {
return a + b;
}
int (*pf)(int, int) = add; // 可以这么理解定义:因为(*pf)表示函数,那么pf就是函数的指针
// 类似数组,函数指针也有两种使用方式
cout 2, 3) endl; // 5 指针使用方式
cout 2, 3) endl; // 5 直接作为函数名使用
// 函数指针的定义一般都不怎么直接,使用也不方面
// 经典C++中可以使用typedef简化这个定义
typedef int (*p_fun)(int, int); // 现在p_fun就是一种类型名称
p_fun pAdd = add; // 精简很多
// 现代C++提供了 using 语法让这个过程更加直观,推荐使用
using p_fun = int (*)(int, int); // 可读性更强
p_fun pAdd = add;
// auto大杀器
auto pAdd = add; // 懒人利器
void callBack(int costTimeMs);
void work(void (*pf)(int));
int main() {
work(callBack);
}
void callBack(int costTimeMs) {
using namespace std;
cout "costTime:" endl;
}
void work(void (*pf)(int)) {
std::cout "do some work" std::endl;
// ...
pf(123); // (*pf)(123) 也ok
}
std::function
using namespace std;
void callBack(int costTimeMs) {
cout "costTime:" endl;
}
void work(functionvoid(int)> callBack) {
callBack(1234);
}
int main() {
functionvoid(int)> func = callBack;
work(func);
return 0;
}
更多方式
类(class)
定义类
class MyClass {
public:
// 公共成员,通常的对外提供的方法定义
void setMember(int member);
private:
// 私有成员,成员变量,仅供内部调用函数
int mMember; // 集团规范推荐,使用m前缀
void innerFunc(); // 函数一律小驼峰
protected:
// 受保护成员,成员变量,供子类调用函数
};
// person.hpp
// C++一般使用 hpp 后缀的头文件,表明包含C++特性的代码(模板、引用、类等)
// .h .hpp只是约定的做法,不是语法上的必要性
// 类的定义一般放到头文件中,用来对外声明类
// 类的头文件规范:类名小写 + '_'分割(如果有多个单词的case)
// 如果类名:PersonInfo 对应头文件:person_info.hpp
// 声明 'Person' 类
class Person {
public:
// 构造函数声明
Person(const std::string & name, int age);
// 成员函数声明
void printInfo() const; // const成员函数,保证函数不会修改调用对象
// Setters 和 Getters 声明
void setName(const std::string & name);
const std::string & getName() const;
void setAge(int age);
int getAge() const;
private:
// 成员变量
std::string mName;
int mAge;
};
// person.cpp
// 实现代码放到同名cpp文件中
// 构造函数定义
Person::Person(const std::string & name, int age) : mName(name), mAge(age) {}
// 成员函数定义
void Person::printInfo() const {
std::cout "Name: " ", Age: " std::endl;
}
// Setters 和 Getters 定义
void Person::setName(const std::string & name) {
mName = name;
}
const std::string & Person::getName() const {
return mName;
}
void Person::setAge(int age) {
mAge = age;
}
int Person::getAge() const {
return mAge;
}
-
const成员函数
访问控制
-
Public(公共):公共成员可以在类的外部被访问。 -
Private(私有):私有成员只能在类的内部被访问。 -
Protected(受保护):受保护成员可以在类的内部以及其派生类中被访问。
构造函数
class MyClass {
public:
MyClass(int m1, int m2, int m3) : mM1(m1), mM2(m2), mM3(m3) {}
private:
int mM1;
const int mM2;
int & mM3;
};
// 类的初始化方式
MyClass a1(1, 2, 3); // 传统构造函数
MyClass a1 = MyClass(1, 2, 3); // 同上
MyClass a2 = {1, 2, 3}; // 列表初始化,会匹配最合适的构造函数
MyClass a3{1, 2, 3}; // 同上
-
explicit 关键字
析构函数
class MyClass {
public:
MyClass() {
// 构造函数分配资源或执行初始化
data = new int[10]; // 假设动态分配了内存
}
~MyClass() {
// 析构函数释放资源
delete[] data; // 释放动态分配的内存
}
private:
int* data; // 指向动态分配的内存
};
运算符重载
class MyClass {
public:
MyClass() : data(new int[10]) { } // 构造函数
~MyClass() { delete[] data; } // 析构函数
// 拷贝赋值运算符
MyClass & operator=(const MyClass& other) {
if (this != &other) { // 避免自赋值
std::copy(other.data, other.data + 10, data);
}
return *this;
}
private:
int* data;
};
// 使用
MyClass a;
MyClass b = a; // 默认的赋值操作是浅拷贝,这里因为重载了 = 运算符,变成深拷贝
// C++11开始可以删除默认的赋值操作符,从而防止因浅拷贝带来的风险
class MyClass2 {
// ...
MyClass2 & operator=(const MyClass2 & other) = delete; // 禁用赋值操作符
// ...
};
MyClass2 a;
MyClass2 b = a; // 非法,MyClass2的 = 运算符被禁用
-
C++支持重载的运算符 -
转换函数(这个不算运算符重载,例:operator int())
拷贝构造函数和拷贝赋值运算符
class MyClass {
public:
MyClass() : data(new int[10]) { } // 默认构造函数
~MyClass() { delete[] data; } // 析构函数
// 拷贝构造函数
MyClass(const MyClass & other) : data(new int[10]) {
std::copy(other.data, other.data + 10, data);
std::cout "copy init" std::endl;
}
// 拷贝赋值运算符
MyClass & operator=(const MyClass & other) {
if (this != &other) { // 避免自赋值
std::copy(other.data, other.data + 10, data);
}
std::cout "copy =" std::endl;
return *this;
}
private:
int* data;
};
int main() {
MyClass a;
MyClass b;
MyClass c = a;
c = b;
return 0;
}
// 程序输出
// copy init
// copy =
-
浅拷贝 深拷贝
移动构造函数和移动赋值运算符(C++11)
using namespace std;
class BigMemoryPool {
private:
static const int POOL_SIZE = 4096;
int* mPool;
public:
BigMemoryPool() : mPool(new int[POOL_SIZE]{0}) {
cout "call default init" endl;
}
// 编译器会优化移动构造函数,正常情况可能不会被执行
// 可以添加编译选项 “-fno-elide-constructors” 关闭优化来观察效果
BigMemoryPool(BigMemoryPool && other) noexcept {
mPool = other.mPool;
other.mPool = nullptr;
cout "call move init" endl;
}
BigMemoryPool & operator=(BigMemoryPool && other) noexcept {
if (this != &other) {
this->mPool = other.mPool;
other.mPool = nullptr;
}
cout "call op move" endl;
return *this;
}
void showPoolAddr() {
cout "pool addr:" 0]) endl;
}
~BigMemoryPool() {
cout "call destructor" endl;
}
};
BigMemoryPool makeBigMemoryPool() {
BigMemoryPool x; // 调用默认构造函数
x.showPoolAddr();
return x; // 返回临时变量,属于右值
}
int main() {
BigMemoryPool a(makeBigMemoryPool());
a.showPoolAddr();
a = makeBigMemoryPool();
a.showPoolAddr();
return 0;
}
// 输出内容
call default init
pool addr:0x152009600
instance addr:0x16fdfeda0
pool addr:0x152009600
instance addr:0x16fdfeda0 // 编译器优化,这里a和x其实是同一个实例,因此不会触发移动构造
call default init
pool addr:0x15200e600 // 新的临时变量,堆内存重新分配
instance addr:0x16fdfed88 // 临时变量对象地址
call op move // 移动赋值
call destructor
pool addr:0x15200e600 // a的Pool指向的内存地址变成新临时对象分配的地址,完成转移
instance addr:0x16fdfeda0 // a对象的地址没有变化
call destructor
-
返回值优化(RVO) -
命名返回值优化(NRVO)
友元函数和友元类
友元函数
// 声明 Vector2D 类
class Vector2D {
private:
float x_;
float y_;
public:
Vector2D(float x = 0.0f, float y = 0.0f) : x_(x), y_(y) {}
// 友元函数声明,用于重载 + 操作符
friend Vector2D operator+(const Vector2D & a, const Vector2D & b);
// 输出 Vector2D 对象的友元函数
friend std::ostream & operatorstd::ostream & out, const Vector2D & v);
};
// 重载 + 操作符的友元函数定义
Vector2D operator+(const Vector2D & a, const Vector2D & b) {
return Vector2D(a.x_ + b.x_, a.y_ + b.y_);
}
// 重载
std::ostream & operatorstd::ostream & out, const Vector2D & v) {
out "(" ", " ")";
return out;
}
int main() {
Vector2D vec1(1.0, 2.0);
Vector2D vec2(3.0, 4.0);
Vector2D vec3;
vec3 = vec1 + vec2; // 使用友元函数重载的 + 操作符
std::cout "vec1: " std::endl;
std::cout "vec2: " std::endl;
std::cout "vec3: " std::endl; // 输出: vec3: (4, 6)
return 0;
}
友元类
class MyClass; // 前向声明
// 声明一个类(FriendClass),该类将访问MyClass的私有和受保护成员
class FriendClass {
public:
void accessMyClass(MyClass & obj);
};
// 声明主类(MyClass)
class MyClass {
private:
int secret;
public:
MyClass(int val) : secret(val) {}
// 声明FriendClass为MyClass的友元类
friend class FriendClass;
};
// FriendClass成员函数实现
void FriendClass::accessMyClass(MyClass & obj) {
// 可以访问MyClass的私有成员'secret'
std::cout "MyClass secret value is: " std::endl;
}
int main() {
MyClass obj(42); // 创建MyClass对象
FriendClass friendObj; // 创建FriendClass对象
friendObj.accessMyClass(obj); // 访问MyClass的私有成员
return 0;
}
继承
class Base {
// 基类成员
};
class Derived : public Base {
// 派生类成员
};
-
公有继承(public)最常见的继承类型。在公有继承中,基类的公有成员和保护成员在派生类中保持其原有的访问级别,而基类的私有成员在派生类中是不可访问的。
-
保护继承(protected)基类的公有成员和保护成员都成为派生类的保护成员。这意味着它们只能被派生类或其进一步的派生类中的成员函数访问。
-
私有继承(private)私有继承会将基类的公有成员和保护成员都变成派生类的私有成员。这意味着这些成员只能被派生类的成员函数访问,而不能被派生类的派生类访问。
-
虚继承
多态
class Base {
public:
void baseMethod() {
std::cout "Base method" std::endl;
}
virtual void polymorphicMethod() {
std::cout "Base polymorphic method" std::endl;
}
virtual ~Base() {} // 虚析构函数,用于多态
};
// 公有继承派生类
class Derived : public Base {
public:
// 重写基类的虚函数
void polymorphicMethod() override {
Base::polymorphicMethod(); // 可以通过添加限定域调用基类实现
std::cout "Derived polymorphic method" std::endl;
}
};
int main() {
Derived d;
d.baseMethod(); // 调用基类的方法
d.polymorphicMethod(); // 调用派生类重写的方法
Base *b = &d;
b->polymorphicMethod(); // 通过基类指针调用派生类的方法,体现多态
return 0;
}
-
虚函数表 动态绑定
抽象类和纯虚函数
// Interface in C++
class IShape {
public:
virtual void draw() const = 0; // 纯虚函数
virtual ~IShape() {} // 虚析构函数以确保派生类的析构函数被调用
};
class Circle : public IShape {
public:
void draw() const override {
// 实现绘制圆形的代码
}
};
class Rectangle : public IShape {
public:
void draw() const override {
// 实现绘制矩形的代码
}
};
模板类
template typename T>
class MyTemplateClass {
const T& getValue();
public:
T myValue;
};
容器类
智能指针
// 简单示例
// 定义智能指针
// C++11语法
std::unique_ptr
my_unique_ptr(new MyClass()); std::shared_ptr
my_shared_ptr(new MyClass()); // C++14提供了更安全更现代的方法
auto my_unique_ptr = std::make_unique
(); auto my_shared_ptr = std::make_shared
(); // 可以按照构造函数的定义传参
// 调用类的方法和普通指针类似
my_unique_ptr->func();
my_shared_ptr->func();
// 在需要传对象指针和引用的场景
// 类指针类型
void testFunc1(MyClass * p);
testFunc1(my_unique_ptr.get()); // 通过get获取原始指针
// 引用类型
void testFunc2(MyClass & ref);
testFunc2(*my_unique_ptr); // 通过*运算符获取对象的引用
函数对象
// 封装函数
void printHello() {
std::cout "Hello, World!" std::endl;
}
std::functionvoid()> func = printHello;
// 封装Lambda表达式
std::functionint(int, int)> add = [](int a, int b) -> int {
return a + b;
};
int sum = add(2, 3); // sum 的值为 5
// 封装成员函数
class MyClass {
public:
void memberFunction() const {
std::cout "Member function called." std::endl;
}
};
MyClass obj;
std::functionvoid(const MyClass &)> f = &MyClass::memberFunction;
f(obj); // 输出: Member function called.
// 封装带有绑定参数的函数
void printSum(int a, int b) {
std::cout "Sum: " std::endl;
}
int main() {
using namespace std::placeholders; // 对于 _1, _2, _3...
// 绑定第二个参数为 10,并将第一个参数留作后面指定
std::functionvoid(int)> func = std::bind(printSum, _1, 10);
func(5); // 输出: Sum: 15
return 0;
}
0 条评论