1. 类和结构体的区别
- C++对struct的功能进行了提升,不仅可以定义变量,还可以定义函数,还可以使用private、public关键字,还不用
typedef重命名就能直接用名字定义对象- class的默认访问权限是私有的
- struct的默认访问权限是公有的
2. 类的基本概念
1. 特点
1.1 默认生成四个函数
- 无参构造函数
- 拷贝构造函数
- 赋值运算符函数
- 析构函数
1.2 空类
为了区分不同的对象,就分配一个字节大小的空间(使地址不同)
2. 举个栗子
class Computer {
// 大括号以内称为类的内部
public:
// 成员函数
void setBrand(const char *name)
{
strcpy(_name, name);
}
void setPrice(int price)
{
_price = price;
}
void print()
{
cout << "name = " << _name << endl
<< "price = " << _price << endl;
}
private: // 体现封装性,不能对外提供,仅在本类的范围内可以调用
// 数据成员
char _name[20];
int _price;
}; // 分号不能去掉
3. 构造函数
1. 作用
完成数据成员初始化操作
2. 特点
- 对象在创建时,自动调用构造函数
- 默认情况下,编译器会自动生成一个默认的无参构造函数
- 如果自定义了构造函数,那么编译器就不会生成默认构造函数,如果想创建无参对象,需要自定义无参构造函数,或给参
数加默认值- 构造函数是可以重载的
- 数据成员的初始化与其在初始化列表中的顺序没有关系,只与数据成员在声明的时候的顺序有关系
- 构造函数不能以对象加点的形式进行调用,可以直接调用,直接调用的时候会创建对象
Point(int ix = 0, int iy = 0)
: _ix(ix)
, _iy(iy) // 初始化列表
{
}
4. 析构函数
1. 作用
完成数据成员的清理工作
2. 特点
- 没有返回类型,参数列表不能有参数
- 对象在销毁的时候,自动调用析构函数,(但调用析构函数不代表对象就被销毁了)
- 默认情况下,编译器会自动生成析构函数,啥都没做
- 析构函数可以显式调用(对象加点的形式调用),但不建议
- 析构函数调用场景
5. 拷贝构造函数
1. 浅拷贝(编译器自动生成)
Computer::Computer(const Computer &rhs)
: _name(rhs._name) // 会有doule free的问题
, _price(rhs._price)
{
}
2. 深拷贝
Computer::Computer(const Computer &rhs)
:_name(new char[strlen(rhs._name) + 1]())
, _price(rhs._price)
{
}
3. 调用时机
- 当用一个已经创建的对象去初始化一个新的对象时
- 当形参和实参都是对象时,在进行形参和实参结合时
- 当函数的返回类型为对象时,在执行return语句时
4. 拷贝构造函数的参数问题
4.1 左值和右值的区别
- 左值:能取地址的叫左值。字符串常量是左值
- 右值:不能取地址的叫右值。临时变量、临时对象、匿名变量、匿名对象、字面值常量(如数字10)
4.2 参数问题
Q1:拷贝构造函数中的引用符号&可以去掉吗?
A1:不可以,会出现无限递归导致栈溢出
如果去掉之后,那么当调用拷贝构造函数的时候会满足形参与实参相结合,继续调用拷贝构造函数,然后又会满足一
个已经存在的对象去初始化刚刚创建的对象,如此循环下去。而函数的参数是会入栈的,而栈是有大小的,如此循环
入栈,会导致栈溢出Q2:拷贝构造函数中的const符号可以去掉吗?
A2:不可以,如果传递的实参是右值的时候,可能会出现非const左值引用不能绑定右值的问题
6. this 指针
特点
Computer *const this
1. this指针指向对象本身
2. this指针隐藏于每一个非静态成员函数的第一个参数的位置
3. this指针是一个指针常量,不能改变指向
7. 赋值运算符函数
1. 特点
默认情况下:编译器会自动生成一个赋值运算符函数
2. 参数和返回值问题
Q1:赋值运算符函数参数中的引用可以去掉吗?
A1:不可以,会多调用一次拷贝构造函数
会出现形参与实参相结合,但由于拷贝构造函数中的参数带了引用,所以不会出现无限递归的问题Q2:赋值运算符函数参数中的const可以去掉吗?
A2:不可以,可能会出现左值引用不能绑定右值的问题
Q3:赋值运算符函数返回值中的引用可以去掉吗?
A3:不可以,会多调用一次拷贝构造函数
函数的返回值类型为类类型时,会满足拷贝构造函数调用时机3Q4:赋值运算符函数返回值类型可以不是对象吗?
A4:不可以,连等的情况
8. 四种特殊数据成员的初始化
- 常量数据成员:在初始化列表进行显式初始化
- 引用数据成员:在初始化列表进行显式初始化,大小占用一个指针的大小
- 对象数据成员:在初始化列表进行显式初始化
- 静态数据成员:属于类,被所有该类实例化的对象共享,不占用对象的大小空间。在实现文件的全局静态位置初始
化,不能在初始化列表进行初始化float Computer::_totalPrice = 0
9. 特殊成员函数(研究this指针)
1. 静态成员函数
static void printTotalPrice();
- 可以通过类名::静态函数名 和 对象.静态函数名 进行调用
- 没有this指针,所以不能访问非静态的数据成员和非静态成员函数
- 但非静态成员函数可以访问静态数据成员和静态成员函数
2. const成员函数
void print(/* Computer const * const this */) const;
- this指针和非const成员函数不同,所以可以重载
- 只读,内容不能修改
- 非const对象调用非const版本的成员函数,const对象调用const版本的成员函数
- 非const对象
可以
调用const版本的成员函数,const对象不能
调用非const版本的成员函数- 建议先写const版本的成员函数
10. 对象组织
1. const对象:只能调用const成员函数,只读
const Computer cc("lenovo", 5600);
2. 堆对象:
Computer *pc = new Computer("lenovo", 5600);
delete pc;
3. 对象数组
Computer array[10] = {{"lenovo", 5600}, {"huawei", 6000}};
Computer *pArray = new Computer[10]();
pArray[1] = {"lenovo", 5600};
(pArray+1)->print();
delete [] pArray;
11. 单例模式
- 单例模式设计目的:一个类只能创建一个对象
- 用途:全局唯一的对象,字典库、词典、日志记录器等
#include <iostream>
using namespace std;
class Singleton
{
public:
static Singleton *getInstance() // 能在类外通过函数名调用,间接创建对象
{
if (nullptr == _pInstance)
_pInstance = new Singleton();
return _pInstance;
}
static void destroyInstace() // 间接销毁对象
{
if (_pInstance)
{
delete _pInstance; // 调用析构函数
_pInstance = nullptr;
}
}
private:
Singleton() // 构造函数和析构函数私有,不能直接在类外创建对象
{
cout << "Singleton()" << endl;
}
~Singleton()
{
cout << "~Singleton()" << endl;
}
private:
static Singleton * _pInstance; // 只有这一个对象
};
Singleton * Singleton::_pInstance = nullptr;
void test()
{
Singleton *ps1 = Singleton::getInstance();
Singleton *ps2 = Singleton::getInstance();
cout << "ps1 = " << ps1 << endl;
cout << "ps2 = " << ps2 << endl;
ps1->destroyInstace();
ps2->destroyInstace();
}
int main(int argc, char **argv)
{
test();
return 0;
}
12. 内存对齐
- 数据成员要对齐,每个数据成员都要对齐
- 结构体要对齐,最后结构体的大小是结构体中最大数据成员的整数倍
- 结构体里面还有结构体的时候,里面的结构体也要满足对齐规则(要按照最大数据成员的整数倍进行对齐)
#pragma pack(4)
和 系统位数取个最小值,按这个最小值对齐(如果最小值为4,则doule要分两行存储)
13. new和delete表达式
1. new表达式的三个步骤
- 执行operator new标准库函数,申请未初始化的空间,就是为了构造对象
- 在申请的空间执行构造函数,初始化对象的数据成员
- 返回指针
2. delete表达式的三个步骤
- 执行析构函数,回收对象中数据成员所占用的资源
- 执行operator delete库函数,回收对象本身所占用的资源
Q1:怎样只能创建栈对象,不能创建堆对象
A1:将operator new/delete
函数私有
Q2:怎样只能创建堆对象,不能创建栈对象
A3:将析构函数
私有,然后自定义一个销毁函数void destroy(){delete this;}
(不能在destory()里直接执行析构函数,因为这样堆对象本身所占用的空间就不会被回收)