类的定义

类是C++面向对象编程的基本构建块,它将数据和操作数据的函数封装在一起。

类的基本语法

类的组成部分

数据成员(属性)

成员函数(方法)

访问修饰符

C++提供了三种访问修饰符来控制类成员的访问权限:

对象创建

栈上创建对象

栈上创建的对象会在超出作用域时自动销毁。

堆上创建对象

堆上创建的对象需要手动释放内存。

构造函数

默认构造函数

带参数的构造函数

初始化列表

Tip

如果构造函数只接受一个参数,并且允许从其他类型的值隐式或显式地创建当前类的对象,这个构造函数也可以称为转换构造函数。

对象创建的其他方式

拷贝构造

移动构造

列表初始化

对象数组的创建

对象销毁

栈上对象的销毁

栈上创建的对象会在超出其作用域时自动销毁。

堆上对象的销毁

堆上创建的对象需要手动释放内存。

析构函数

析构函数是对象销毁时自动调用的特殊成员函数,用于释放资源。

对象销毁的顺序

局部对象

局部对象的销毁顺序与创建顺序相反(后进先出)。

成员对象

类的成员对象销毁顺序与初始化顺序相反,与声明顺序无关。

继承关系中的对象

派生类对象销毁时,先调用派生类的析构函数,再调用基类的析构函数。

虚析构函数

当通过基类指针删除派生类对象时,需要虚析构函数确保正确调用派生类的析构函数。

对象数组的销毁

拷贝构造函数

拷贝构造函数是C++中一种特殊的构造函数,用于构造一个新对象。其形式为:

其中other是同类型的另一个对象的引用,通常声明为常量引用。

调用时机

拷贝构造函数在以下情况下会被调用:

  1. 用一个对象初始化另一个对象

  2. 函数按值传递对象参数

  3. 函数按值返回对象

默认拷贝构造函数

如果没有显式定义拷贝构造函数,编译器会提供一个默认的拷贝构造函数,执行浅拷贝(逐成员复制)。

自定义拷贝构造函数

当类包含动态分配的资源时,需要自定义拷贝构造函数实现深拷贝。

浅拷贝与深拷贝

浅拷贝

深拷贝

禁用拷贝构造函数

在某些情况下,可能需要禁止对象的复制:

转换构造函数

转换构造函数允许从其他类型的值隐式或显式地创建当前类的对象。

转换构造函数是通常接受一个参数,用于将其他类型转换为当前类类型:

 

赋值运算符函数

赋值运算符函数用于定义对象之间的赋值行为。它在已经存在的对象之间进行赋值操作时被调用。

赋值运算符函数的形式为:

其中other是要赋值的源对象,返回类型通常是当前类的引用,以支持连续赋值。

默认赋值运算符

如果没有显式定义赋值运算符,编译器会提供一个默认的赋值运算符,执行浅拷贝(逐成员复制)。

自定义赋值运算符

当类包含动态分配的资源时,需要自定义赋值运算符实现深拷贝。

Note

自我赋值检查

防止对象赋值给自己时出现问题:

异常安全

确保在分配新资源失败时不会丢失原有资源:

返回*this

返回对象自身的引用,支持连续赋值:

与拷贝构造函数的区别

禁用赋值运算符

在某些情况下,可能需要禁止对象的赋值:

特殊数据成员

静态数据成员

静态数据成员是属于类而非对象的成员变量,所有对象共享同一个静态数据成员。

特点:

常量数据成员

常量数据成员是在对象创建后不能修改的成员变量。

特点:

引用数据成员

引用数据成员是对其他对象的引用。

特点:

特殊的成员函数

静态成员函数

静态成员函数是属于类而非对象的函数,它们可以在不创建类的实例的情况下被调用。

特点

  1. 不依赖对象实例:静态成员函数不与任何对象关联,可以通过类名直接调用

  2. 没有this指针:由于不依赖对象实例,静态成员函数没有this指针

  3. 只能访问静态成员:静态成员函数只能访问类的静态成员(静态数据成员和其他静态成员函数)

  4. 不能声明为const:静态成员函数不能被声明为const、volatile或virtual

  5. 不能使用非静态数据成员:不能直接访问类的非静态数据成员

示例

const成员函数

const成员函数是承诺不会修改对象状态的成员函数,通过在函数声明后加const关键字实现。

特点

  1. 不能修改对象状态:const成员函数不能修改非mutable的数据成员

  2. 可以用于const对象:const对象只能调用const成员函数

  3. this指针为const:在const成员函数中,this指针是指向const对象的指针

  4. 可以重载:可以基于const限定符重载成员函数

  5. 可以修改mutable成员:即使在const成员函数中,也可以修改声明为mutable的成员

示例

两者的比较

特性静态成员函数const成员函数
调用方式通过类名或对象只能通过对象
this指针没有const this指针
访问限制只能访问静态成员可以访问所有成员,但不能修改非mutable成员
对象要求不需要对象实例可以被const和非const对象调用

单例模式

单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取该实例。

单例模式的核心思想是:

单例模式的实现方式

懒汉式(延迟初始化)

特点:

饿汉式(立即初始化)

特点:

线程安全的懒汉式(双检锁)

特点:

Meyers单例

特点:

单例模式的销毁

使用智能指针

使用嵌套类作为析构器

Tip

单例模式的析构函数是否需要私有?取决于具体的设计需求和实现方式:

析构函数私有的情况:需要严格控制对象生命周期

  • 防止外部代码意外删除单例对象

  • 完全控制对象的生命周期

  • 避免栈上创建对象


析构函数公有的情况

1.Meyers单例

  • 自动管理生命周期

  • 线程安全(C++11保证)

  • 程序结束时自动调用析构函数

  • 符合RAII原则

2.智能指针管理的单例

单例模式的优缺点

优点:

缺点:

string

C++风格字符串是指C++标准库中的std::string类,它是对C风格字符串的封装和扩展,提供了更安全、更方便的字符串处理方式。

std::string是C++标准库中的字符串类,定义在<string>头文件中,属于std命名空间。

创建和初始化

基本操作

字符串连接

访问字符

字符串长度和容量

子字符串

查找和替换

插入和删除

比较

与C风格字符串的转换

C++字符串转C风格字符串

C风格字符串转C++字符串

字符串流

字符串转换

C++风格字符串与C风格字符串的比较

特性std::stringC风格字符串
内存管理自动手动
边界检查支持(at方法)不支持
动态调整大小支持不支持
字符串操作丰富的成员函数需要使用库函数
连接操作使用+运算符需要使用strcat等函数
安全性低(容易缓冲区溢出)

vector

std::vector是C++标准模板库(STL)中最常用的容器之一,它实现了动态数组的功能,能够自动管理内存并提供丰富的操作接口。

std::vector是一个模板类,定义在<vector>头文件中,属于std命名空间。它提供了一个可以动态增长的数组,具有以下特点:

创建和初始化

基本操作

添加和删除元素

访问元素

大小和容量

迭代器

常用算法

std::vector可以与STL算法库一起使用:

二维vector

与C风格数组的比较

特性std::vectorC风格数组
大小可动态调整固定
内存管理自动手动
边界检查支持(at方法)不支持
功能丰富的成员函数和算法基本操作
性能略有开销最高效
安全性

vector的底层实现

vector在底层通常由三个指针(或迭代器)组成:

  1. start:指向数组中第一个元素的位置

  2. finish:指向最后一个元素之后的位置(past-the-end)

  3. end of storage:指向分配内存的末尾

内存管理策略

vector使用连续的内存块来存储元素,这使得它能够提供随机访问的能力。当创建一个空的vector时,通常不会立即分配内存,而是在添加第一个元素时才分配。

当vector需要更多空间时(例如通过push_back添加元素),它会:

  1. 分配一个更大的新内存块(通常是当前容量的1.5倍或2倍,具体倍数取决于实现)

  2. 将现有元素复制或移动到新内存

  3. 释放旧内存

  4. 更新指针

这种策略确保了push_back操作的均摊时间复杂度为O(1)。

当vector被销毁或调用clear()方法时,它会销毁所有元素并释放分配的内存。resize()shrink_to_fit()也可能导致内存重新分配。

简化的自定义vector

注意事项

  1. 迭代器失效:在修改vector时,迭代器可能会失效

  2. 引用失效:当vector重新分配内存时,之前的引用会失效

  3. 使用reserve避免频繁重新分配