目录
一、C++简介
1、学习各个点的重要级别介绍
2、为什么要学习C++?
3、 C++语言的发展史(了解)
4、 C++特点(了解)
5、 面向过程与面向对象区别(熟悉)
6、 面向对象编程的重要知识点(熟悉)
8、 开发环境(掌握)
二、从C到C++
1、 引用(掌握)
1.1 概念
1.2 引用的性质
1.3 引用的参数
【面试题】
核心区别
2、 赋值(熟悉)
3、 键盘输入(熟悉)
4、 string字符串类(掌握)
5、 函数
5.1 内联函数(熟悉)
5.2 函数重载(重点)
5.3 哑元函数(熟悉)
三、面向对象基础
1、类与对象(重点)
1.1 概念
1.2 类的内容
1.3 对象的创建
【面试题】
核心区别
【练习】1、键盘输入一个100-999之间的数,依次输出这个数的个十百位。
2、封装(重点)
3、构造函数(掌握)
3.1 基本使用
3.2 构造初始化列表
3.3 隐式调用和显式调用
3.4 拷贝构造函数
3.4.1 概念
【思考】默认的拷贝构造函数是否存在隐患?
3.4.2 浅拷贝
3.4.3 深拷贝
【思考】当前深拷贝的代码是否存在隐患?
4、 析构函数(析构函数)
5、作用域限定符::
5.1 名字空间(熟悉)
5.2 类内声明,类外定义(掌握)
6、this指针(掌握)
6.1 概念
6.2 功能
6.2.1 类内调用成员
6.2.2 区分重名的成员变量和局部变量
6.2.3 链式调用
未完待续,后面内容继续从此处更新......
一、C++简介
1、学习各个点的重要级别介绍
重点一般分为一下几个级别:
● 重点:面试考试大概率涉及,需要不借助任何的资料掌握。
● 掌握:面试考试可能涉及,需要不借助任何资料掌握。
● 熟悉:面试考试可能涉及,可以稍微参考资料掌握。
● 了解:面试考试小概率涉及,能吹吹就行。
2、为什么要学习C++?
1、 C++是一个新的就业方向。
2、 C++可以做什么?
● 服务器开发:游戏服务器、推荐服务器等等。当然还包括应用程序开发、嵌入式软件开发。
● AI人工智能
● QT,应用程序开发
3、 拓展知识面
● 之前学习的C语言,是面向过程的编程语言。C++面向对象编程思想的一门语言。
3、 C++语言的发展史(了解)
1983年,贝尔实验室(Bell Labs)的Bjarne Stroustrup发明了C++。 C++在C语言的基础上进行了扩充和完善,是一种面向对象程序设计(OOP)语言。
Stroustrup说:“这个名字象征着源自于C语言变化的自然演进”。还处于发展完善阶段时被称为“new C”,之后被称为“C with Class”。C++被视为C语言的上层结构,1983年Rick Mascitti建议使用C++这个名字,就是源于C语言中的“++”操作符(变量自增)。而且在共同的命名约定中,使用“+”以表示增强的程序。
常用于系统开发,引擎开发、嵌入式开发等应用领域, 至今仍然是最受广大程序员喜爱的编程语言之一。
4、 C++特点(了解)
● 在支持C基础上,全面支持面向对象编程
● 编程领域广泛,功能强大(最难的编程语言之一)
● C++语言的标准还一直保持着更新,本次学习主要以ISO C++98与11 标准为主。
● 为数不多的支持底层操作的面向对象语言。
● 再面向对象语言中运行效率极高。
5、 面向过程与面向对象区别(熟悉)
【思考】如果把大象装进冰箱,应该怎么做?
1、 (我)打开冰箱
2、 (我)把大象放进去
3、 (我)把冰箱门关上
上面这种方式就是典型的面向过程的编程思想,这种思想关注的重点是“过程”,“过程”指的是一系列有序的步骤,只要按照这个步骤来做,就可以得到预计的结果。这种程序的特点执行效率极高(因为都是亲历亲为)。适合小体量的软件项目编程,偏性能的项目一般这样做。
使用面向对象的思想把大象装进冰箱:
1、 (我)把大象和冰箱拟人化
2、 (我)给大象和冰箱安排任务
3、 大象和冰箱执行任务
面向对象的语言,关注的重点是“对象”,再计算机中,“对象”可以理解为一系列由于某种原因聚焦再一起的数据,再编程过程中处理对象之间的关系。这种思考方式更近于人类的思考方式。这种程序也有缺点,缺点是运行效率低,但是编程效率高,适合大规模的软件项目。
总结:
6、 面向对象编程的重要知识点(熟悉)
● 类
● 对象
● 封装
● 继承
● 多态
封装 -> 继承 -> 多态。也被称为面向对象的三大特性。
8、 开发环境(掌握)
单论C++的开发环境,没有严格的要求,为了学习的方便,直接使用下门课程的环境进行开发。即Qt Creator。
只需要再一个不包含任何中文路径下,一直点击下一步即可。
安装完成后,为了使其支持中文输出,更改下面的编码:
设置完成后,重新启动QtCreator,就可以新建一个C++项目了,操作步骤如下:
1、 点击
2、 再弹出的窗口中,按照下图所示进行操作
3、 在弹出的窗口中,先输入项目名称,再设定项目路径,最后点击下一步。
需要注意,中途不得出现任何的中文字符,和特殊字符。
4、 再弹出的窗口中,直接点击“下一步”
5、 在项目管理界面直接点击完成。
6、 可以看到新项目已经创建成功了
.pro文件为项目配置文件,通常不需要手动进行编辑,只有在项目中完全开启C++11功能时,增加下面这句话即可。
- QMAKE_CXXFLAGS += -std=c++11
复制代码
添加完成后,忘记保存。
.cpp文件为C++的源文件,用于编写C++代码。
点击左下角的
(快捷键ctrl+R),即可运行项目。
补充几个快捷键的使用技巧。
1、 Alt + 0 显示/隐藏边栏。
2、 ctrl+A全选,再ctrl+i对齐。 代码排版
3、 ctrl+F 搜索+替换
更改主题
二、从C到C++
本章介绍一些C++拓展的非面向对象的功能。
1、 引用(掌握)
1.1 概念
引用从一定程度上讲是指针的平替。几乎被所有的面向对象编程语言所使用,引用相当于对某一目标变量起“别名”。
操作引用与操作原变量完全一样。
- #include <iostream>
- using namespace std;
- int main()
- {
- int a = 1;
- // b是a的引用
- int &b = a;
- cout << a << " " << &a << endl; // 1 0x61fe88
- cout << b << " " << &b << endl; // 1 0x61fe88
- return 0;
- }
复制代码
1.2 引用的性质
● 可以改变引用的值,但是不能再次成为其他变量的引用。
- #include <iostream>
- using namespace std;
- int main()
- {
- int a = 1;
- // b是a的引用
- int &b = a;
- int c = 3;
- b = c; // 赋值,不是引用,b还是a的引用
- b++;
- cout << a << " " << &a << endl; // 4 0x61fe88
- cout << b << " " << &b << endl; // 4 0x61fe88
- cout << c << " " << &c << endl; // 3 0x61fe84
- return 0;
- }
复制代码
● 声明引用时,必须要初始化。
- #include <iostream>
- using namespace std;
- int main()
- {
- int a = 1;
- // int &b; // 错误,引用必须初始化
- b = a;
- return 0;
- }
复制代码
● 声明引用时(普通使用),不能初始化为NULL。
- #include <iostream>
- using namespace std;
- int main()
- {
- int a = 1;
- // int &b = NULL; // 错误,常量不能直接起别名
- return 0;
- }
复制代码
● 声明引用的时候,初始化的值可以是纯数值,但是此时要用const关键字修饰引用,表示该引用为常量引用,这样的引用的值不可变。
- #include <iostream>
- using namespace std;
- int main()
- {
- const int &b = 12; // 常量引用
- // b = 444; // 错误 常量引用的数值不能被改变的。
- return 0;
- }
复制代码
● 可以将变量引用的地址,赋值给一个指针,此时指针指向的还是原来的变量。
- #include <iostream>
- using namespace std;
- int main()
- {
- int a = 1;
- int &b = a;
- int *c = &b; // C同时指向了a和b
- cout << a << " " << &a << endl; // 1 0x61fe84
- cout << b << " " << &b << endl; // 1 0x61fe84
- cout << *c << " " << c << endl; // 1 0x61fe84
- return 0;
- }
复制代码
● 可以对指针建立引用
- #include <iostream>
- using namespace std;
- int main()
- {
- int a = 1;
- int &b = a;
- int *c = &b;
- int *&d = c; // d是c的引用
- cout << a << " " << &a << endl; // 1 0x61fe84
- cout << b << " " << &b << endl; // 1 0x61fe84
- cout << *c << " " << c << endl; // 1 0x61fe84
- cout << *d << " " << d << endl; // 1 0x61fe84
- return 0;
- }
复制代码
● 可以使用const修饰引用,此时如果原变量的值改变,引用的值也会改变。
- #include <iostream>
- using namespace std;
- int main()
- {
- int a = 2;
- const int &b = a;
- // b++; 错误 b是只读的
- a++;
- cout << a << " " << &a << endl; // 3
- cout << b << " " << &b << endl; // 3
- return 0;
- }
复制代码
1.3 引用的参数
【思考】
写一个函数,函数有两个参数a和b,函数的功能是交换两个传入的参数原来变量的值。
- #include <iostream>
- using namespace std;
- // 法一:操作拷贝的是副本,不符合需求
- void test(int a,int b)
- {
- int temp = a;
- a = b;
- b = temp;
- cout << a << endl;
- cout << b << endl;
- }
- // 法二:C语言写法,在C++中不推荐这种写法
- void test1(int *a,int *b)
- {
- *a = *a ^ *b;
- *b = *a ^ *b;
- *a = *a ^ *b;
- }
- // 法三:C++编程方式,符合需求
- void test2(int &a,int &b) //引用作为参数传递
- {
- a = a ^ b;
- b = a ^ b;
- a = a ^ b;
- }
- int main()
- {
- int a1 = 1;
- int b1 = 2;
- test2(a1,b1);
- cout << a1 << endl;
- cout << b1 << endl;
- return 0;
- }
复制代码
引用作为参数进行定义
好处:在参数传递的时候,不会产生副本,这样会提高程序的运行效率。我们在正常编译中,建议使用引用传递参数。
引用形参,在不参与计算的情况下,我们建议使用const进行修饰,以达到引用的安全性。
- #include <iostream>
- using namespace std;
- void test2(const int &a)
- {
- // a++; // 错误 const修饰,无法修改
- cout << a << endl; //1
- }
- int main()
- {
- int a1 = 1;
- test2(a1);
- cout << a1 << endl; //1
- return 0;
- }
复制代码
【面试题】
指针和引用的区别?
指针:是一个变量,存储另一个变量的内存地址。
需要显式解引用(*)来访问目标值。
引用:是变量的别名,本质是绑定到目标变量的“另一个名字”。
无需解引用,直接操作引用即操作原变量。
核心区别
特性 | 指针 | 引用 |
---|
初始化要求 | 可以声明时不初始化(但建议初始化) | 必须初始化,且不能重新绑定到其他变量 |
空值(Null) | 可以赋值为 nullptr | 不能为空,必须绑定有效对象 |
重定向能力 | 可以指向不同对象 | 一旦初始化,无法更改绑定对象 |
内存占用 | 占用独立内存(存储地址) | 不占用独立内存(仅是别名) |
多级间接访问 | 支持多级指针(如 int**) | 不支持多级引用,但支持对指针的引用(如 int*&) |
运算操作 | 支持指针算术(+, -, ++ 等) | 不支持运算,操作等同于原变量 |
2、 赋值(熟悉)
通常使用=号进行赋值操作,C++新增了以下赋值的语法:
- #include <iostream>
- using namespace std;
- int main()
- {
- int a(1); // 等同于 int a = 1;
- cout << a << endl;
- int b(a); // 等同于 int b = a;
- cout << b << endl;
- int c(a+b); // 等同于 int c = a + b;
- cout << c << endl;
- return 0;
- }
复制代码
在C++11中对上述写法进行升级
- #include <iostream>
- using namespace std;
- int main()
- {
- int a(1); // 等同于 int a = 1;
- cout << a << endl;
- double b = 3.14;
- int b1 = b;
- cout << b1 << endl; // 3
- int b2(b);
- cout << b2 << endl; // 3
- int b3{b}; // 升级:对数据窄化提出警告
- cout << b3 << endl; // 3
-
- return 0;
- }
复制代码
3、 键盘输入(熟悉)
可以使用cin把用户在命令行中输入的内容赋值到变量中。
cin与cout一样,都是数据头文件iostream中的标准输入输出流。
- #include <iostream>
- using namespace std;
- int main()
- {
- int a;
- // C++的字符串是string
- string str;
- cout << "请输入一个数字和字符串" << endl;
- cin >> a >> str; // 接收键盘输入,一个整数和一个字符串,可以连续操作
- cout << a << str << endl;
- return 0;
- }
复制代码
如果cin输入的字符串包含空格,可以使用下面的方式:
- #include <iostream>
- using namespace std;
- int main()
- {
- // C++的字符串是string
- string a;
- cout << "请输入一个字符串,可以包含空格" << endl;
- getline(cin,a);
- cout << a << endl;
- return 0;
- }
复制代码
4、 string字符串类(掌握)
string不是C++的基本数据类型,它是一个C++标准库中的字符串类,使用时需要引入头文件#include 。而不是string.h
string在绝大多数情况下可以代替C语言的字符串,不必担心内存是否足够和字符串长度等,其中内部还包含了很多的字符串处理函数,可以完成各种情况下的字符串的处理功能。
string和C语言相同,字符串编码使用的是ASCII码。不支持中文处理。
- #include <iostream>
- using namespace std;
- int main()
- {
- string str = "helloworld";
- cout << str.size() << endl; // 10
- cout << str.length() << endl; // 10
- cout << str[5] << endl; // w
- cout << str.at(5) << endl; // w
- return 0;
- }
复制代码
两种方式都可以,但是在C++中更推荐使用at函数,原因是at函数更安全,[ ]的执行效率高。
- #include <iostream>
- using namespace std;
- int main()
- {
- string str = "helloworld";
- cout << str[100] << endl; // 会输出一个越界数据
- cout << str.at(100) << endl; // 程序运行停止
- cout << "hello" << endl;
- return 0;
- }
复制代码
string类支持多种遍历方式:
● 普通循环(以for循环为主)
● C++11: for each循环
- #include <iostream>
- using namespace std;
- int main()
- {
- string str = "helloworld";
- // 以for循环的方式进行输出字符串
- for(int i = 0; i < str.size(); i++)
- {
- cout << str.at(i);
- }
- cout << endl;
- // 以for each的方式循环遍历字符串
- for(char i : str)
- {
- cout << i;
- }
- return 0;
- }
复制代码
了解:字符串与数字的转换
- #include <iostream>
- #include <sstream> // 字符串流
- using namespace std;
- int main()
- {
- string s = "123";
- // int i = s; 错误
- // string → int
- istringstream iss(s);
- int i;
- iss >> i;
- cout << i << endl;
- // int → string
- // string s2 = i; 错误
- stringstream ss;
- ss << i;
- string s2 = ss.str();
- cout << s2 << endl;
- return 0;
- }
复制代码
5、 函数
5.1 内联函数(熟悉)
内联函数用于取代C语言中宏定义的函数,内联函数的正确使用可以提升程序的执行效率。
内联函数在编译的时候,直接把函数体展开到主函数中编译,在运行期间可以减少调用的开销,以空间换取时间。
通常将具有以下性质的函数写为内联函数
● 代码长度5行以内
● 不包含复杂的控制语句
● 频繁的被调用
内联函数的关键字inline
后续学习的成员函数默认添加inline关键字修饰。
但是我们手动添加上inline关键字,目的是将函数声明成内联函数,但是这个不是我们能决定的,编译器有自己的判断准则,我们只是给编译器提一个建议,具体是否变为内联函数,还是编译器自己决定的。
- include <iostream>
- using namespace std;
- // 内联函数
- inline void print_string(string str)
- {
- cout << str << endl;
- }
- int main()
- {
- print_string("helloworld");
- return 0;
- }
复制代码
5.2 函数重载(重点)
C++中允许多个函数使用同一个名称,这种用法就是函数重载,函数重载要求函数名称相同,但是参数不同(类型不同、数量不同)或者前后顺序不同,与返回值类型无关。
- #include <iostream>
- using namespace std;
- void print_show(int i)
- {
- cout << "调用了int重载:" << i << endl;
- }
- void print_show(float i)
- {
- cout << "调用了float重载:" << i << endl;
- }
- void print_show(string i)
- {
- cout << "调用了string重载:" << i << endl;
- }
- // 错误 名称相同,参数类型相同,编译器无法区分
- // 与返回值类型无关
- //int print_show(int s)
- //{
- // cout << "调用了int2重载:" << s << endl;
- // return s;
- //}
- // 错误 const关键字,也无法作为判断依据
- //void print_show(const string s)
- //{
- // cout << "调用了string重载:" << s << endl;
- //}
- int main()
- {
- print_show(1.2);
- return 0;
- }
复制代码
5.3 哑元函数(熟悉)
函数的参数只有类型,没有名称,有这样参数的函数就是哑元函数。
作用1:哑元函数可以用来区分函数重载
作用2:运算符重载中会用到
- #include <iostream>
- using namespace std;
- // 哑元函数
- void print_show(int)
- {
- cout << "调用了int重载" << endl;
- }
- int main()
- {
- print_show(1);
- return 0;
- }
复制代码
三、面向对象基础
1、类与对象(重点)
1.1 概念
类:类是一个抽象的概念,用于描述同一类对象的特点。
对象:根据类的概念所创造的实体。
【思考】:一个对象可以没有对应的类嘛?
不可以的。
对于现在的学习,如果只写一个类,没有对应的对象,这样的类是没有意义的。
1.2 类的内容
类中最基础的内容包括两个部分:一个是属性,一个是行为。
● 属性:表示一些特征项的数值,比如说:身高、体重、年龄、性别、型号、而这些特正项的数值也被称为”成员变量“。属性一般以名词存在。
● 行为:表示能执行的动作,能干什么事?比如说:吃饭、睡觉、打架、叫、唱、跳、rap、打篮球。行为一般通过函数来表示,也被称为”成员函数“。行为一般以动词存在。
成员 = 成员函数 + 成员变量。
【例子】以手机为例,来说明类的定义。
规定手机能够播放音乐、运行游戏、打电话。手机有:品牌、型号、重量等等。
- #include <iostream>
- using namespace std;
- // 大驼峰命名法(帕斯卡命名法)
- // 每个单词的首字母要大写
- class MobilePhone
- {
- public: // 权限:public最开放的权限
- string brand; // 品牌
- string model; // 型号
- int weight; // 重量
- void play_music()
- {
- cout << "来财" << endl;
- }
- void run_game()
- {
- cout << "无畏契约、第五人格、cs";
- }
- void call()
- {
- cout << "您拨打的电话正在通话中,请稍后在拨" << endl;
- }
- };
- int main()
- {
- return 0;
- }
复制代码
1.3 对象的创建
C++中存在两种类型的对象。
● 栈内存对象
对象所在的{}执行结束后,自动被销毁。
- #include <iostream>
- using namespace std;
- // 大驼峰命名法(帕斯卡命名法)
- // 每个单词的首字母要大写
- class MobilePhone
- {
- public: // 权限:public最开放的权限
- string brand; // 品牌
- string model; // 型号
- int weight; // 重量
- void play_music()
- {
- cout << "来财" << endl;
- }
- void run_game()
- {
- cout << "无畏契约、第五人格、cs";
- }
- void call()
- {
- cout << "您拨打的电话正在通话中,请稍后在拨" << endl;
- }
- };
- int main()
- {
- MobilePhone mp; // 创建一个栈内存对象
- mp.brand = "华为";
- mp.model = "遥遥领先";
- mp.weight = 500;
- cout << mp.brand << " " << mp.model << " " << mp.weight << endl;
- mp.play_music();
- mp.run_game();
- mp.call();
- return 0;
- }
复制代码
● 堆内存对象
必须使用new关键字创建,使用指针保存,如果不使用delete关键字销毁,则堆内存对象会持续存在,而导致内存泄漏。堆内存调用对象内容时,使用 ->而不是" . "
- #include <iostream>
- using namespace std;
- // 大驼峰命名法(帕斯卡命名法)
- // 每个单词的首字母要大写
- class MobilePhone
- {
- public: // 权限:public最开放的权限
- string brand; // 品牌
- string model; // 型号
- int weight; // 重量
- void play_music()
- {
- cout << "来财" << endl;
- }
- void run_game()
- {
- cout << "无畏契约、第五人格、cs";
- }
- void call()
- {
- cout << "您拨打的电话正在通话中,请稍后在拨" << endl;
- }
- };
- int main()
- {
- MobilePhone *mp = new MobilePhone; // 堆内存对象
- mp->brand = "小米";
- mp->model = "mi6";
- mp->weight = 200;
- cout << mp->brand << " " << mp->model << " " << mp->weight << endl;
- mp->play_music();
- mp->run_game();
- mp->call();
- delete mp; // 手动销毁
- mp = NULL; // 建议指向空,防止后面误用
- return 0;
- }
复制代码
【面试题】
malloc和free与new和delete 的区别?
malloc返回值为 void *类型,需要强制转换,而new创建什么类型对象,返回什么类型地址。
核心区别
特性 | malloc/free | new/delete |
---|
语言 | C/C++ | C++ 特有 |
内存分配方式 | 仅分配/释放原始内存 | 分配内存并调用构造/析构函数 |
类型安全 | 需手动类型转换 | 自动类型推导 |
内存大小计算 | 需手动指定字节数 | 自动计算类型大小 |
失败处理 | 返回 NULL | 抛出 std::bad_alloc 异常(默认) |
重载支持 | 不支持 | 支持运算符重载 |
数组处理 | 需手动计算空间 | 支持 new[]/delete[] 语法 |
与对象生命周期的结合 | 无关 | 管理对象构造与析构 |
【练习】1、键盘输入一个100-999之间的数,依次输出这个数的个十百位。
参考代码:
- int main()
- {
- //1、键盘输入一个100-999之间的数,依次输出这个数的个十百位。
- int a;
- cout << "请输入一个100-999之间的数:" << endl;
- cin >> a;
- cout << "个位:" << a%10 << endl;
- cout << "十位:" << a/10%10 << endl;
- cout << "百位:" << a/100 << endl;
- return 0;
- }
复制代码
【练习】2、输入一行字符串,分别统计出其中的英文字母,数字和其他字符的个数。
参考代码:
- int main()
- {
- //2、输入一行字符串,分别统计出其中的英文字母,数字和其他字符的个数。
- string str;
- cout << "请输入一行字符串:" << endl;
- cin >> str;
- int num=0, letter=0, other=0;
- for(int i=0;i<str.size();i++)
- {
- if(str.at(i)>='0' && str.at(i)<='9')
- num++;
- else if((str.at(i)>='a' && str.at(i)<='z' )|| (str.at(i)>='A' && str.at(i)<= 'Z'))
- letter++;
- else
- other++;
- }
- cout << "英文字母个数为:" << letter << endl;
- cout << "数字个数为:" << num << endl;
- cout << "其他字符个数为:" << other << endl;
- return 0;
- }
复制代码
2、封装(重点)
MobilePhone类与结构体差别不大,实际上可以认为结构体就是一种完全开放的类。
封装指的是,将类的一些属性和细节隐藏(private),重新提供外部访问接口,封装的优势可以提升代码的安全性,并且可以让程序员更关注上层的结构,而非内部细节。
- #include <iostream>
- using namespace std;
- // 大驼峰命名法(帕斯卡命名法)
- // 每个单词的首字母要大写
- class MobilePhone
- {
- private: // 私有权限,private 是最封闭的权限,只能在内类访问
- string brand; // 品牌
- string model; // 型号
- int weight = 200; // 重量
- public: // 权限:public最开放的权限
- // get 函数
- string get_brand()
- {
- return brand;
- }
- // set 函数
- void set_brand(string b)
- {
- brand = b;
- }
- // get 函数
- string get_model()
- {
- return model;
- }
- // set 函数
- void set_model(string m)
- {
- model = m;
- }
- // get 函数
- int get_weight()
- {
- return weight;
- }
- };
- int main()
- {
- MobilePhone *mp = new MobilePhone; // 堆内存对象
- mp->set_brand("小辣椒");
- mp->set_model("超辣");
- cout << mp->get_brand() << " " << mp->get_model() << " " << mp->get_weight() << endl;
- delete mp; // 手动销毁
- mp = NULL; // 建议指向空,防止后面误用
- return 0;
- }
复制代码
3、构造函数(掌握)
3.1 基本使用
构造函数是一种特殊的成员函数,用于创建对象时初始化,创建对象时必须直接或者间接调用当前类任意一个构造函数。
写法上有以下要求:
● 函数名称必须与类名完全相同
● 构造函数不写返回值
● 如果程序员不手写构造函数,编译器会自动添加一个无参的构造函数
手动添加任意一个构造函数,编译器就不会自动添加默认无参构造函数了。
● 构造函数在创建对象时,常用于给对象的属性赋予初始值。
- #include <iostream>
- using namespace std;
- // 大驼峰命名法(帕斯卡命名法)
- // 每个单词的首字母要大写
- class MobilePhone
- {
- private: // 私有权限,private 是最封闭的权限,只能在内类访问
- string brand; // 品牌
- string model; // 型号
- int weight; // 重量
- public: // 权限:public最开放的权限
- // 编译器自动添加的构造函数
- MobilePhone()
- {
- brand = "8848";
- model = "8848钛金手机-鳄鱼皮-巅峰版";
- weight = 1000;
- }
- // get 函数
- string get_brand()
- {
- return brand;
- }
- // get 函数
- string get_model()
- {
- return model;
- }
- // get 函数
- int get_weight()
- {
- return weight;
- }
- };
- int main()
- {
- MobilePhone *mp = new MobilePhone; // 堆内存对象
- cout << mp->get_brand() << " " << mp->get_model() << " " << mp->get_weight() << endl;
- delete mp; // 手动销毁
- mp = NULL; // 建议指向空,防止后面误用
- return 0;
- }
复制代码
● 构造函数也支持函数重载
- #include <iostream>
- using namespace std;
- // 大驼峰命名法(帕斯卡命名法)
- // 每个单词的首字母要大写
- class MobilePhone
- {
- private: // 私有权限,private 是最封闭的权限,只能在内类访问
- string brand; // 品牌
- string model; // 型号
- int weight; // 重量
- public: // 权限:public最开放的权限
- // 无参构造函数
- MobilePhone()
- {
- brand = "8848";
- model = "8848钛金手机-鳄鱼皮-巅峰版";
- weight = 1000;
- }
- // 有参构造函数
- MobilePhone(string b, string m, int w)
- {
- brand = b;
- model = m;
- weight = w;
- }
- // get 函数
- string get_brand()
- {
- return brand;
- }
- // get 函数
- string get_model()
- {
- return model;
- }
- // get 函数
- int get_weight()
- {
- return weight;
- }
- };
- int main()
- {
- MobilePhone *mp = new MobilePhone("小米","18 pro",200); // 堆内存对象
- cout << mp->get_brand() << " " << mp->get_model() << " " << mp->get_weight() << endl;
- delete mp; // 手动销毁
- mp = NULL; // 建议指向空,防止后面误用
- return 0;
- }
复制代码
● 构造函数也支持函数参数默认值
- #include <iostream>
- using namespace std;
- // 大驼峰命名法(帕斯卡命名法)
- // 每个单词的首字母要大写
- class MobilePhone
- {
- private: // 私有权限,private 是最封闭的权限,只能在内类访问
- string brand; // 品牌
- string model; // 型号
- int weight; // 重量
- public: // 权限:public最开放的权限
- // 无参构造函数
- // 下面两种写法的构造函数,都可以给对象做初始化,但是不能同时存在,因为会触发二义性的问题。
- // MobilePhone()
- // {
- // brand = "8848";
- // model = "8848钛金手机-鳄鱼皮-巅峰版";
- // weight = 1000;
- // }
- // 有参构造函数(全缺省构造函数)
- MobilePhone(string b = "小米", string m = "15pro", int w = 200)
- {
- brand = b;
- model = m;
- weight = w;
- }
- // get 函数
- string get_brand()
- {
- return brand;
- }
- // get 函数
- string get_model()
- {
- return model;
- }
- // get 函数
- int get_weight()
- {
- return weight;
- }
- };
- int main()
- {
- MobilePhone *mp = new MobilePhone; // 堆内存对象
- cout << mp->get_brand() << " " << mp->get_model() << " " << mp->get_weight() << endl;
- delete mp; // 手动销毁
- mp = NULL; // 建议指向空,防止后面误用
- return 0;
- }
复制代码
3.2 构造初始化列表
构造初始化列表是一种更简单的给成员变量赋予初始值的写法。
- #include <iostream>
- using namespace std;
- // 大驼峰命名法(帕斯卡命名法)
- // 每个单词的首字母要大写
- class MobilePhone
- {
- private: // 私有权限,private 是最封闭的权限,只能在内类访问
- string brand; // 品牌
- string model; // 型号
- int weight; // 重量
- public: // 权限:public最开放的权限
- // 无参构造函数
- MobilePhone()
- :brand("8848"),model("8848钛金手机-鳄鱼皮-巅峰版"),weight(200){}
- // 有参构造函数
- MobilePhone(string b, string m, int w)
- :brand(b),model(m),weight(w){}
- // get 函数
- string get_brand()
- {
- return brand;
- }
- // get 函数
- string get_model()
- {
- return model;
- }
- // get 函数
- int get_weight()
- {
- return weight;
- }
- };
- int main()
- {
- MobilePhone *mp = new MobilePhone("华为","18",200); // 堆内存对象
- cout << mp->get_brand() << " " << mp->get_model() << " " << mp->get_weight() << endl;
- delete mp; // 手动销毁
- mp = NULL; // 建议指向空,防止后面误用
- return 0;
- }
复制代码
当构造函数的局部变量与成员变量重名时,除了使用后面学习的this指针的方式外,还可以使用构造初始化列表区分。
3.3 隐式调用和显式调用
构造函数的调用可以分为显式调用和隐式调用。
显式调用指的是创建对象的时候手写构造函数的名称。
隐式调用指的是在创建对象的时候不写参数列表,或者构造函数的名称也不写。编译器会尝试调用对应参数的构造函数。
- #include <iostream>
- using namespace std;
- class Student
- {
- private:
- int age;
- public:
- Student(int a):age(a)
- {
- cout << "构造函数被调用啦" << endl;
- }
- Student()
- {
- cout << "无参构造函数被调用啦" << endl;
- }
- int get_age()
- {
- return age;
- }
- };
- int main()
- {
- Student s1(12); // 栈内存对象,显式调用
- Student s3 = Student(14); // 显式调用
- Student s4 = 15; // 隐式调用
- Student *s2 = new Student(13); // 显式调用
- Student s5; // 默认调用方式,显式调用
- return 0;
- }
复制代码
建议使用显式调用,可以使用explicit关键字屏蔽隐式调用的语法。
3.4 拷贝构造函数
3.4.1 概念
当程序员不手写拷贝构造函数时,编译器会自动添加一个拷贝构造函数,使对象创建可以通过这个构造函数实现。
- #include <iostream>
- using namespace std;
- class Student
- {
- private:
- int age;
- public:
- Student(int a):age(a)
- {
- cout << "构造函数被调用啦" << endl;
- }
- Student()
- {
- cout << "无参构造函数被调用啦" << endl;
- }
- // 手动添加默认的拷贝构造函数
- Student(Student &mp)
- {
- age = mp.age;
- }
- int get_age()
- {
- return age;
- }
- };
- int main()
- {
- Student s1(12); // 调用有参构造函数
- cout << s1.get_age() << endl; // 12
- Student s2(s1); // 拷贝构造函数
- cout << s2.get_age() << endl; // 12
- return 0;
- }
复制代码
【思考】默认的拷贝构造函数是否存在隐患?
存在,当成员变量出现指针类型时,默认的拷贝构造函数会导致两个对象的指针成员变量指向同一处。这种现象被称为”浅拷贝“。
3.4.2 浅拷贝
- #include <iostream>
- #include <string.h>
- using namespace std;
- class Dog
- {
- private:
- char *name;
- public:
- Dog(char *n)
- {
- name = n;
- }
- void show_name()
- {
- cout << name << endl;
- }
- };
- int main()
- {
- char arr[20] = "旺财";
- Dog d1(arr);
- Dog d2(d1); // 拷贝构造函数
- strcpy(arr,"大黄"); // 浅拷贝,更改外部内存,对象内部的数据也被更改了,因为是操作的同一块空间。
- d1.show_name(); // 大黄
- d2.show_name(); // 大黄
- return 0;
- }
复制代码
这种情况必须手动重写构造函数,使每次赋值都创建一个新的副本,从而每个对象单独持有自己的成员变量。这种方式也被称为”深拷贝“。
3.4.3 深拷贝
- #include <iostream>
- #include <string.h>
- using namespace std;
- class Dog
- {
- private:
- char *name;
- public:
- Dog(char *n)
- {
- name = new char[20];
- strcpy(name,n);
- }
- Dog(Dog &d)
- {
- name = new char[20];
- strcpy(name,d.name);
- }
- void show_name()
- {
- cout << name << endl;
- }
- };
- int main()
- {
- char arr[20] = "旺财";
- Dog d1(arr);
- Dog d2(d1); // 拷贝构造函数
- strcpy(arr,"大黄"); // 浅拷贝,更改外部内存,对象内部的数据也被更改了,因为是操作的同一块空间。
- d1.show_name(); // 旺财
- d2.show_name(); // 旺财
- return 0;
- }
复制代码
【思考】当前深拷贝的代码是否存在隐患?
存在,new开辟的堆内存空间,没有释放,造成内存泄漏的问题。
4、 析构函数(析构函数)
析构函数是与构造函数完全对立的函数。
编译器会自动添加默认没有任何操作的析构函数。
构造函数 | 析构函数 |
创建对象时手动调用 | 当对象销毁时,自动调用 |
函数名称是类名 | 函数名称是~类名 |
构造函数可以重载 | 析构函数没有参数,不能重载 |
用于创建对象时做初始化的 | 用于销毁对象时释放资源 |
有返回值但是不写,返回值是新创建的对象 | 没有返回值 |
- #include <iostream>
- #include <string.h>
- using namespace std;
- class Dog
- {
- private:
- char *name;
- public:
- Dog(char *n)
- {
- name = new char[20];
- strcpy(name,n);
- }
- Dog(Dog &d)
- {
- name = new char[20];
- strcpy(name,d.name);
- }
- void show_name()
- {
- cout << name << endl;
- }
- ~Dog()
- {
- cout << "析构函数被调用了" << endl;
- delete []name;
- }
- };
- int main()
- {
- char arr[20] = "旺财";
- Dog d1(arr);
- Dog d2(d1); // 拷贝构造函数
- strcpy(arr,"大黄"); // 浅拷贝,更改外部内存,对象内部的数据也被更改了,因为是操作的同一块空间。
- d1.show_name(); // 旺财
- d2.show_name(); // 旺财
- return 0;
- }
复制代码
5、作用域限定符::
5.1 名字空间(熟悉)
namespace my_space //声明一个名字空间
using namespace my_space //使用名字空间
- #include <iostream>
- #include <string.h>
- using namespace std;
- int a = 2;
- namespace my_space
- {
- int a = 3;
- int b = 4;
- }
- using namespace my_space;
- int main()
- {
- int a = 1;
- cout << a << endl; // 就近 打印1
- cout << ::a << endl; // ::a 全局作用域 2
- cout << my_space::a << endl; // 3
- cout << b << endl;
- return 0;
- }
复制代码
5.2 类内声明,类外定义(掌握)
类外定义,加 “ 类名::”,告诉编译器是哪个类的成员函数,定义写在类外,简洁,代码实现性更高。
- #include <iostream>
- using namespace std;
- class Demo
- {
- public:
- // 类内声明
- Demo();
- void test(string str);
- };
- // 类外定义
- Demo::Demo()
- {
- cout << "创建了一个对象" << endl;
- }
- void Demo::test(string str)
- {
- cout << "string::" << str << endl;
- }
- int main()
- {
- Demo d; // 调用无参构造函数
- d.test("hello");
- return 0;
- }
复制代码
6、this指针(掌握)
6.1 概念
this指针是一个特殊的指针,指向当前类对象的首地址。
成员函数(包括构造函数与析构函数)中都有this指针,因此this指针只能在类内部使用。实际上this指向的就是当前运行的成员函数所绑定的对象。
- #include <iostream>
- using namespace std;
- class Test
- {
- public:
- void test_this()
- {
- cout << this << endl;
- }
- };
- int main()
- {
- Test t1;
- cout << &t1 << endl; // 0x61fe8f
- t1.test_this(); // 0x61fe8f
- Test *t2 = new Test;
- cout << t2 << endl; // 0x781108
- t2->test_this(); // 0x781108
- delete t2;
- return 0;
- }
复制代码
6.2 功能
6.2.1 类内调用成员
- #include <iostream>
- using namespace std;
- class Test
- {
- private:
- string name;
- public:
- Test(string n)
- {
- // 编译器默认添加this指针 指向的对象调用成员
- this->name = n;
- }
- string get_name()
- {
- // 编译器默认添加this指针 指向的对象调用成员
- return this->name;
- }
- };
- int main()
- {
- Test t1("zhangsan");
- cout << t1.get_name() << endl;
- return 0;
- }
复制代码
6.2.2 区分重名的成员变量和局部变量
- #include <iostream>
- using namespace std;
- class Test
- {
- private:
- string name;
- public:
- Test(string name):name(name) // 构造初始化列表可以区分
- {
- // 通过this在函数体中区分
- this->name = name;
- }
- string get_name()
- {
- return name;
- }
- };
- int main()
- {
- Test t1("zhangsan");
- cout << t1.get_name() << endl;
- return 0;
- }
复制代码
6.2.3 链式调用
支持链式调用的成员函数的特点:
1、 当一个成员函数的返回值是当前类型的引用时,往往表示这个函数支持链式调用。
2、 return后面是*this
- #include <iostream>
- using namespace std;
- class Test
- {
- private:
- int val = 0;
- public:
- Test& add(int i)
- {
- val += i; // val = val + i;
- return *this; // this 是一个指针,返回当前对象需要取内容
- }
- int get_val()
- {
- return val;
- }
- };
- int main()
- {
- Test t1;
- t1.add(1);
- t1.add(2);
- t1.add(100);
- cout << t1.get_val() << endl; // 103
- Test t2;
- // 链式调用
- cout << t2.add(2).add(21).add(200).get_val() << endl; // 223
- cout << t2.get_val() << endl; // 223
- return 0;
- }
复制代码
[练习]:猫吃鱼
写一个类Fish,有品种和重量两个属性,属性的类型自己选择,要求属性封装。
写一个类Cat,Cat中有一个公有的成员函数:
Fish& eat(Fish &f);
eat函数的功能要求判断Fish的品种:
●如果品种是“秋刀鱼”,则输出“无论多沉,我都爱吃。”。同时修改Fish &f的重量为0,并作为函数的返回值返回。
●如果品种不是“秋刀鱼”,则判断鱼的重量,若重量大于200,吃鱼输出信息并返回一个重量为0的Fish;若重量小于200,输出信息,不要修改鱼的重量,直接返回鱼的对象。
其它知识点(构造函数、构造初始化列表等)可以自行选择是否采用。
代码示例:
- #include <iostream>
- using namespace std;
- class Fish
- {
- private:
- string variety;
- int weight;
- public:
- //有参构造函数-构造初始化列表
- Fish(string v,int w)
- :variety(v),weight(w){}
- string get_variety()
- {
- return variety;
- }
- int get_weight()
- {
- return weight;
- }
- int set_weight(int w)
- {
- weight=w;
- }
- };
- class Cat
- {
- public:
- Cat& eat(Fish &f)
- {
- if(f.get_variety() == "秋刀鱼")
- {
- cout << "无论多沉,我都爱吃。" << endl;
- f.set_weight(0);
- cout << "剩余重量:" << f.get_weight() << endl;
- }
- else
- {
- if(f.get_weight()>200)
- {
- cout << "不是秋刀鱼我也爱吃!" << endl;
- f.set_weight(0);
- cout << "剩余重量:"<< f.get_weight() << endl;
- }
- else
- {
- cout << "我不饿,先不吃鱼了" << endl;
- cout <<"剩余重量:" << f.get_weight() << endl;
- }
- }
- return *this;
- }
- };
- int main()
- {
- while(1)
- {
- int a;
- string str;
- cout << "想吃什么鱼?想吃多重的?" << endl;
- cin >> str >> a;
- Fish f(str,a);
- Cat cat;
- cat.eat(f);
- }
- // Fish f1("秋刀鱼",200);
- // Fish f2("鲅鱼",800);
- // Fish f3("鳗鱼",150);
- //链式调用
- // cat.eat(f1).eat(f2).eat(f3);
- return 0;
- }
复制代码
7、static关键字(掌握)
7.1静态局部变量
使用static修饰局部变量,这样的变量就是静态局部变量。
静态局部变量在第一次调用时创建,直到程序结束后销毁,同一个类的所有对象共用这一份静态局部变量。
- #include <iostream>
- using namespace std;
- class Test
- {
- public:
- void func()
- {
- int a = 1;
- static int b = 1;
- cout << "a=" << ++a << endl;
- cout << "b=" << ++b << endl;
- }
- };
- int main()
- {
- Test t1;
- t1.func(); //a=2 b=2
- t1.func(); //a=2 b=3
- Test t2;
- t2.func(); //a=2 b=4
- return 0;
- }
复制代码
7.2 静态成员变量
使用static修饰成员变量,这样的变量就是静态成员变量。
静态成员变量需要在类内声明,类外定义。
一个类的所有对象共用一份静态成员变量,虽然静态成员变量可以使用对象调用,但是更建议直接使用类名::调用。静态成员变量可以脱离对象使用,在程序运行时就开辟内存空间,直到程序运行结束后销毁。
- #include <iostream>
- using namespace std;
- class Test
- {
- public:
- int a = 1;
- // static int b = 2; // 错误,静态成员变量需要类内声明。类外初始化
- static int b;
- };
- int Test::b = 1;
- int main()
- {
- cout << Test::b << " " << &Test::b << endl;
- Test t1;
- cout << ++t1.a << " " << &t1.a << endl; // 2
- cout << ++t1.b << " " << &t1.b << endl; // 2
- cout << "------------------" << endl;
- Test t2;
- cout << ++t2.a << " " << &t2.a << endl; // 2
- cout << ++t2.b << " " << &t2.b << endl; // 3
- cout << Test::b << " " << &Test::b << endl;
- return 0;
- }
复制代码
7.3 静态成员函数
使用static修饰的成员函数,这样的函数就是静态成员函数。
可以通过类名直接调用,也可以通过对象调用(推荐使用类名直接调用);
静态成员函数没有this指针,不能在静态成员函数中调用同类的非静态成员,但是静态成员函数,可以调用静态成员。
- #include <iostream>
- using namespace std;
- class Test
- {
- public:
- int a = 10;
- void func0()
- {
- cout << a << endl;
- // func1(); // 非静态成员函数可以调用静态成员函数
- cout << "非静态成员函数" << endl;
- }
- static void func1()
- {
- // func0(); // 错误 静态成员函数,无法直接调用非静态成员函数,因为没有this指针
- cout << "静态成员函数1" << endl;
- }
- static void func2()
- {
- func1();
- cout << "静态成员函数2" << endl;
- }
- };
- int main()
- {
- // Test::func1();
- Test t1;
- // t1.func0();
- // t1.func1();
- t1.func2();
- return 0;
- }
复制代码
【思考】如果要在静态成员函数中调用当前类的非静态成员,要如何实现?
1.通过传参
2.直接创建对象
- #include <iostream>
- using namespace std;
- class Test
- {
- public:
- int a = 10;
- void func0()
- {
- cout << a << endl;
- cout << "非静态成员函数" << endl;
- }
- static void func1(Test &t1) //传参
- {
- // Test t1; // 直接创建对象
-
- cout << "静态成员函数1" << endl;
- }
- static void func2()
- {
- cout << "静态成员函数2" << endl;
- }
- };
- int main()
- {
- Test t1;
- t1.func1(t1);
- return 0;
- }
复制代码
7.4 单例设计模式(了解)
设计模式是一套被反复使用,多人知晓的,经过分类的,代码设计经验的总结。通常用到一些面向对象的语言中,如:C++、JAVA、C#等等
- #include <iostream>
- using namespace std;
- /**
- * @brief The Singleton class
- * 单例模式:只能创建一个对象(可以多次)
- */
- class Singleton
- {
- private:
- Singleton(){}
- Singleton(const Singleton&);
- static Singleton* instance; // 静态成员变量
- public:
- static Singleton* get_instance() // 静态成员函数
- {
- if(instance == NULL)
- instance = new Singleton;
- return instance;
- }
- static void delete_instance()
- {
- if(instance != NULL)
- {
- delete instance;
- instance = NULL;
- }
- }
- };
- Singleton* Singleton::instance = NULL;
- int main()
- {
- Singleton* s1 = Singleton::get_instance();
- Singleton* s2 = Singleton::get_instance();
- cout << s1 << endl;
- cout << s2 << endl;
- return 0;
- }
复制代码
8、 const关键字(掌握)
8.1 const修饰成员函数
const修饰的成员函数,表示常成员函数。
特性如下:
● 可以调用成员变量,但是不能修改成员变量的值。
● 不能调用非const的成员函数,哪怕这个函数并没有修改成员变量。
- #include <iostream>
- using namespace std;
- class Demo
- {
- private:
- int a;
- public:
- Demo(int a):a(a) {}
- void func0()
- {
- cout << "哈哈哈哈哈" << endl;
- }
- // 常成员函数
- int get_a() const
- {
- return a;
- }
- void test() const
- {
- // 错误 const修饰的成员函数,不能修改成员变量
- // a++;
- cout << a << endl;
- // func0(); 错误 const修饰的成员函数,不能调用非const修饰的成员函数
- get_a();
- }
- };
- int main()
- {
- Demo demo(1);
- cout << demo.get_a() << endl; // 1
- demo.func0();
- demo.test();
- return 0;
- }
复制代码
8.2 const修饰对象
const修饰的对象被称为常量对象,这种对象的成员变量值无法被修改,也无法调用非const的成员函数。
- #include <iostream>
- using namespace std;
- class Demo
- {
- private:
- int a;
- public:
- int b = 10;
- Demo(int a):a(a)
- {
- }
- void func0()
- {
- cout << "哈哈哈哈哈" << endl;
- }
- // 常成员函数
- int get_a() const
- {
- return a;
- }
- void test() const
- {
- // 错误 const修饰的成员函数,不能修改成员变量
- // a++;
- cout << a << endl;
- // func0(); 错误 const修饰的成员函数,不能调用非const修饰的成员函数
- get_a();
- }
- };
- int main()
- {
- // 常对象
- const Demo demo(1);
- // Demo const demo(1); // 两种初始化的写法,等效于上一行
- cout << demo.get_a() << endl; // 1
- // demo.func0(); // 错误,const修饰的对象,无法调用非const修饰的成员函数
- demo.test();
- // demo.b = 10; // 错误 const修饰的对象,无法修改成员变量
- cout << demo.b << endl; // 可以调用但是无法修改
- return 0;
- }
复制代码
8.3 const修饰成员变量
const修饰的成员变量为常成员变量,表示该成员变量的值无法被修改。
常成员变量有两种初始化的方式:
● 直接赋值
● 构造初始化列表
上述两种初始化同时使用时,以构造构造初始化列表为准。
- #include <iostream>
- using namespace std;
- class Demo
- {
- private:
- const int a = 1;
- const int b = 2;
- const int c = 3;
- public:
- Demo(int a,int b,int c):a(a),b(b),c(c)
- {
- }
- void show()
- {
- cout << a << " " << b << " " << c << endl;
- }
- void test()
- {
- // a++;
- // b++;
- // c++;
- // a = 10;
- // b = 20;
- // c = 30;
- }
- };
- int main()
- {
- Demo d1(10,20,30);
- d1.show();
- return 0;
- }
复制代码
8.4 const修饰局部变量
const修饰局部变量,表示该局部变量不可被修改。
这种方式常用于引用参数。
- #include <iostream>
- using namespace std;
- class Demo
- {
- private:
- const int a = 1;
- const int b = 2;
- const int c = 3;
- public:
- Demo(int a,int b,int c):a(a),b(b),c(c)
- {
- }
- void show()
- {
- cout << a << " " << b << " " << c << endl;
- }
- void test(const int &f)
- {
- // const修饰局部变量
- const int e = 1;
- // e = 40; // 错误 const修饰局部变量不能被修改
- cout << e << endl;
- }
- };
- int main()
- {
- int a = 20;
- Demo d1(10,20,30);
- d1.show();
- d1.test(a);
- return 0;
- }
复制代码
四、运算符重载
1、 友元(熟悉)
1.1 概念
类主要实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,仅能够通过类的成员函数才能读写。如果数据成员定义成公共的,则又破坏了封装性,但是在某些情况下,需要频发的读写类数据成员,特别是对某些成员函数多次调用时,由于参数传递,类型检查、和安全性检查等等都需要时间开销,而影响程序的运行效率。
友元有三种实现方式:
● 友元函数
● 友元类
● 友元成员函数
友元函数是一种定义在类外部的普通函数,但他还需要再类体内进行声明,为了和该类的成员函数加以区分,再声明前面加一个关键字friend。友元函数不是成员函数,但是它能够访问类中的所有成员,包括私有成员。
友元在于提高程序的运行效率,但是它破坏了类的封装性和隐藏性,使得非成员函数也能够访问类的私有成员,导致程序维护性变差,因此使用友元要慎重。
1.2 友元函数
友元函数不属于任何一个类,是一个类外的函数,但是再类内需要声明。虽然友元函数不是成员函数,但是却可以访问类中的所有成员(包括私有成员)。
- #include <iostream>
- using namespace std;
- class Test
- {
- private:
- int a;
- public:
- Test(int i):a(i){}
- void show()
- {
- cout << a << " " << &a << endl;
- }
- // 友元函数,类内声明
- friend void and_test(Test &t);
- };
- // 友元函数
- void and_test(Test &t)
- {
- cout << t.a << " " << &t.a << endl;
- }
- int main()
- {
- Test t1(1);
- and_test(t1); // 1 0x61fe8c
- t1.show(); // 1 0x61fe8c
- return 0;
- }
复制代码
友元函数的使用需要注意以下几点:
● 友元函数没有this指针
● 友元函数的声明,可以放在类中的任何位置,不受权限修饰符的影响。
● 一个友元函数可以访问多个类,只需要再各个类中分别“声明”,然后把对象传递进来。
1.3 友元类
当一个类B成为了另一个类Test的朋友时,类Test的所有成员都可以被类B访问,此时类B就是类Test的友元类。
- #include <iostream>
- using namespace std;
- class Test
- {
- private:
- int a;
- public:
- Test(int i):a(i){}
- void show()
- {
- cout << a << " " << &a << endl;
- }
- // 友元类,类内声明
- friend class B;
- };
- class B
- {
- public:
- void and_test(Test &t)
- {
- cout << t.a << " " << &t.a << endl;
- }
- void and_test1(Test &t)
- {
- cout << t.a << " " << &t.a << endl;
- }
- void and_test2(Test &t)
- {
- cout << t.a << " " << &t.a << endl;
- }
- };
- int main()
- {
- Test t1(2);
- B b;
- b.and_test(t1); // 2 0x61fe8c
- b.and_test1(t1);
- b.and_test2(t1);
- t1.show(); // 2 0x61fe8c
- return 0;
- }
复制代码
友元类的使用需要注意以下几点:
● 友元关系不能被继承
● 友元关系不具有交换性(比如:类B声明为类Test的友元,类B可以访问类Test的成员,但是类Test不能访问类B的私有成员,需要需要访问,需要将类Test声明成类B的友元)
互为友元代码,需要类内声明,类外实现。
- #include <iostream>
- using namespace std;
- class Cat;
- class Test
- {
- private:
- int a;
- public:
- Test(int i):a(i){}
- void test(Cat &c);
- friend class Cat;
- };
- class Cat
- {
- private:
- int b;
- public:
- Cat(int i):b(i){}
- void test1(Test &t);
- friend class Test;
- };
- void Test::test(Cat &c)
- {
- cout <<c.b<<endl;
- }
- void Cat::test1(Test &t)
- {
- cout <<t.a++<<endl;
- }
- int main()
- {
- Test t(44);
- Cat c(12);
- c.test1(t);
- return 0;
- }
复制代码
1.4 友元成员函数
使类B中的成员函数成为类Test的友元成员函数,这样类B的该成员函数就可以访问类Test的所有成员了。
- #include <iostream>
- using namespace std;
- // 第四步:声明被访问的类
- class Test;
- class B
- {
- public:
- // 第二步:声明友元成员函数(需要:类内声明。类外定义)
- void and_test(Test &t);
- };
- class Test
- {
- private:
- int a;
- public:
- Test(int i):a(i){}
- void show()
- {
- cout << a << " " << &a << endl;
- }
- // 友元成员函数,类内声明(第一步)
- friend void B::and_test(Test &t);
- };
- // 第三步:类外定义友元成员函数
- void B::and_test(Test &t)
- {
- cout << t.a << " " << &t.a << endl;
- }
- int main()
- {
- Test t1(2);
- B b;
- b.and_test(t1); // 2 0x61fe8c
- t1.show(); // 2 0x61fe8c
- return 0;
- }
复制代码
2、 运算符重载(掌握)
2.1 概念
C++中可以把部分运算符看做成函数,此时运算符也可以重载。
运算符预定义的操作只能针对基本数据类型,但是对于自定义类型,也需要类似的运算操作时,此时就可以重新定义这些运算符的功能,使其支持特定类型,完成特定的操作。
运算符重载有两种实现的方式:
● 友元函数运算符重载
● 成员函数运算符重载
2.2 友元函数运算符重载
- #include <iostream>
- using namespace std;
- class MyInt
- {
- private:
- int a;
- public:
- MyInt(int a):a(a){}
- int get_int()
- {
- return a;
- }
- // + 运算符重载
- friend MyInt operator +(MyInt &i,MyInt &i2);
- };
- MyInt operator +(MyInt &i,MyInt &i2)
- {
- // MyInt int4(0);
- // int4.a = i.a + i2.a;
- // return int4;
- // int → MyInt 触发隐式调用构造函数
- // MyInt n1 = 4;
- return i.a + i2.a;
- }
- int main()
- {
- MyInt int1(2);
- MyInt int2(int1); // 拷贝构造函数
- MyInt int3 = int1 + int2;
- cout << int3.get_int() << endl; // 4
- return 0;
- }
复制代码
++ 运算符重载:
- #include <iostream>
- using namespace std;
- class MyInt
- {
- private:
- int a;
- public:
- MyInt(int a):a(a){}
- int get_int()
- {
- return a;
- }
- // + 运算符重载
- friend MyInt operator +(MyInt &i,MyInt &i2);
- friend MyInt operator ++(MyInt &i); // 前置自增
- friend MyInt operator ++(MyInt &i,int); // 后置自增
- };
- MyInt operator +(MyInt &i,MyInt &i2)
- {
- return i.a + i2.a;
- }
- MyInt operator ++(MyInt &i)
- {
- return ++i.a;
- }
- MyInt operator ++(MyInt &i,int)
- {
- return i.a++;
- }
- int main()
- {
- MyInt int1(2);
- MyInt int2(int1); // 拷贝构造函数
- MyInt int3 = int1 + int2;
- cout << int3.get_int() << endl; // 4
- cout << (++int1).get_int() << endl; // 3
- cout << (int1++).get_int() << endl; // 3
- cout << int1.get_int() << endl; // 4
- return 0;
- }
复制代码
2.3 成员函数运算符重载
成员函数运算符重载相比于友元函数运算符重载,最主要的区别在于,由于函数的第一个传入参数,在成员函数运算符重载中使用this指针代替,因此同样的运算符重载,成员函数比友元函数重载参数少一个。
- #include <iostream>
- using namespace std;
- class MyInt
- {
- private:
- int a;
- public:
- MyInt(int a):a(a){}
- int get_int()
- {
- return a;
- }
- // + 运算符重载
- MyInt operator +(MyInt &i2);
- MyInt operator ++(); // 前置自增
- MyInt operator ++(int); // 后置自增
- };
- MyInt MyInt::operator +(MyInt &i2)
- {
- return this->a + i2.a;
- }
- MyInt MyInt::operator ++()
- {
- return ++this->a;
- }
- MyInt MyInt::operator ++(int)
- {
- return this->a++;
- }
- int main()
- {
- MyInt int1(2);
- MyInt int2(int1); // 拷贝构造函数
- MyInt int3 = int1 + int2;
- cout << int3.get_int() << endl; // 4
- cout << (++int1).get_int() << endl; // 3
- cout << (int1++).get_int() << endl; // 3
- cout << int1.get_int() << endl; // 4
- return 0;
- }
复制代码
2.4 特殊运算符重载
2.4.1 赋值运算符重载
除了之前学习的无参构造函数,拷贝构造函数与析构函数以外,如果程序员不收手写,编译器会给一个类添加赋值运算符重载函数。
- #include <iostream>
- using namespace std;
- class MyInt
- {
- private:
- int a;
- public:
- MyInt(int a):a(a){}
- int get_int()
- {
- return a;
- }
- // 编译器会自动添加赋值运算符重载函数
- MyInt &operator =(MyInt &i)
- {
- cout << "运算符重载函数被调用了" << endl;
- this->a = i.a;
- return *this;
- }
- // + 运算符重载
- MyInt operator +(MyInt &i2);
- MyInt operator ++(); // 前置自增
- MyInt operator ++(int); // 后置自增
- };
- MyInt MyInt::operator +(MyInt &i2)
- {
- return this->a + i2.a;
- }
- MyInt MyInt::operator ++()
- {
- return ++this->a;
- }
- MyInt MyInt::operator ++(int)
- {
- return this->a++;
- }
- int main()
- {
- MyInt int1(2);
- MyInt int2(int1); // 拷贝构造函数
- MyInt int3 = int1 + int2;
- int3 = int1;
- cout << int3.get_int() << endl;
- return 0;
- }
复制代码
当类中出现指针类型的成员变量时,默认的赋值运算符重载函数类似于默认的浅拷贝构造函数,因此也需要手动编写解决”浅拷贝“的问题。
【面试题】一个类什么也不写,编译器添加了那些函数?
无参构造函数、拷贝构造函数、析构函数、赋值运算符重载函数
【面试题】一个类权限也不写,默认是什么权限?
默认为私有权限。
【面试题】一个空类的大小是多少大?
1个字节。
2.4.2 类型转换运算符重载
必须使用成员函数运算符重载,且格式比较特殊。
- #include <iostream>
- using namespace std;
- class MyInt
- {
- private:
- int a;
- public:
- MyInt(int a):a(a){}
- int get_int()
- {
- return a;
- }
- // 编译器会自动添加赋值运算符重载函数
- MyInt &operator =(MyInt &i)
- {
- cout << "运算符重载函数被调用了" << endl;
- this->a = i.a;
- return *this;
- }
- // + 运算符重载
- MyInt operator +(MyInt &i2);
- MyInt operator ++(); // 前置自增
- MyInt operator ++(int); // 后置自增
- // 类型转换运算符重载
- operator int()
- {
- return a;
- }
- operator string()
- {
- return "hello";
- }
- };
- MyInt MyInt::operator +(MyInt &i2)
- {
- return this->a + i2.a;
- }
- MyInt MyInt::operator ++()
- {
- return ++this->a;
- }
- MyInt MyInt::operator ++(int)
- {
- return this->a++;
- }
- int main()
- {
- MyInt int1(2);
- MyInt int2(int1); // 拷贝构造函数
- MyInt int3 = int1; // 拷贝
- int2 = int3; // 赋值运算符重载
- int a1 = int2;
- cout << a1 << endl; // 2
- string s2 = int3;
- cout << s2 << endl; // hello
- return 0;
- }
复制代码
2.5 注意事项
● 重载的运算符限制在C++语言中已有的运算符范围,即不能创建新的运算符。
● 运算符重载的本质上也是函数重载,但是不支持函数参数默认值的设定。
● 重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符的操作数和语法结构。
● 运算符重载必须基于或者包含自定义类型,不能改变基本数据类的运算规则。
● 重载的功能应该与原有的功能相似,避免没有目的的滥用运算符重载
● 一般情况下,双目运算符建议使用友元函数重载,单目运算符建议使用成员函数运算符重载。
3、 std::string 字符串类(熟悉)
字符串对象是一种特殊类型的容器,专门设计用于操作字符串。
- #include <iostream>
- #include <string.h>
- using namespace std;
- int main()
- {
- string s; // 创建一个空的字符串
- // 判断是否为空
- cout << s.empty() << endl; // 1
- // 隐式调用构造函数
- string s1 = "hello";
- cout << s1 << endl; // hello
- // 构造函数显式调用
- string s2("world");
- cout << s2 << endl; // world
- // ==、!=、>、< 判断编码
- cout << (s1 == s2) << endl; // 0
- cout << (s1 != s2) << endl; // 1
- cout << (s1 > s2) << endl; // 0
- cout << (s1 < s2) << endl; // 1
- // 拷贝构造函数
- string s3(s2);
- cout << s3 << endl; // world
- // 参数1:char* 源字符串
- // 参数2:保留的字符数
- string s4("ABCDEFG",3);
- cout << s4 << endl; // ABC
- // 参数1:std::string 源字符串
- // 参数2:不保留的字符数,从头开始
- string s5(s2,3);
- cout << s5 << endl; // ld
- // 参数1:字符数量
- // 参数2:字符内容
- string s6(5,'a');
- cout << s6 << endl; // aaaaa
- // 交换
- cout << "s5 = " << s5 << " " << "s6 = " << s6 << endl;
- swap(s5,s6); // 交换后:aaaaa ld
- cout << "s5 = " << s5 << " " << "s6 = " << s6 << endl;
- // 字符串拼接
- string s7 = s5 + s6;
- cout << s7 << endl; // aaaaald
- // 向后追加字符串
- s7.append("jiajia");
- cout << s7 << endl; // aaaaaldjiajia
- // 向后追加单字符
- s7.push_back('s');
- cout << s7 << endl; // aaaaaldjiajias
- // 插入
- // 参数1:插入的位置
- // 参数2:插入的内容
- s7.insert(1,"234");
- cout << s7 << endl; // a234aaaaldjiajias
- // 删除字符串
- // 参数1:起始位置
- // 参数2:删除的字符数量
- s7.erase(2,5);
- cout << s7 << endl; // a2aldjiajias
- // 替换
- // 参数1:起始位置
- // 参数2:被替换的字符数
- // 参数3:替换的新内容
- s7.replace(0,3,"***");
- cout << s7 << endl; // ***ldjiajias
- // 清空
- s7.clear();
- cout << s7.length() << endl; // 0
- // 直接赋值初始化
- string s8 = "hahaha";
- cout << s8 << endl;
- // 重新赋值
- s8 = "ABCDEFGH";
- cout << s8 << endl; // ABCDEFGH
- // 参数1:拷贝的目标
- // 参数2:拷贝的字符数量
- // 参数3:拷贝的起始位置
- // C++的string 到 C语言的string也就是字符数组
- char arr[20] = {0};
- s8.copy(arr,6,1);
- cout << arr << endl; // BCDEFG
- // C++string 到C string 用到了C语言中strcpy
- // c_str C++的字符串转换成C语言的字符数组
- // c_str 返回一个const char *
- char c[20] = {0};
- strcpy(c,s8.c_str());
- cout << c << endl; // ABCDEFGH
- return 0;
- }
复制代码