认识类和对象
- 面向对象四大特征
抽象:抽出事物 的最本质的特征;
封装:把数据和处理(函数)包在一起;
继承:数据和处理函数的传承;
多态:同一个事物(函数)的多种形态;
类的定义和创建
类的定义:与C++中的’struct‘类似
class 类 名{
成员变量;
成员函数;
};
构成
数据成员(data member)/成员变量/属性:对象内部数据和状态,只能在类定义中声明,可以在成员函数中直接调用。
成员函数/方法:对象相关的操作,可以在类内实现或类外实现。作用域运算符:’::’ 表示函数归属。
访问限制符
private(默认|私有)
public(公开)
protected(保护)实践中,成员变量多数情况使用’private’或’protected’,成员函数多数情况使用’public’。通常使用成员函数改变对象的成员变量。
声明与实现分离
- 头文件 - - 声明
方式:#pragma once或#ifdef ….#endif
作用:防止头文件二次编译 源文件 - -实现
引用头文件:#include <>(标准库函数)/#include “”(自定义/第三方函数)‘class’与’struct’的区别
1.默认的访问控制不同
‘struct’是’public’
‘class’是’private’
2.’struct ‘可以使用花括号内的初始值列表’{…}’初始化,’class’不可以(c++98不可以,C++11可以)。注意:
C++中的’struct’可以有成员函数,而C不可以。
C++中的’struct’可以使用访问控制关键字(’private’,’public’,’protected’)。- 成员变量默认初始化为随机值(主要影响指针)。
对象创建
- 直接定义 - - 类作为类型定义变量 - - 栈上创建
类名 对象1;// 调用默认构造函数
类名 对象2 = 对象1;// 调用复制构造函数
类名 对象3(对象1);// 调用复制构造函数
- 动态创建 - -堆上创建
类名* 对象指针 = new 类名;// 调用默认构造函数
delete 对象指针;
int *p = new int[10];
delete p;//只释放p[0]
delete [] p;//释放全部数组
基本类型的初始化新增语法:
int a(0);//等价于int a = 0;
const float b(1.0);//等价于cosnt float b = 1.0;
空结构体与空类的大小(‘sizeof’)为 ‘1’,主要在于初始化/实例化时,编译器给变量/对象分配内存(地址),通常,‘sizeof(类型)==’sizeof(变量)’。
注意:C++除了特殊情况,很少直接使用malloc/free()申请释放内存,取而代之的是’new/delete’。
’this‘ 指针
作用域 - - 类内部
特点
1.类的一个自动生成、自动隐藏的私有成员
2.每个对象仅有一个’this’指针
3.当一个对象被创建时,‘this’指针就存放指向对象数据的首地址
4.不是对象本身的一部分,不会影响sizeof(对象)结果如果成员函数形参与成员变量同名,使用‘this->’作为前缀区分。
方法
构造函数
- 语法
类名(参数){
函数体
}
- 在对象被创建时自动执行
- 构造函数的函数名与类名相同
没有返回值类型、也没有返回值
调用时机
- 对象直接定义创建 - -构造函数不能被显式调用
- ‘new’动态创建
- 默认构造函数
类中没有显示的定义任何构造函数,编译器就会自动为该类型生成默认构造函数 - 构造函数的三个作用
- 给创建的对象建立一个标示符
- 为对象数据成员开辟内存空间
- 完成对象数据成员的初始化
- 语法
类名(参数):成员变量(参数){
函数体
}
- 作用:初始化非静态成员变量
- 说明:从概念上来讲,构造函数的执行可以分为两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段
- 必须使用初始化列表的情况
- 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面。
- 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面。
- 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。
- 初始化列表与构造函数内部成员变量赋值的区别:成员变量初始化与成员变量赋值
能使用初始化列表的时候尽量使用初始化列表
- 成员变量的初始化顺序:成员变量在使用初始化列表时,与构造函数中初始化成员列表的顺序无关,只与定义的成员变量的顺序有关。
C++的函数可以加默认参数。
写法:
- 默认参数必须写在函数声明中,不能写在函数实现的参数列表中
- 默认参数必须写在所有非默认参数的后面
- 默认参数可以写任意多个
使用:
默认参数可以不用传递值,此时,使用默认值
默认参数可以传值,此时,使用实参值
析构函数
- 语法
~类名{
函数体
}
- 析构函数的函数名与类名相同
- 函数名前必须有一个’~’
- 没有参数
- 没有返回值类型、也没有返回值
只能有一个析构函数
调用时机
- 对象离开作用域
- ‘delete’
- 默认析构函数:类中没有显式的定义析构函数,编译器就会自动为该类型生成默认析构函数
- 作用:释放对象所申请占有的资源
引用(别名)
- 语法
声明:‘const类型名& 对象名’/‘类型名& 对象名’
使用:与对象变量、基本类型变量一样 - 何处使用引用
- 函数参数列表
- 成员变量 - - 对象初始化时,必须显示初始化的变量
- 为何使用引用
- 避免对象复制
- 避免传递空指针
- 使用方便
类型: 基本类型(‘bool’、‘char’、‘int’、‘short’、‘float’)
复合类型(指针、数组、引用)
自定义类型(‘struct’、‘union’、‘class’) 引用作用:取代指针
引用常用于两种情形:成员变量和函数的参数列表。引用与指针的区别:
1. 指针指向一块内存,它的内容是指内存的地址;引用是某块内存的别名;
2. 引用只能定义时被初始化一次,之后不可变;指针可以改变;
3. 引用不能为空,指针可以为空;
4. 引用使用时无需解引用‘*’,指针需要解引用;
5. ‘sizeof(引用)’的得到的是所指向的变量/对象的大小,而‘sizeof(指针)’得到的是指针本身的大小。
拷贝/复制构造函数
- 语法:
类名(类名& 形参){
函数体
}
或者
类名(const 类名&形参){
函数体
}
或者
类名(const 类名 形参){
函数体std:swap();
}
- 调用时机
- 一个对象作为函数参数,以值传递的方式传入函数体
- 一个对象作为函数返回值,以值从函数返回
- 一个对象用作给另外一个对象进行初始化(赋值初始化)
实践说明:
gcc自动NRV(named return value)优化,不执行拷贝构造函数,需要在编译命令添加选项禁止‘-fno-elide-constructors’;
vs会执行拷贝构造函数
总之,尽量不要返回局部对象。
默认拷贝构造函数
- 本质:内存拷贝
- 作用:复制一个已经存在的对象
如何禁用默认拷贝构造函数?在类定义,添加私有的复制构造函数的声明,但不实现。
赋值运算符重载函数
- 语法
类名& operator = (const 类名& 形参){
//赋值操作
return *this;
}
- 调用时机:赋值
- 默认赋值运算符重载函数:内存拷贝
作用:赋值
如何禁用默认赋值运算符重载函数?在类定义,添加私有的赋值运算符重载函数的声明,但不实现
拷贝构造函数与赋值运算符的区别
拷贝构造函数:当一个已经存在的对象来初始化一个未曾存在的对象
赋值操作符:当两个对象都已经存在时
深拷贝与浅拷贝
- 浅拷贝:只拷贝指针地址
- 深拷贝:重新分配堆内存,拷贝指针指向内容
友元
- 作用:非成员函数访问类中的私有成员
- 分类
- 全局友元函数:将全局函数声明成友元函数
- 友元成员函数:类的提前引用声明,将一个函数声明为多个类的友元函数
- 友元类:将整个声明为友元
- 特点
- 友元关系单向性
- 友元关系不可传递
‘const’限定符
- 本质:不可修改
- ‘const’与变量/对象
const 类型 变量 = 初始值
const 类型 对象
- 定义时必须初始化
- 全局作用域声明的’const’变量默认作用域是定义所在文件
- ‘const’对象只能调用’const’成员
’const’与宏定义’#define’的区别
- | const | 宏定义#define |
---|---|---|
编译器处理方式 | 编译运行阶段使用 | 预处理阶段展开/替换 |
类型 | 有具体的类型 | 没有类型 |
安全检查 | 编译阶段会执行类型检查 | 不做任何类型检查 |
存储方式 | 分配内存 | 不分配内存 |
* ‘const’与指针
No. | 类型 | 语法 | 作用 |
---|---|---|---|
1 | const 指针 | 类型* const 变量 = 初始值; | 指针指向地址不能改变 |
2 | 指向’const’对象的指针 | const 类型* 变量 = 初始值; 或 类型 const *变量 = 初始值 | 指针指向的对象不能改变 |
3 | 指向’const’对象的const 指针 | const 类型*const 变量 = 初始值; | 指针指向地址和对象都不能改变 |
No.2是使用最频繁的方式
- ‘const’与引用
‘类型 const & 变量 = 初始值;’与’const 类型& 变量 = 初始值;’都是引用对象不能改变 - ‘const’与函数的参数和返回值
类型 | 语法 | 作用 | 说明 |
---|---|---|---|
const 参数 | 返回值类型 函数(const 类型 形参) | 函数内部不能改变参数的值 | 这样的参数的输入值 |
const 返回值 | const 返回值类型 函数(形参列表) | 函数的返回值不能改变 | 常用于字符串/指针 |
const 成员变量
- 不能在类声明中初始化’const’数据成员
- ‘const’成员变量只能在类构造函数的初始化列表中初始化
如果使用’const’成员变量不能使用赋值运算符重载函数
声明
class 类名{
public:
返回值类型 函数名(形参列表) const;
};
- 定义
返回值类型 函数名(形参列表) const;
必须在成员函数的声明和定义后都加上’const’
const修饰位置 | 作用 |
---|---|
变量 | 变量不可修改,通常用来代替’#define’ |
对象/实例 | 对象的成员变量不可修改,只能调用’const’成员函数 |
函数参数 | 参数不能在函数内部修改,只作为入参 |
函数返回值 | 返回的结果不能被修改,常用于字符串 |
成员变量 | 只能在初始化列表中初始化 |
成员函数 | 不改变成员变量 |
只要能够使用’const’,尽量使用’const’。
‘static’限定符
本质
1. 生存周期:整个程序的生存周期;
2. 作用域:属于类,不属于对象.
- 声明
class 类名{
static 返回类型 函数(形参列表);
};
- 定义
返回类型 类名::函数(形参列表){
函数体;
}
- 调用
类名::函数(实参列表);
- 规则
- ‘static’只能用于类的声明中,定义不能标示为‘static’
- 非静态是可以访问静态的方法和函数
- 静态成员函数可以设置‘private’、‘public’、‘protected’访问权限
- 禁忌
- 静态成员函数不能访问非静态函数或者变量
- 静态成员函数不能使用‘this’关键字
- 静态成员函数不能使用cv限定符(’const‘与’virtual‘)
因为静态成员函数是属于类而不是某个对象,静态成员变量所有类的对象/实例共享
static 修饰位置 | 作用 |
---|---|
变量 | 静态变量 |
函数 | 只源文件内部使用的函数 |
成员变量 | 对象共享变量 |
成员函数 | 类提供的函数,或者作为静态成员对象的接口 |
单例模式:使用静态成员变量和静态成员函数。
‘const static’限定符
变量类型 | 声明位置 |
---|---|
一般成员变量 | 在构造函数初始化列表中初始化 |
const 成员变量 | 必须在构造函数初始化列表中初始化 |
static 成员变量 | 必须在类外初始化 |
static const /const static 成员变量 | 变量声明处或者类外初始化 |
注意:‘static const’/‘const static’成员变量必须是整形。
函数的调用优化
‘inline’ - - 宏定义的接班人。- 条件:一般用在代码比较简单的函数
- 语法
- 关键字‘inline’必须与函数实现/定义体放在一起才能使函数成为内联
- 定义在类声明之中的成员函数将自动地成为内联函数
- 慎用内联
- 如果函数体内的代码比较长,使用内联将导致内存消耗代价较高
- 如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大
- 不要随便地将构造函数和析构函数的定义放在类声明中
本质:内联函数的代码直接替换函数调用,省去函数调用的开销
运算符重载
- 语法:运算符重载主要有两种方式实现:
- 成员函数运算符重载
返回值类型 operator 运算符(参数){
函数体
}
- 友元函数运算符重载
- 成员函数运算符重载
friend 返回值类型 operator 运算符(形参列表){
函数体
}
- 分类
No. | 类型 | 运算符 | 成员函数 | 友元函数 |
---|---|---|---|---|
1 | 双目算术运算符 | ‘+’ ‘-’ ‘*’ ‘/’ ‘%’ | ‘类名 operator 运算符(const 类名&)const’ | ‘类名 operator 运算符(const 类名&,const 类名&)’ |
2 | 关系运算符 | ‘==’ ‘!=’ ‘>’ ‘<’ ‘>=’ ‘<=’ | ‘bool operator 运算符 (const 类名&)const’ | ‘bool operator 运算符 (const 类名&,const 类名 &)’ |
3 | 双目逻辑运算符 | ‘&&’ ‘¦¦’ | ‘bool operator 运算符(const 类名& )const ‘ | bool operator 运算符 (const 类名&,const 类名&) |
4 | 单目逻辑运算符 | ! | bool operator !() const | bool operator ! (const 类名&) |
5 | 单目算术运算符 | + - | 类名& operator 运算符 () | 类名& operator 运算符 (const 类名&) |
6 | 双目位运算符 | & ¦ | 类名 operator 运算符 (const 类名& ) const | 类名 operator 运算符 (const 类名& ,const 类名& ) |
7 | 单目位运算符 | ~ | 类名 operator ~ () | 类名 operator ~ (类名&) |
8 | 位移运算符 | << >> | 类名 operator 运算符 (int i) const | 类名 operator 运算符 (const 类名&,int i) |
9 | 前缀自增减运算符 | ++ -- | 类名& operator 操作符 () | 类名& operator 操作符 (类名&) |
10 | 后缀自增减运算符 | ++ -- | 类名 operator ++ (int) | 类名 operator ++ (类名&,int) |
11 | 复合赋值运算符 | += -= *= /= %= &= ¦= ^= | 类名& operator 运算符 (const 类名& ) | 类名& operator += (类名&,const 类名&) |
12 | 内存运算符 | new delete | 参见说明 | 参见说明 |
13 | 流运算符 | >> << | - | 参见说明 |
14 | 类型转换符 | 数据类型 | 参见说明 | - |
15 | 其他运算符重载 | = [] () -> | 参见说明 | - |
说明:内存运算符比较复杂;
说明
- 内存运算符比较复杂:
成员函数
- 内存运算符比较复杂:
void *operator new(size_t size);
void *operator new[](size_t size);
void operator delete(void*p);
void operator delete [](void* p);
友元函数
void *operator new(类名,size_t size);
void *operator new[](类名&,size_t size);
void operator delete(类名&,void*p);
void operator delete [](类名&,void* p);
- 流运算符
流运算符只能使用友元函数实现。
inline ostream &operator << (ostream&, 类名&)
inline istream &operator >> (istream&, 类名&)
- 类型转换符
这些运算符只能使用成员函数实现。
operator char* () const;
operator int ();
operator const char () const;
operator short int () const;
operator long long () const;
- 其他运算符重载
这些运算符只能使用成员函数实现。
类名& operator = (const 类名& );
char operator [] (int i);//返回值不能作为左值
const char* operator () ();
T operator -> ();
规则
- 五个不能重载的运算符:成员运算符
.
、指针运算符*
、作用域运算符::
、sizeof
、条件运算符?:
- 不允许用户自定义新的运算符,只能对已有的运算符进行重载
- 重载运算符不允许改变运算符原操作数的个数
- 重载运算符不能改变运算符的优先级
- 重载运算符函数不能有默认的参数,会导致参数个数不匹配
- 五个不能重载的运算符:成员运算符
本质
函数重载