[C.C++] 【C++】零基础教程,看这一篇就够了

103 0
Honkers 6 小时前 来自手机 | 显示全部楼层 |阅读模式

目录

一、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功能时,增加下面这句话即可。

  1. 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 概念

       引用从一定程度上讲是指针的平替。几乎被所有的面向对象编程语言所使用,引用相当于对某一目标变量起“别名”。

操作引用与操作原变量完全一样。

  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. int a = 1;
  6. // b是a的引用
  7. int &b = a;
  8. cout << a << " " << &a << endl; // 1 0x61fe88
  9. cout << b << " " << &b << endl; // 1 0x61fe88
  10. return 0;
  11. }
复制代码

1.2 引用的性质

● 可以改变引用的值,但是不能再次成为其他变量的引用。

  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. int a = 1;
  6. // b是a的引用
  7. int &b = a;
  8. int c = 3;
  9. b = c; // 赋值,不是引用,b还是a的引用
  10. b++;
  11. cout << a << " " << &a << endl; // 4 0x61fe88
  12. cout << b << " " << &b << endl; // 4 0x61fe88
  13. cout << c << " " << &c << endl; // 3 0x61fe84
  14. return 0;
  15. }
复制代码

● 声明引用时,必须要初始化。

  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. int a = 1;
  6. // int &b; // 错误,引用必须初始化
  7. b = a;
  8. return 0;
  9. }
复制代码

● 声明引用时(普通使用),不能初始化为NULL。

  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. int a = 1;
  6. // int &b = NULL; // 错误,常量不能直接起别名
  7. return 0;
  8. }
复制代码

● 声明引用的时候,初始化的值可以是纯数值,但是此时要用const关键字修饰引用,表示该引用为常量引用,这样的引用的值不可变。

  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. const int &b = 12; // 常量引用
  6. // b = 444; // 错误 常量引用的数值不能被改变的。
  7. return 0;
  8. }
复制代码

● 可以将变量引用的地址,赋值给一个指针,此时指针指向的还是原来的变量。

  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. int a = 1;
  6. int &b = a;
  7. int *c = &b; // C同时指向了a和b
  8. cout << a << " " << &a << endl; // 1 0x61fe84
  9. cout << b << " " << &b << endl; // 1 0x61fe84
  10. cout << *c << " " << c << endl; // 1 0x61fe84
  11. return 0;
  12. }
复制代码

● 可以对指针建立引用

  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. int a = 1;
  6. int &b = a;
  7. int *c = &b;
  8. int *&d = c; // d是c的引用
  9. cout << a << " " << &a << endl; // 1 0x61fe84
  10. cout << b << " " << &b << endl; // 1 0x61fe84
  11. cout << *c << " " << c << endl; // 1 0x61fe84
  12. cout << *d << " " << d << endl; // 1 0x61fe84
  13. return 0;
  14. }
复制代码

● 可以使用const修饰引用,此时如果原变量的值改变,引用的值也会改变。

  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. int a = 2;
  6. const int &b = a;
  7. // b++; 错误 b是只读的
  8. a++;
  9. cout << a << " " << &a << endl; // 3
  10. cout << b << " " << &b << endl; // 3
  11. return 0;
  12. }
复制代码

1.3 引用的参数

【思考】

写一个函数,函数有两个参数a和b,函数的功能是交换两个传入的参数原来变量的值。

  1. #include <iostream>
  2. using namespace std;
  3. // 法一:操作拷贝的是副本,不符合需求
  4. void test(int a,int b)
  5. {
  6. int temp = a;
  7. a = b;
  8. b = temp;
  9. cout << a << endl;
  10. cout << b << endl;
  11. }
  12. // 法二:C语言写法,在C++中不推荐这种写法
  13. void test1(int *a,int *b)
  14. {
  15. *a = *a ^ *b;
  16. *b = *a ^ *b;
  17. *a = *a ^ *b;
  18. }
  19. // 法三:C++编程方式,符合需求
  20. void test2(int &a,int &b) //引用作为参数传递
  21. {
  22. a = a ^ b;
  23. b = a ^ b;
  24. a = a ^ b;
  25. }
  26. int main()
  27. {
  28. int a1 = 1;
  29. int b1 = 2;
  30. test2(a1,b1);
  31. cout << a1 << endl;
  32. cout << b1 << endl;
  33. return 0;
  34. }
复制代码

       引用作为参数进行定义

       好处:在参数传递的时候,不会产生副本,这样会提高程序的运行效率。我们在正常编译中,建议使用引用传递参数。

       引用形参,在不参与计算的情况下,我们建议使用const进行修饰,以达到引用的安全性。

  1. #include <iostream>
  2. using namespace std;
  3. void test2(const int &a)
  4. {
  5. // a++; // 错误 const修饰,无法修改
  6. cout << a << endl; //1
  7. }
  8. int main()
  9. {
  10. int a1 = 1;
  11. test2(a1);
  12. cout << a1 << endl; //1
  13. return 0;
  14. }
复制代码

【面试题】

 指针和引用的区别?

指针:是一个变量,存储另一个变量的内存地址。
需要显式解引用(*)来访问目标值。

引用:是变量的别名,本质是绑定到目标变量的“另一个名字”。
无需解引用,直接操作引用即操作原变量。

 核心区别

特性指针引用
初始化要求可以声明时不初始化(但建议初始化)必须初始化,且不能重新绑定到其他变量
空值(Null)可以赋值为 nullptr不能为空,必须绑定有效对象
重定向能力可以指向不同对象一旦初始化,无法更改绑定对象
内存占用占用独立内存(存储地址)不占用独立内存(仅是别名)
多级间接访问支持多级指针(如 int**)不支持多级引用,但支持对指针的引用(如 int*&)
运算操作支持指针算术(+, -, ++ 等)不支持运算,操作等同于原变量

2、  赋值(熟悉)

通常使用=号进行赋值操作,C++新增了以下赋值的语法:

  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. int a(1); // 等同于 int a = 1;
  6. cout << a << endl;
  7. int b(a); // 等同于 int b = a;
  8. cout << b << endl;
  9. int c(a+b); // 等同于 int c = a + b;
  10. cout << c << endl;
  11. return 0;
  12. }
复制代码

在C++11中对上述写法进行升级

  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. int a(1); // 等同于 int a = 1;
  6. cout << a << endl;
  7. double b = 3.14;
  8. int b1 = b;
  9. cout << b1 << endl; // 3
  10. int b2(b);
  11. cout << b2 << endl; // 3
  12. int b3{b}; // 升级:对数据窄化提出警告
  13. cout << b3 << endl; // 3
  14. return 0;
  15. }
复制代码

3、  键盘输入(熟悉)

可以使用cin把用户在命令行中输入的内容赋值到变量中。

cin与cout一样,都是数据头文件iostream中的标准输入输出流。

  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. int a;
  6. // C++的字符串是string
  7. string str;
  8. cout << "请输入一个数字和字符串" << endl;
  9. cin >> a >> str; // 接收键盘输入,一个整数和一个字符串,可以连续操作
  10. cout << a << str << endl;
  11. return 0;
  12. }
复制代码

如果cin输入的字符串包含空格,可以使用下面的方式:

  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. // C++的字符串是string
  6. string a;
  7. cout << "请输入一个字符串,可以包含空格" << endl;
  8. getline(cin,a);
  9. cout << a << endl;
  10. return 0;
  11. }
复制代码

4、  string字符串类(掌握)

      string不是C++的基本数据类型,它是一个C++标准库中的字符串类,使用时需要引入头文件#include 。而不是string.h

       string在绝大多数情况下可以代替C语言的字符串,不必担心内存是否足够和字符串长度等,其中内部还包含了很多的字符串处理函数,可以完成各种情况下的字符串的处理功能。

       string和C语言相同,字符串编码使用的是ASCII码。不支持中文处理。

  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. string str = "helloworld";
  6. cout << str.size() << endl; // 10
  7. cout << str.length() << endl; // 10
  8. cout << str[5] << endl; // w
  9. cout << str.at(5) << endl; // w
  10. return 0;
  11. }
复制代码

   两种方式都可以,但是在C++中更推荐使用at函数,原因是at函数更安全,[ ]的执行效率高。

  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. string str = "helloworld";
  6. cout << str[100] << endl; // 会输出一个越界数据
  7. cout << str.at(100) << endl; // 程序运行停止
  8. cout << "hello" << endl;
  9. return 0;
  10. }
复制代码

string类支持多种遍历方式:

● 普通循环(以for循环为主)

● C++11: for each循环

  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. string str = "helloworld";
  6. // 以for循环的方式进行输出字符串
  7. for(int i = 0; i < str.size(); i++)
  8. {
  9. cout << str.at(i);
  10. }
  11. cout << endl;
  12. // 以for each的方式循环遍历字符串
  13. for(char i : str)
  14. {
  15. cout << i;
  16. }
  17. return 0;
  18. }
复制代码

了解:字符串与数字的转换

  1. #include <iostream>
  2. #include <sstream> // 字符串流
  3. using namespace std;
  4. int main()
  5. {
  6. string s = "123";
  7. // int i = s; 错误
  8. // string → int
  9. istringstream iss(s);
  10. int i;
  11. iss >> i;
  12. cout << i << endl;
  13. // int → string
  14. // string s2 = i; 错误
  15. stringstream ss;
  16. ss << i;
  17. string s2 = ss.str();
  18. cout << s2 << endl;
  19. return 0;
  20. }
复制代码

5、  函数

5.1 内联函数(熟悉)

      内联函数用于取代C语言中宏定义的函数,内联函数的正确使用可以提升程序的执行效率。

     内联函数在编译的时候,直接把函数体展开到主函数中编译,在运行期间可以减少调用的开销,以空间换取时间。

通常将具有以下性质的函数写为内联函数

● 代码长度5行以内

● 不包含复杂的控制语句

● 频繁的被调用

内联函数的关键字inline

后续学习的成员函数默认添加inline关键字修饰。

但是我们手动添加上inline关键字,目的是将函数声明成内联函数,但是这个不是我们能决定的,编译器有自己的判断准则,我们只是给编译器提一个建议,具体是否变为内联函数,还是编译器自己决定的。

  1. include <iostream>
  2. using namespace std;
  3. // 内联函数
  4. inline void print_string(string str)
  5. {
  6. cout << str << endl;
  7. }
  8. int main()
  9. {
  10. print_string("helloworld");
  11. return 0;
  12. }
复制代码

5.2 函数重载(重点)

       C++中允许多个函数使用同一个名称,这种用法就是函数重载,函数重载要求函数名称相同,但是参数不同(类型不同、数量不同)或者前后顺序不同,与返回值类型无关。

  1. #include <iostream>
  2. using namespace std;
  3. void print_show(int i)
  4. {
  5. cout << "调用了int重载:" << i << endl;
  6. }
  7. void print_show(float i)
  8. {
  9. cout << "调用了float重载:" << i << endl;
  10. }
  11. void print_show(string i)
  12. {
  13. cout << "调用了string重载:" << i << endl;
  14. }
  15. // 错误 名称相同,参数类型相同,编译器无法区分
  16. // 与返回值类型无关
  17. //int print_show(int s)
  18. //{
  19. // cout << "调用了int2重载:" << s << endl;
  20. // return s;
  21. //}
  22. // 错误 const关键字,也无法作为判断依据
  23. //void print_show(const string s)
  24. //{
  25. // cout << "调用了string重载:" << s << endl;
  26. //}
  27. int main()
  28. {
  29. print_show(1.2);
  30. return 0;
  31. }
复制代码

5.3 哑元函数(熟悉)

函数的参数只有类型,没有名称,有这样参数的函数就是哑元函数。

作用1:哑元函数可以用来区分函数重载

作用2:运算符重载中会用到

  1. #include <iostream>
  2. using namespace std;
  3. // 哑元函数
  4. void print_show(int)
  5. {
  6. cout << "调用了int重载" << endl;
  7. }
  8. int main()
  9. {
  10. print_show(1);
  11. return 0;
  12. }
复制代码

三、面向对象基础

1、类与对象(重点)

1.1 概念

类:类是一个抽象的概念,用于描述同一类对象的特点。

对象:根据类的概念所创造的实体。

【思考】:一个对象可以没有对应的类嘛?

                  不可以的。

对于现在的学习,如果只写一个类,没有对应的对象,这样的类是没有意义的。

1.2 类的内容

类中最基础的内容包括两个部分:一个是属性,一个是行为。

● 属性:表示一些特征项的数值,比如说:身高、体重、年龄、性别、型号、而这些特正项的数值也被称为”成员变量“。属性一般以名词存在。

● 行为:表示能执行的动作,能干什么事?比如说:吃饭、睡觉、打架、叫、唱、跳、rap、打篮球。行为一般通过函数来表示,也被称为”成员函数“。行为一般以动词存在。

成员 = 成员函数 + 成员变量。

【例子】以手机为例,来说明类的定义。

规定手机能够播放音乐、运行游戏、打电话。手机有:品牌、型号、重量等等。

  1. #include <iostream>
  2. using namespace std;
  3. // 大驼峰命名法(帕斯卡命名法)
  4. // 每个单词的首字母要大写
  5. class MobilePhone
  6. {
  7. public: // 权限:public最开放的权限
  8. string brand; // 品牌
  9. string model; // 型号
  10. int weight; // 重量
  11. void play_music()
  12. {
  13. cout << "来财" << endl;
  14. }
  15. void run_game()
  16. {
  17. cout << "无畏契约、第五人格、cs";
  18. }
  19. void call()
  20. {
  21. cout << "您拨打的电话正在通话中,请稍后在拨" << endl;
  22. }
  23. };
  24. int main()
  25. {
  26. return 0;
  27. }
复制代码

1.3 对象的创建

C++中存在两种类型的对象。

● 栈内存对象

对象所在的{}执行结束后,自动被销毁。

  1. #include <iostream>
  2. using namespace std;
  3. // 大驼峰命名法(帕斯卡命名法)
  4. // 每个单词的首字母要大写
  5. class MobilePhone
  6. {
  7. public: // 权限:public最开放的权限
  8. string brand; // 品牌
  9. string model; // 型号
  10. int weight; // 重量
  11. void play_music()
  12. {
  13. cout << "来财" << endl;
  14. }
  15. void run_game()
  16. {
  17. cout << "无畏契约、第五人格、cs";
  18. }
  19. void call()
  20. {
  21. cout << "您拨打的电话正在通话中,请稍后在拨" << endl;
  22. }
  23. };
  24. int main()
  25. {
  26. MobilePhone mp; // 创建一个栈内存对象
  27. mp.brand = "华为";
  28. mp.model = "遥遥领先";
  29. mp.weight = 500;
  30. cout << mp.brand << " " << mp.model << " " << mp.weight << endl;
  31. mp.play_music();
  32. mp.run_game();
  33. mp.call();
  34. return 0;
  35. }
复制代码
● 堆内存对象

      必须使用new关键字创建,使用指针保存,如果不使用delete关键字销毁,则堆内存对象会持续存在,而导致内存泄漏。堆内存调用对象内容时,使用 ->而不是" . "

  1. #include <iostream>
  2. using namespace std;
  3. // 大驼峰命名法(帕斯卡命名法)
  4. // 每个单词的首字母要大写
  5. class MobilePhone
  6. {
  7. public: // 权限:public最开放的权限
  8. string brand; // 品牌
  9. string model; // 型号
  10. int weight; // 重量
  11. void play_music()
  12. {
  13. cout << "来财" << endl;
  14. }
  15. void run_game()
  16. {
  17. cout << "无畏契约、第五人格、cs";
  18. }
  19. void call()
  20. {
  21. cout << "您拨打的电话正在通话中,请稍后在拨" << endl;
  22. }
  23. };
  24. int main()
  25. {
  26. MobilePhone *mp = new MobilePhone; // 堆内存对象
  27. mp->brand = "小米";
  28. mp->model = "mi6";
  29. mp->weight = 200;
  30. cout << mp->brand << " " << mp->model << " " << mp->weight << endl;
  31. mp->play_music();
  32. mp->run_game();
  33. mp->call();
  34. delete mp; // 手动销毁
  35. mp = NULL; // 建议指向空,防止后面误用
  36. return 0;
  37. }
复制代码

【面试题】

malloc和free与new和delete 的区别?

malloc返回值为 void *类型,需要强制转换,而new创建什么类型对象,返回什么类型地址。

 核心区别

特性malloc/freenew/delete
语言C/C++C++ 特有
内存分配方式仅分配/释放原始内存分配内存并调用构造/析构函数
类型安全需手动类型转换自动类型推导
内存大小计算需手动指定字节数自动计算类型大小
失败处理返回 NULL抛出 std::bad_alloc 异常(默认)
重载支持不支持支持运算符重载
数组处理需手动计算空间支持 new[]/delete[] 语法
与对象生命周期的结合无关管理对象构造与析构

【练习】1、键盘输入一个100-999之间的数,依次输出这个数的个百位。

参考代码:

  1. int main()
  2. {
  3. //1、键盘输入一个100-999之间的数,依次输出这个数的个十百位。
  4. int a;
  5. cout << "请输入一个100-999之间的数:" << endl;
  6. cin >> a;
  7. cout << "个位:" << a%10 << endl;
  8. cout << "十位:" << a/10%10 << endl;
  9. cout << "百位:" << a/100 << endl;
  10. return 0;
  11. }
复制代码

【练习】2输入一行字符串,分别统计出其中的英文字母,数字和其他字符的个数。

参考代码:

  1. int main()
  2. {
  3. //2、输入一行字符串,分别统计出其中的英文字母,数字和其他字符的个数。
  4. string str;
  5. cout << "请输入一行字符串:" << endl;
  6. cin >> str;
  7. int num=0, letter=0, other=0;
  8. for(int i=0;i<str.size();i++)
  9. {
  10. if(str.at(i)>='0' && str.at(i)<='9')
  11. num++;
  12. else if((str.at(i)>='a' && str.at(i)<='z' )|| (str.at(i)>='A' && str.at(i)<= 'Z'))
  13. letter++;
  14. else
  15. other++;
  16. }
  17. cout << "英文字母个数为:" << letter << endl;
  18. cout << "数字个数为:" << num << endl;
  19. cout << "其他字符个数为:" << other << endl;
  20. return 0;
  21. }
复制代码

2、封装(重点)

       MobilePhone类与结构体差别不大,实际上可以认为结构体就是一种完全开放的类。

       封装指的是,将类的一些属性和细节隐藏(private),重新提供外部访问接口,封装的优势可以提升代码的安全性,并且可以让程序员更关注上层的结构,而非内部细节。

  1. #include <iostream>
  2. using namespace std;
  3. // 大驼峰命名法(帕斯卡命名法)
  4. // 每个单词的首字母要大写
  5. class MobilePhone
  6. {
  7. private: // 私有权限,private 是最封闭的权限,只能在内类访问
  8. string brand; // 品牌
  9. string model; // 型号
  10. int weight = 200; // 重量
  11. public: // 权限:public最开放的权限
  12. // get 函数
  13. string get_brand()
  14. {
  15. return brand;
  16. }
  17. // set 函数
  18. void set_brand(string b)
  19. {
  20. brand = b;
  21. }
  22. // get 函数
  23. string get_model()
  24. {
  25. return model;
  26. }
  27. // set 函数
  28. void set_model(string m)
  29. {
  30. model = m;
  31. }
  32. // get 函数
  33. int get_weight()
  34. {
  35. return weight;
  36. }
  37. };
  38. int main()
  39. {
  40. MobilePhone *mp = new MobilePhone; // 堆内存对象
  41. mp->set_brand("小辣椒");
  42. mp->set_model("超辣");
  43. cout << mp->get_brand() << " " << mp->get_model() << " " << mp->get_weight() << endl;
  44. delete mp; // 手动销毁
  45. mp = NULL; // 建议指向空,防止后面误用
  46. return 0;
  47. }
复制代码

3、构造函数(掌握)

3.1 基本使用

       构造函数是一种特殊的成员函数,用于创建对象时初始化,创建对象时必须直接或者间接调用当前类任意一个构造函数。

写法上有以下要求:

● 函数名称必须与类名完全相同

● 构造函数不写返回值

● 如果程序员不手写构造函数,编译器会自动添加一个无参的构造函数

手动添加任意一个构造函数,编译器就不会自动添加默认无参构造函数了。

● 构造函数在创建对象时,常用于给对象的属性赋予初始值。

  1. #include <iostream>
  2. using namespace std;
  3. // 大驼峰命名法(帕斯卡命名法)
  4. // 每个单词的首字母要大写
  5. class MobilePhone
  6. {
  7. private: // 私有权限,private 是最封闭的权限,只能在内类访问
  8. string brand; // 品牌
  9. string model; // 型号
  10. int weight; // 重量
  11. public: // 权限:public最开放的权限
  12. // 编译器自动添加的构造函数
  13. MobilePhone()
  14. {
  15. brand = "8848";
  16. model = "8848钛金手机-鳄鱼皮-巅峰版";
  17. weight = 1000;
  18. }
  19. // get 函数
  20. string get_brand()
  21. {
  22. return brand;
  23. }
  24. // get 函数
  25. string get_model()
  26. {
  27. return model;
  28. }
  29. // get 函数
  30. int get_weight()
  31. {
  32. return weight;
  33. }
  34. };
  35. int main()
  36. {
  37. MobilePhone *mp = new MobilePhone; // 堆内存对象
  38. cout << mp->get_brand() << " " << mp->get_model() << " " << mp->get_weight() << endl;
  39. delete mp; // 手动销毁
  40. mp = NULL; // 建议指向空,防止后面误用
  41. return 0;
  42. }
复制代码

● 构造函数也支持函数重载

  1. #include <iostream>
  2. using namespace std;
  3. // 大驼峰命名法(帕斯卡命名法)
  4. // 每个单词的首字母要大写
  5. class MobilePhone
  6. {
  7. private: // 私有权限,private 是最封闭的权限,只能在内类访问
  8. string brand; // 品牌
  9. string model; // 型号
  10. int weight; // 重量
  11. public: // 权限:public最开放的权限
  12. // 无参构造函数
  13. MobilePhone()
  14. {
  15. brand = "8848";
  16. model = "8848钛金手机-鳄鱼皮-巅峰版";
  17. weight = 1000;
  18. }
  19. // 有参构造函数
  20. MobilePhone(string b, string m, int w)
  21. {
  22. brand = b;
  23. model = m;
  24. weight = w;
  25. }
  26. // get 函数
  27. string get_brand()
  28. {
  29. return brand;
  30. }
  31. // get 函数
  32. string get_model()
  33. {
  34. return model;
  35. }
  36. // get 函数
  37. int get_weight()
  38. {
  39. return weight;
  40. }
  41. };
  42. int main()
  43. {
  44. MobilePhone *mp = new MobilePhone("小米","18 pro",200); // 堆内存对象
  45. cout << mp->get_brand() << " " << mp->get_model() << " " << mp->get_weight() << endl;
  46. delete mp; // 手动销毁
  47. mp = NULL; // 建议指向空,防止后面误用
  48. return 0;
  49. }
复制代码

● 构造函数也支持函数参数默认值

  1. #include <iostream>
  2. using namespace std;
  3. // 大驼峰命名法(帕斯卡命名法)
  4. // 每个单词的首字母要大写
  5. class MobilePhone
  6. {
  7. private: // 私有权限,private 是最封闭的权限,只能在内类访问
  8. string brand; // 品牌
  9. string model; // 型号
  10. int weight; // 重量
  11. public: // 权限:public最开放的权限
  12. // 无参构造函数
  13. // 下面两种写法的构造函数,都可以给对象做初始化,但是不能同时存在,因为会触发二义性的问题。
  14. // MobilePhone()
  15. // {
  16. // brand = "8848";
  17. // model = "8848钛金手机-鳄鱼皮-巅峰版";
  18. // weight = 1000;
  19. // }
  20. // 有参构造函数(全缺省构造函数)
  21. MobilePhone(string b = "小米", string m = "15pro", int w = 200)
  22. {
  23. brand = b;
  24. model = m;
  25. weight = w;
  26. }
  27. // get 函数
  28. string get_brand()
  29. {
  30. return brand;
  31. }
  32. // get 函数
  33. string get_model()
  34. {
  35. return model;
  36. }
  37. // get 函数
  38. int get_weight()
  39. {
  40. return weight;
  41. }
  42. };
  43. int main()
  44. {
  45. MobilePhone *mp = new MobilePhone; // 堆内存对象
  46. cout << mp->get_brand() << " " << mp->get_model() << " " << mp->get_weight() << endl;
  47. delete mp; // 手动销毁
  48. mp = NULL; // 建议指向空,防止后面误用
  49. return 0;
  50. }
复制代码

3.2 构造初始化列表

构造初始化列表是一种更简单的给成员变量赋予初始值的写法。

  1. #include <iostream>
  2. using namespace std;
  3. // 大驼峰命名法(帕斯卡命名法)
  4. // 每个单词的首字母要大写
  5. class MobilePhone
  6. {
  7. private: // 私有权限,private 是最封闭的权限,只能在内类访问
  8. string brand; // 品牌
  9. string model; // 型号
  10. int weight; // 重量
  11. public: // 权限:public最开放的权限
  12. // 无参构造函数
  13. MobilePhone()
  14. :brand("8848"),model("8848钛金手机-鳄鱼皮-巅峰版"),weight(200){}
  15. // 有参构造函数
  16. MobilePhone(string b, string m, int w)
  17. :brand(b),model(m),weight(w){}
  18. // get 函数
  19. string get_brand()
  20. {
  21. return brand;
  22. }
  23. // get 函数
  24. string get_model()
  25. {
  26. return model;
  27. }
  28. // get 函数
  29. int get_weight()
  30. {
  31. return weight;
  32. }
  33. };
  34. int main()
  35. {
  36. MobilePhone *mp = new MobilePhone("华为","18",200); // 堆内存对象
  37. cout << mp->get_brand() << " " << mp->get_model() << " " << mp->get_weight() << endl;
  38. delete mp; // 手动销毁
  39. mp = NULL; // 建议指向空,防止后面误用
  40. return 0;
  41. }
复制代码

       当构造函数的局部变量与成员变量重名时,除了使用后面学习的this指针的方式外,还可以使用构造初始化列表区分。

3.3 隐式调用和显式调用

       构造函数的调用可以分为显式调用隐式调用

       显式调用指的是创建对象的时候手写构造函数的名称。

       隐式调用指的是在创建对象的时候不写参数列表,或者构造函数的名称也不写。编译器会尝试调用对应参数的构造函数。

  1. #include <iostream>
  2. using namespace std;
  3. class Student
  4. {
  5. private:
  6. int age;
  7. public:
  8. Student(int a):age(a)
  9. {
  10. cout << "构造函数被调用啦" << endl;
  11. }
  12. Student()
  13. {
  14. cout << "无参构造函数被调用啦" << endl;
  15. }
  16. int get_age()
  17. {
  18. return age;
  19. }
  20. };
  21. int main()
  22. {
  23. Student s1(12); // 栈内存对象,显式调用
  24. Student s3 = Student(14); // 显式调用
  25. Student s4 = 15; // 隐式调用
  26. Student *s2 = new Student(13); // 显式调用
  27. Student s5; // 默认调用方式,显式调用
  28. return 0;
  29. }
复制代码

建议使用显式调用,可以使用explicit关键字屏蔽隐式调用的语法。

3.4 拷贝构造函数

3.4.1 概念

       当程序员不手写拷贝构造函数时,编译器会自动添加一个拷贝构造函数,使对象创建可以通过这个构造函数实现。

  1. #include <iostream>
  2. using namespace std;
  3. class Student
  4. {
  5. private:
  6. int age;
  7. public:
  8. Student(int a):age(a)
  9. {
  10. cout << "构造函数被调用啦" << endl;
  11. }
  12. Student()
  13. {
  14. cout << "无参构造函数被调用啦" << endl;
  15. }
  16. // 手动添加默认的拷贝构造函数
  17. Student(Student &mp)
  18. {
  19. age = mp.age;
  20. }
  21. int get_age()
  22. {
  23. return age;
  24. }
  25. };
  26. int main()
  27. {
  28. Student s1(12); // 调用有参构造函数
  29. cout << s1.get_age() << endl; // 12
  30. Student s2(s1); // 拷贝构造函数
  31. cout << s2.get_age() << endl; // 12
  32. return 0;
  33. }
复制代码
【思考】默认的拷贝构造函数是否存在隐患?

       存在,当成员变量出现指针类型时,默认的拷贝构造函数会导致两个对象的指针成员变量指向同一处。这种现象被称为”浅拷贝“。

3.4.2 浅拷贝
  1. #include <iostream>
  2. #include <string.h>
  3. using namespace std;
  4. class Dog
  5. {
  6. private:
  7. char *name;
  8. public:
  9. Dog(char *n)
  10. {
  11. name = n;
  12. }
  13. void show_name()
  14. {
  15. cout << name << endl;
  16. }
  17. };
  18. int main()
  19. {
  20. char arr[20] = "旺财";
  21. Dog d1(arr);
  22. Dog d2(d1); // 拷贝构造函数
  23. strcpy(arr,"大黄"); // 浅拷贝,更改外部内存,对象内部的数据也被更改了,因为是操作的同一块空间。
  24. d1.show_name(); // 大黄
  25. d2.show_name(); // 大黄
  26. return 0;
  27. }
复制代码

       这种情况必须手动重写构造函数,使每次赋值都创建一个新的副本,从而每个对象单独持有自己的成员变量。这种方式也被称为”深拷贝“。

3.4.3 深拷贝

  1. #include <iostream>
  2. #include <string.h>
  3. using namespace std;
  4. class Dog
  5. {
  6. private:
  7. char *name;
  8. public:
  9. Dog(char *n)
  10. {
  11. name = new char[20];
  12. strcpy(name,n);
  13. }
  14. Dog(Dog &d)
  15. {
  16. name = new char[20];
  17. strcpy(name,d.name);
  18. }
  19. void show_name()
  20. {
  21. cout << name << endl;
  22. }
  23. };
  24. int main()
  25. {
  26. char arr[20] = "旺财";
  27. Dog d1(arr);
  28. Dog d2(d1); // 拷贝构造函数
  29. strcpy(arr,"大黄"); // 浅拷贝,更改外部内存,对象内部的数据也被更改了,因为是操作的同一块空间。
  30. d1.show_name(); // 旺财
  31. d2.show_name(); // 旺财
  32. return 0;
  33. }
复制代码
【思考】当前深拷贝的代码是否存在隐患?

存在,new开辟的堆内存空间,没有释放,造成内存泄漏的问题。

4、  析构函数(析构函数)

析构函数是与构造函数完全对立的函数。

编译器会自动添加默认没有任何操作的析构函数。

构造函数

析构函数

创建对象时手动调用

当对象销毁时,自动调用

函数名称是类名

函数名称是~类名

构造函数可以重载

析构函数没有参数,不能重载

用于创建对象时做初始化的

用于销毁对象时释放资源

有返回值但是不写,返回值是新创建的对象

没有返回值

  1. #include <iostream>
  2. #include <string.h>
  3. using namespace std;
  4. class Dog
  5. {
  6. private:
  7. char *name;
  8. public:
  9. Dog(char *n)
  10. {
  11. name = new char[20];
  12. strcpy(name,n);
  13. }
  14. Dog(Dog &d)
  15. {
  16. name = new char[20];
  17. strcpy(name,d.name);
  18. }
  19. void show_name()
  20. {
  21. cout << name << endl;
  22. }
  23. ~Dog()
  24. {
  25. cout << "析构函数被调用了" << endl;
  26. delete []name;
  27. }
  28. };
  29. int main()
  30. {
  31. char arr[20] = "旺财";
  32. Dog d1(arr);
  33. Dog d2(d1); // 拷贝构造函数
  34. strcpy(arr,"大黄"); // 浅拷贝,更改外部内存,对象内部的数据也被更改了,因为是操作的同一块空间。
  35. d1.show_name(); // 旺财
  36. d2.show_name(); // 旺财
  37. return 0;
  38. }
复制代码

5、作用域限定符::

5.1 名字空间(熟悉)

namespace my_space     //声明一个名字空间

using namespace my_space //使用名字空间

  1. #include <iostream>
  2. #include <string.h>
  3. using namespace std;
  4. int a = 2;
  5. namespace my_space
  6. {
  7. int a = 3;
  8. int b = 4;
  9. }
  10. using namespace my_space;
  11. int main()
  12. {
  13. int a = 1;
  14. cout << a << endl; // 就近 打印1
  15. cout << ::a << endl; // ::a 全局作用域 2
  16. cout << my_space::a << endl; // 3
  17. cout << b << endl;
  18. return 0;
  19. }
复制代码

5.2 类内声明,类外定义(掌握)

       类外定义,加 “ 类名::”,告诉编译器是哪个类的成员函数,定义写在类外,简洁,代码实现性更高。

  1. #include <iostream>
  2. using namespace std;
  3. class Demo
  4. {
  5. public:
  6. // 类内声明
  7. Demo();
  8. void test(string str);
  9. };
  10. // 类外定义
  11. Demo::Demo()
  12. {
  13. cout << "创建了一个对象" << endl;
  14. }
  15. void Demo::test(string str)
  16. {
  17. cout << "string::" << str << endl;
  18. }
  19. int main()
  20. {
  21. Demo d; // 调用无参构造函数
  22. d.test("hello");
  23. return 0;
  24. }
复制代码

6、this指针(掌握)

6.1 概念

       this指针是一个特殊的指针,指向当前类对象的首地址。

       成员函数(包括构造函数与析构函数)中都有this指针,因此this指针只能在类内部使用。实际上this指向的就是当前运行的成员函数所绑定的对象。

  1. #include <iostream>
  2. using namespace std;
  3. class Test
  4. {
  5. public:
  6. void test_this()
  7. {
  8. cout << this << endl;
  9. }
  10. };
  11. int main()
  12. {
  13. Test t1;
  14. cout << &t1 << endl; // 0x61fe8f
  15. t1.test_this(); // 0x61fe8f
  16. Test *t2 = new Test;
  17. cout << t2 << endl; // 0x781108
  18. t2->test_this(); // 0x781108
  19. delete t2;
  20. return 0;
  21. }
复制代码

6.2 功能

6.2.1 类内调用成员
  1. #include <iostream>
  2. using namespace std;
  3. class Test
  4. {
  5. private:
  6. string name;
  7. public:
  8. Test(string n)
  9. {
  10. // 编译器默认添加this指针 指向的对象调用成员
  11. this->name = n;
  12. }
  13. string get_name()
  14. {
  15. // 编译器默认添加this指针 指向的对象调用成员
  16. return this->name;
  17. }
  18. };
  19. int main()
  20. {
  21. Test t1("zhangsan");
  22. cout << t1.get_name() << endl;
  23. return 0;
  24. }
复制代码
6.2.2 区分重名的成员变量和局部变量
  1. #include <iostream>
  2. using namespace std;
  3. class Test
  4. {
  5. private:
  6. string name;
  7. public:
  8. Test(string name):name(name) // 构造初始化列表可以区分
  9. {
  10. // 通过this在函数体中区分
  11. this->name = name;
  12. }
  13. string get_name()
  14. {
  15. return name;
  16. }
  17. };
  18. int main()
  19. {
  20. Test t1("zhangsan");
  21. cout << t1.get_name() << endl;
  22. return 0;
  23. }
复制代码
6.2.3 链式调用

支持链式调用的成员函数的特点:

1、  当一个成员函数的返回值是当前类型的引用时,往往表示这个函数支持链式调用。

2、  return后面是*this

  1. #include <iostream>
  2. using namespace std;
  3. class Test
  4. {
  5. private:
  6. int val = 0;
  7. public:
  8. Test& add(int i)
  9. {
  10. val += i; // val = val + i;
  11. return *this; // this 是一个指针,返回当前对象需要取内容
  12. }
  13. int get_val()
  14. {
  15. return val;
  16. }
  17. };
  18. int main()
  19. {
  20. Test t1;
  21. t1.add(1);
  22. t1.add(2);
  23. t1.add(100);
  24. cout << t1.get_val() << endl; // 103
  25. Test t2;
  26. // 链式调用
  27. cout << t2.add(2).add(21).add(200).get_val() << endl; // 223
  28. cout << t2.get_val() << endl; // 223
  29. return 0;
  30. }
复制代码

[练习]:猫吃鱼

 写一个类Fish,有品种和重量两个属性,属性的类型自己选择,要求属性封装。
写一个类Cat,Cat中有一个公有的成员函数:
Fish& eat(Fish &f);
eat函数的功能要求判断Fish的品种:
●如果品种是“秋刀鱼”,则输出“无论多沉,我都爱吃。”。同时修改Fish &f的重量为0,并作为函数的返回值返回。
●如果品种不是“秋刀鱼”,则判断鱼的重量,若重量大于200,吃鱼输出信息并返回一个重量为0的Fish;若重量小于200,输出信息,不要修改鱼的重量,直接返回鱼的对象。


其它知识点(构造函数、构造初始化列表等)可以自行选择是否采用。

代码示例:

  1. #include <iostream>
  2. using namespace std;
  3. class Fish
  4. {
  5. private:
  6. string variety;
  7. int weight;
  8. public:
  9. //有参构造函数-构造初始化列表
  10. Fish(string v,int w)
  11. :variety(v),weight(w){}
  12. string get_variety()
  13. {
  14. return variety;
  15. }
  16. int get_weight()
  17. {
  18. return weight;
  19. }
  20. int set_weight(int w)
  21. {
  22. weight=w;
  23. }
  24. };
  25. class Cat
  26. {
  27. public:
  28. Cat& eat(Fish &f)
  29. {
  30. if(f.get_variety() == "秋刀鱼")
  31. {
  32. cout << "无论多沉,我都爱吃。" << endl;
  33. f.set_weight(0);
  34. cout << "剩余重量:" << f.get_weight() << endl;
  35. }
  36. else
  37. {
  38. if(f.get_weight()>200)
  39. {
  40. cout << "不是秋刀鱼我也爱吃!" << endl;
  41. f.set_weight(0);
  42. cout << "剩余重量:"<< f.get_weight() << endl;
  43. }
  44. else
  45. {
  46. cout << "我不饿,先不吃鱼了" << endl;
  47. cout <<"剩余重量:" << f.get_weight() << endl;
  48. }
  49. }
  50. return *this;
  51. }
  52. };
  53. int main()
  54. {
  55. while(1)
  56. {
  57. int a;
  58. string str;
  59. cout << "想吃什么鱼?想吃多重的?" << endl;
  60. cin >> str >> a;
  61. Fish f(str,a);
  62. Cat cat;
  63. cat.eat(f);
  64. }
  65. // Fish f1("秋刀鱼",200);
  66. // Fish f2("鲅鱼",800);
  67. // Fish f3("鳗鱼",150);
  68. //链式调用
  69. // cat.eat(f1).eat(f2).eat(f3);
  70. return 0;
  71. }
复制代码

7、static关键字(掌握)

7.1静态局部变量

       使用static修饰局部变量,这样的变量就是静态局部变量。

       静态局部变量在第一次调用时创建,直到程序结束后销毁,同一个类的所有对象共用这一份静态局部变量。

  1. #include <iostream>
  2. using namespace std;
  3. class Test
  4. {
  5. public:
  6. void func()
  7. {
  8. int a = 1;
  9. static int b = 1;
  10. cout << "a=" << ++a << endl;
  11. cout << "b=" << ++b << endl;
  12. }
  13. };
  14. int main()
  15. {
  16. Test t1;
  17. t1.func(); //a=2 b=2
  18. t1.func(); //a=2 b=3
  19. Test t2;
  20. t2.func(); //a=2 b=4
  21. return 0;
  22. }
复制代码

7.2 静态成员变量

       使用static修饰成员变量,这样的变量就是静态成员变量。

       静态成员变量需要在类内声明,类外定义。

       一个类的所有对象共用一份静态成员变量,虽然静态成员变量可以使用对象调用,但是更建议直接使用类名::调用。静态成员变量可以脱离对象使用,在程序运行时就开辟内存空间,直到程序运行结束后销毁。

  1. #include <iostream>
  2. using namespace std;
  3. class Test
  4. {
  5. public:
  6. int a = 1;
  7. // static int b = 2; // 错误,静态成员变量需要类内声明。类外初始化
  8. static int b;
  9. };
  10. int Test::b = 1;
  11. int main()
  12. {
  13. cout << Test::b << " " << &Test::b << endl;
  14. Test t1;
  15. cout << ++t1.a << " " << &t1.a << endl; // 2
  16. cout << ++t1.b << " " << &t1.b << endl; // 2
  17. cout << "------------------" << endl;
  18. Test t2;
  19. cout << ++t2.a << " " << &t2.a << endl; // 2
  20. cout << ++t2.b << " " << &t2.b << endl; // 3
  21. cout << Test::b << " " << &Test::b << endl;
  22. return 0;
  23. }
复制代码

7.3 静态成员函数

       使用static修饰的成员函数,这样的函数就是静态成员函数。

       可以通过类名直接调用,也可以通过对象调用(推荐使用类名直接调用);

      静态成员函数没有this指针,不能在静态成员函数中调用同类的非静态成员,但是静态成员函数,可以调用静态成员。

  1. #include <iostream>
  2. using namespace std;
  3. class Test
  4. {
  5. public:
  6. int a = 10;
  7. void func0()
  8. {
  9. cout << a << endl;
  10. // func1(); // 非静态成员函数可以调用静态成员函数
  11. cout << "非静态成员函数" << endl;
  12. }
  13. static void func1()
  14. {
  15. // func0(); // 错误 静态成员函数,无法直接调用非静态成员函数,因为没有this指针
  16. cout << "静态成员函数1" << endl;
  17. }
  18. static void func2()
  19. {
  20. func1();
  21. cout << "静态成员函数2" << endl;
  22. }
  23. };
  24. int main()
  25. {
  26. // Test::func1();
  27. Test t1;
  28. // t1.func0();
  29. // t1.func1();
  30. t1.func2();
  31. return 0;
  32. }
复制代码
【思考】如果要在静态成员函数中调用当前类的非静态成员,要如何实现?

1.通过传参

2.直接创建对象

  1. #include <iostream>
  2. using namespace std;
  3. class Test
  4. {
  5. public:
  6. int a = 10;
  7. void func0()
  8. {
  9. cout << a << endl;
  10. cout << "非静态成员函数" << endl;
  11. }
  12. static void func1(Test &t1) //传参
  13. {
  14. // Test t1; // 直接创建对象
  15. cout << "静态成员函数1" << endl;
  16. }
  17. static void func2()
  18. {
  19. cout << "静态成员函数2" << endl;
  20. }
  21. };
  22. int main()
  23. {
  24. Test t1;
  25. t1.func1(t1);
  26. return 0;
  27. }
复制代码

7.4 单例设计模式(了解)

       设计模式是一套被反复使用,多人知晓的,经过分类的,代码设计经验的总结。通常用到一些面向对象的语言中,如:C++、JAVA、C#等等

  1. #include <iostream>
  2. using namespace std;
  3. /**
  4. * @brief The Singleton class
  5. * 单例模式:只能创建一个对象(可以多次)
  6. */
  7. class Singleton
  8. {
  9. private:
  10. Singleton(){}
  11. Singleton(const Singleton&);
  12. static Singleton* instance; // 静态成员变量
  13. public:
  14. static Singleton* get_instance() // 静态成员函数
  15. {
  16. if(instance == NULL)
  17. instance = new Singleton;
  18. return instance;
  19. }
  20. static void delete_instance()
  21. {
  22. if(instance != NULL)
  23. {
  24. delete instance;
  25. instance = NULL;
  26. }
  27. }
  28. };
  29. Singleton* Singleton::instance = NULL;
  30. int main()
  31. {
  32. Singleton* s1 = Singleton::get_instance();
  33. Singleton* s2 = Singleton::get_instance();
  34. cout << s1 << endl;
  35. cout << s2 << endl;
  36. return 0;
  37. }
复制代码

8、  const关键字(掌握)

8.1 const修饰成员函数

const修饰的成员函数,表示常成员函数。

特性如下:

● 可以调用成员变量,但是不能修改成员变量的值。

● 不能调用非const的成员函数,哪怕这个函数并没有修改成员变量。

  1. #include <iostream>
  2. using namespace std;
  3. class Demo
  4. {
  5. private:
  6. int a;
  7. public:
  8. Demo(int a):a(a) {}
  9. void func0()
  10. {
  11. cout << "哈哈哈哈哈" << endl;
  12. }
  13. // 常成员函数
  14. int get_a() const
  15. {
  16. return a;
  17. }
  18. void test() const
  19. {
  20. // 错误 const修饰的成员函数,不能修改成员变量
  21. // a++;
  22. cout << a << endl;
  23. // func0(); 错误 const修饰的成员函数,不能调用非const修饰的成员函数
  24. get_a();
  25. }
  26. };
  27. int main()
  28. {
  29. Demo demo(1);
  30. cout << demo.get_a() << endl; // 1
  31. demo.func0();
  32. demo.test();
  33. return 0;
  34. }
复制代码

8.2 const修饰对象

       const修饰的对象被称为常量对象,这种对象的成员变量值无法被修改,也无法调用非const的成员函数。

  1. #include <iostream>
  2. using namespace std;
  3. class Demo
  4. {
  5. private:
  6. int a;
  7. public:
  8. int b = 10;
  9. Demo(int a):a(a)
  10. {
  11. }
  12. void func0()
  13. {
  14. cout << "哈哈哈哈哈" << endl;
  15. }
  16. // 常成员函数
  17. int get_a() const
  18. {
  19. return a;
  20. }
  21. void test() const
  22. {
  23. // 错误 const修饰的成员函数,不能修改成员变量
  24. // a++;
  25. cout << a << endl;
  26. // func0(); 错误 const修饰的成员函数,不能调用非const修饰的成员函数
  27. get_a();
  28. }
  29. };
  30. int main()
  31. {
  32. // 常对象
  33. const Demo demo(1);
  34. // Demo const demo(1); // 两种初始化的写法,等效于上一行
  35. cout << demo.get_a() << endl; // 1
  36. // demo.func0(); // 错误,const修饰的对象,无法调用非const修饰的成员函数
  37. demo.test();
  38. // demo.b = 10; // 错误 const修饰的对象,无法修改成员变量
  39. cout << demo.b << endl; // 可以调用但是无法修改
  40. return 0;
  41. }
复制代码

8.3 const修饰成员变量

const修饰的成员变量为常成员变量,表示该成员变量的值无法被修改。

常成员变量有两种初始化的方式:

● 直接赋值

● 构造初始化列表

上述两种初始化同时使用时,以构造构造初始化列表为准。

  1. #include <iostream>
  2. using namespace std;
  3. class Demo
  4. {
  5. private:
  6. const int a = 1;
  7. const int b = 2;
  8. const int c = 3;
  9. public:
  10. Demo(int a,int b,int c):a(a),b(b),c(c)
  11. {
  12. }
  13. void show()
  14. {
  15. cout << a << " " << b << " " << c << endl;
  16. }
  17. void test()
  18. {
  19. // a++;
  20. // b++;
  21. // c++;
  22. // a = 10;
  23. // b = 20;
  24. // c = 30;
  25. }
  26. };
  27. int main()
  28. {
  29. Demo d1(10,20,30);
  30. d1.show();
  31. return 0;
  32. }
复制代码

8.4 const修饰局部变量

const修饰局部变量,表示该局部变量不可被修改。

这种方式常用于引用参数。

  1. #include <iostream>
  2. using namespace std;
  3. class Demo
  4. {
  5. private:
  6. const int a = 1;
  7. const int b = 2;
  8. const int c = 3;
  9. public:
  10. Demo(int a,int b,int c):a(a),b(b),c(c)
  11. {
  12. }
  13. void show()
  14. {
  15. cout << a << " " << b << " " << c << endl;
  16. }
  17. void test(const int &f)
  18. {
  19. // const修饰局部变量
  20. const int e = 1;
  21. // e = 40; // 错误 const修饰局部变量不能被修改
  22. cout << e << endl;
  23. }
  24. };
  25. int main()
  26. {
  27. int a = 20;
  28. Demo d1(10,20,30);
  29. d1.show();
  30. d1.test(a);
  31. return 0;
  32. }
复制代码

四、运算符重载

1、  友元(熟悉)

1.1 概念

类主要实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,仅能够通过类的成员函数才能读写。如果数据成员定义成公共的,则又破坏了封装性,但是在某些情况下,需要频发的读写类数据成员,特别是对某些成员函数多次调用时,由于参数传递,类型检查、和安全性检查等等都需要时间开销,而影响程序的运行效率。

友元有三种实现方式:

● 友元函数

● 友元类

● 友元成员函数

友元函数是一种定义在类外部的普通函数,但他还需要再类体内进行声明,为了和该类的成员函数加以区分,再声明前面加一个关键字friend。友元函数不是成员函数,但是它能够访问类中的所有成员,包括私有成员。

友元在于提高程序的运行效率,但是它破坏了类的封装性和隐藏性,使得非成员函数也能够访问类的私有成员,导致程序维护性变差,因此使用友元要慎重。

1.2 友元函数

       友元函数不属于任何一个类,是一个类外的函数,但是再类内需要声明。虽然友元函数不是成员函数,但是却可以访问类中的所有成员(包括私有成员)。

  1. #include <iostream>
  2. using namespace std;
  3. class Test
  4. {
  5. private:
  6. int a;
  7. public:
  8. Test(int i):a(i){}
  9. void show()
  10. {
  11. cout << a << " " << &a << endl;
  12. }
  13. // 友元函数,类内声明
  14. friend void and_test(Test &t);
  15. };
  16. // 友元函数
  17. void and_test(Test &t)
  18. {
  19. cout << t.a << " " << &t.a << endl;
  20. }
  21. int main()
  22. {
  23. Test t1(1);
  24. and_test(t1); // 1 0x61fe8c
  25. t1.show(); // 1 0x61fe8c
  26. return 0;
  27. }
复制代码

友元函数的使用需要注意以下几点:

● 友元函数没有this指针

● 友元函数的声明,可以放在类中的任何位置,不受权限修饰符的影响。

● 一个友元函数可以访问多个类,只需要再各个类中分别“声明”,然后把对象传递进来。

1.3 友元类

       当一个类B成为了另一个类Test的朋友时,类Test的所有成员都可以被类B访问,此时类B就是类Test的友元类。

  1. #include <iostream>
  2. using namespace std;
  3. class Test
  4. {
  5. private:
  6. int a;
  7. public:
  8. Test(int i):a(i){}
  9. void show()
  10. {
  11. cout << a << " " << &a << endl;
  12. }
  13. // 友元类,类内声明
  14. friend class B;
  15. };
  16. class B
  17. {
  18. public:
  19. void and_test(Test &t)
  20. {
  21. cout << t.a << " " << &t.a << endl;
  22. }
  23. void and_test1(Test &t)
  24. {
  25. cout << t.a << " " << &t.a << endl;
  26. }
  27. void and_test2(Test &t)
  28. {
  29. cout << t.a << " " << &t.a << endl;
  30. }
  31. };
  32. int main()
  33. {
  34. Test t1(2);
  35. B b;
  36. b.and_test(t1); // 2 0x61fe8c
  37. b.and_test1(t1);
  38. b.and_test2(t1);
  39. t1.show(); // 2 0x61fe8c
  40. return 0;
  41. }
复制代码

友元类的使用需要注意以下几点:

● 友元关系不能被继承

● 友元关系不具有交换性(比如:类B声明为类Test的友元,类B可以访问类Test的成员,但是类Test不能访问类B的私有成员,需要需要访问,需要将类Test声明成类B的友元)

互为友元代码,需要类内声明,类外实现。

  1. #include <iostream>
  2. using namespace std;
  3. class Cat;
  4. class Test
  5. {
  6. private:
  7. int a;
  8. public:
  9. Test(int i):a(i){}
  10. void test(Cat &c);
  11. friend class Cat;
  12. };
  13. class Cat
  14. {
  15. private:
  16. int b;
  17. public:
  18. Cat(int i):b(i){}
  19. void test1(Test &t);
  20. friend class Test;
  21. };
  22. void Test::test(Cat &c)
  23. {
  24. cout <<c.b<<endl;
  25. }
  26. void Cat::test1(Test &t)
  27. {
  28. cout <<t.a++<<endl;
  29. }
  30. int main()
  31. {
  32. Test t(44);
  33. Cat c(12);
  34. c.test1(t);
  35. return 0;
  36. }
复制代码

1.4 友元成员函数

       使类B中的成员函数成为类Test的友元成员函数,这样类B的该成员函数就可以访问类Test的所有成员了。

  1. #include <iostream>
  2. using namespace std;
  3. // 第四步:声明被访问的类
  4. class Test;
  5. class B
  6. {
  7. public:
  8. // 第二步:声明友元成员函数(需要:类内声明。类外定义)
  9. void and_test(Test &t);
  10. };
  11. class Test
  12. {
  13. private:
  14. int a;
  15. public:
  16. Test(int i):a(i){}
  17. void show()
  18. {
  19. cout << a << " " << &a << endl;
  20. }
  21. // 友元成员函数,类内声明(第一步)
  22. friend void B::and_test(Test &t);
  23. };
  24. // 第三步:类外定义友元成员函数
  25. void B::and_test(Test &t)
  26. {
  27. cout << t.a << " " << &t.a << endl;
  28. }
  29. int main()
  30. {
  31. Test t1(2);
  32. B b;
  33. b.and_test(t1); // 2 0x61fe8c
  34. t1.show(); // 2 0x61fe8c
  35. return 0;
  36. }
复制代码

2、  运算符重载(掌握)

2.1 概念

C++中可以把部分运算符看做成函数,此时运算符也可以重载。

运算符预定义的操作只能针对基本数据类型,但是对于自定义类型,也需要类似的运算操作时,此时就可以重新定义这些运算符的功能,使其支持特定类型,完成特定的操作。

运算符重载有两种实现的方式:

● 友元函数运算符重载

● 成员函数运算符重载

2.2 友元函数运算符重载

  1. #include <iostream>
  2. using namespace std;
  3. class MyInt
  4. {
  5. private:
  6. int a;
  7. public:
  8. MyInt(int a):a(a){}
  9. int get_int()
  10. {
  11. return a;
  12. }
  13. // + 运算符重载
  14. friend MyInt operator +(MyInt &i,MyInt &i2);
  15. };
  16. MyInt operator +(MyInt &i,MyInt &i2)
  17. {
  18. // MyInt int4(0);
  19. // int4.a = i.a + i2.a;
  20. // return int4;
  21. // int → MyInt 触发隐式调用构造函数
  22. // MyInt n1 = 4;
  23. return i.a + i2.a;
  24. }
  25. int main()
  26. {
  27. MyInt int1(2);
  28. MyInt int2(int1); // 拷贝构造函数
  29. MyInt int3 = int1 + int2;
  30. cout << int3.get_int() << endl; // 4
  31. return 0;
  32. }
复制代码

++ 运算符重载:

  1. #include <iostream>
  2. using namespace std;
  3. class MyInt
  4. {
  5. private:
  6. int a;
  7. public:
  8. MyInt(int a):a(a){}
  9. int get_int()
  10. {
  11. return a;
  12. }
  13. // + 运算符重载
  14. friend MyInt operator +(MyInt &i,MyInt &i2);
  15. friend MyInt operator ++(MyInt &i); // 前置自增
  16. friend MyInt operator ++(MyInt &i,int); // 后置自增
  17. };
  18. MyInt operator +(MyInt &i,MyInt &i2)
  19. {
  20. return i.a + i2.a;
  21. }
  22. MyInt operator ++(MyInt &i)
  23. {
  24. return ++i.a;
  25. }
  26. MyInt operator ++(MyInt &i,int)
  27. {
  28. return i.a++;
  29. }
  30. int main()
  31. {
  32. MyInt int1(2);
  33. MyInt int2(int1); // 拷贝构造函数
  34. MyInt int3 = int1 + int2;
  35. cout << int3.get_int() << endl; // 4
  36. cout << (++int1).get_int() << endl; // 3
  37. cout << (int1++).get_int() << endl; // 3
  38. cout << int1.get_int() << endl; // 4
  39. return 0;
  40. }
复制代码

2.3 成员函数运算符重载

       成员函数运算符重载相比于友元函数运算符重载,最主要的区别在于,由于函数的第一个传入参数,在成员函数运算符重载中使用this指针代替,因此同样的运算符重载,成员函数比友元函数重载参数少一个。

  1. #include <iostream>
  2. using namespace std;
  3. class MyInt
  4. {
  5. private:
  6. int a;
  7. public:
  8. MyInt(int a):a(a){}
  9. int get_int()
  10. {
  11. return a;
  12. }
  13. // + 运算符重载
  14. MyInt operator +(MyInt &i2);
  15. MyInt operator ++(); // 前置自增
  16. MyInt operator ++(int); // 后置自增
  17. };
  18. MyInt MyInt::operator +(MyInt &i2)
  19. {
  20. return this->a + i2.a;
  21. }
  22. MyInt MyInt::operator ++()
  23. {
  24. return ++this->a;
  25. }
  26. MyInt MyInt::operator ++(int)
  27. {
  28. return this->a++;
  29. }
  30. int main()
  31. {
  32. MyInt int1(2);
  33. MyInt int2(int1); // 拷贝构造函数
  34. MyInt int3 = int1 + int2;
  35. cout << int3.get_int() << endl; // 4
  36. cout << (++int1).get_int() << endl; // 3
  37. cout << (int1++).get_int() << endl; // 3
  38. cout << int1.get_int() << endl; // 4
  39. return 0;
  40. }
复制代码

2.4 特殊运算符重载

2.4.1 赋值运算符重载

       除了之前学习的无参构造函数,拷贝构造函数与析构函数以外,如果程序员不收手写,编译器会给一个类添加赋值运算符重载函数。

  1. #include <iostream>
  2. using namespace std;
  3. class MyInt
  4. {
  5. private:
  6. int a;
  7. public:
  8. MyInt(int a):a(a){}
  9. int get_int()
  10. {
  11. return a;
  12. }
  13. // 编译器会自动添加赋值运算符重载函数
  14. MyInt &operator =(MyInt &i)
  15. {
  16. cout << "运算符重载函数被调用了" << endl;
  17. this->a = i.a;
  18. return *this;
  19. }
  20. // + 运算符重载
  21. MyInt operator +(MyInt &i2);
  22. MyInt operator ++(); // 前置自增
  23. MyInt operator ++(int); // 后置自增
  24. };
  25. MyInt MyInt::operator +(MyInt &i2)
  26. {
  27. return this->a + i2.a;
  28. }
  29. MyInt MyInt::operator ++()
  30. {
  31. return ++this->a;
  32. }
  33. MyInt MyInt::operator ++(int)
  34. {
  35. return this->a++;
  36. }
  37. int main()
  38. {
  39. MyInt int1(2);
  40. MyInt int2(int1); // 拷贝构造函数
  41. MyInt int3 = int1 + int2;
  42. int3 = int1;
  43. cout << int3.get_int() << endl;
  44. return 0;
  45. }
复制代码

       当类中出现指针类型的成员变量时,默认的赋值运算符重载函数类似于默认的浅拷贝构造函数,因此也需要手动编写解决”浅拷贝“的问题。

【面试题】一个类什么也不写,编译器添加了那些函数?

无参构造函数、拷贝构造函数、析构函数、赋值运算符重载函数

【面试题】一个类权限也不写,默认是什么权限?

默认为私有权限。

【面试题】一个空类的大小是多少大?

1个字节。

2.4.2 类型转换运算符重载

必须使用成员函数运算符重载,且格式比较特殊。

  1. #include <iostream>
  2. using namespace std;
  3. class MyInt
  4. {
  5. private:
  6. int a;
  7. public:
  8. MyInt(int a):a(a){}
  9. int get_int()
  10. {
  11. return a;
  12. }
  13. // 编译器会自动添加赋值运算符重载函数
  14. MyInt &operator =(MyInt &i)
  15. {
  16. cout << "运算符重载函数被调用了" << endl;
  17. this->a = i.a;
  18. return *this;
  19. }
  20. // + 运算符重载
  21. MyInt operator +(MyInt &i2);
  22. MyInt operator ++(); // 前置自增
  23. MyInt operator ++(int); // 后置自增
  24. // 类型转换运算符重载
  25. operator int()
  26. {
  27. return a;
  28. }
  29. operator string()
  30. {
  31. return "hello";
  32. }
  33. };
  34. MyInt MyInt::operator +(MyInt &i2)
  35. {
  36. return this->a + i2.a;
  37. }
  38. MyInt MyInt::operator ++()
  39. {
  40. return ++this->a;
  41. }
  42. MyInt MyInt::operator ++(int)
  43. {
  44. return this->a++;
  45. }
  46. int main()
  47. {
  48. MyInt int1(2);
  49. MyInt int2(int1); // 拷贝构造函数
  50. MyInt int3 = int1; // 拷贝
  51. int2 = int3; // 赋值运算符重载
  52. int a1 = int2;
  53. cout << a1 << endl; // 2
  54. string s2 = int3;
  55. cout << s2 << endl; // hello
  56. return 0;
  57. }
复制代码

2.5 注意事项

● 重载的运算符限制在C++语言中已有的运算符范围,即不能创建新的运算符。

● 运算符重载的本质上也是函数重载,但是不支持函数参数默认值的设定。

● 重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符的操作数和语法结构。

● 运算符重载必须基于或者包含自定义类型,不能改变基本数据类的运算规则。

● 重载的功能应该与原有的功能相似,避免没有目的的滥用运算符重载

● 一般情况下,双目运算符建议使用友元函数重载,单目运算符建议使用成员函数运算符重载。

3、  std::string 字符串类(熟悉)

字符串对象是一种特殊类型的容器,专门设计用于操作字符串。

  1. #include <iostream>
  2. #include <string.h>
  3. using namespace std;
  4. int main()
  5. {
  6. string s; // 创建一个空的字符串
  7. // 判断是否为空
  8. cout << s.empty() << endl; // 1
  9. // 隐式调用构造函数
  10. string s1 = "hello";
  11. cout << s1 << endl; // hello
  12. // 构造函数显式调用
  13. string s2("world");
  14. cout << s2 << endl; // world
  15. // ==、!=、>、< 判断编码
  16. cout << (s1 == s2) << endl; // 0
  17. cout << (s1 != s2) << endl; // 1
  18. cout << (s1 > s2) << endl; // 0
  19. cout << (s1 < s2) << endl; // 1
  20. // 拷贝构造函数
  21. string s3(s2);
  22. cout << s3 << endl; // world
  23. // 参数1:char* 源字符串
  24. // 参数2:保留的字符数
  25. string s4("ABCDEFG",3);
  26. cout << s4 << endl; // ABC
  27. // 参数1:std::string 源字符串
  28. // 参数2:不保留的字符数,从头开始
  29. string s5(s2,3);
  30. cout << s5 << endl; // ld
  31. // 参数1:字符数量
  32. // 参数2:字符内容
  33. string s6(5,'a');
  34. cout << s6 << endl; // aaaaa
  35. // 交换
  36. cout << "s5 = " << s5 << " " << "s6 = " << s6 << endl;
  37. swap(s5,s6); // 交换后:aaaaa ld
  38. cout << "s5 = " << s5 << " " << "s6 = " << s6 << endl;
  39. // 字符串拼接
  40. string s7 = s5 + s6;
  41. cout << s7 << endl; // aaaaald
  42. // 向后追加字符串
  43. s7.append("jiajia");
  44. cout << s7 << endl; // aaaaaldjiajia
  45. // 向后追加单字符
  46. s7.push_back('s');
  47. cout << s7 << endl; // aaaaaldjiajias
  48. // 插入
  49. // 参数1:插入的位置
  50. // 参数2:插入的内容
  51. s7.insert(1,"234");
  52. cout << s7 << endl; // a234aaaaldjiajias
  53. // 删除字符串
  54. // 参数1:起始位置
  55. // 参数2:删除的字符数量
  56. s7.erase(2,5);
  57. cout << s7 << endl; // a2aldjiajias
  58. // 替换
  59. // 参数1:起始位置
  60. // 参数2:被替换的字符数
  61. // 参数3:替换的新内容
  62. s7.replace(0,3,"***");
  63. cout << s7 << endl; // ***ldjiajias
  64. // 清空
  65. s7.clear();
  66. cout << s7.length() << endl; // 0
  67. // 直接赋值初始化
  68. string s8 = "hahaha";
  69. cout << s8 << endl;
  70. // 重新赋值
  71. s8 = "ABCDEFGH";
  72. cout << s8 << endl; // ABCDEFGH
  73. // 参数1:拷贝的目标
  74. // 参数2:拷贝的字符数量
  75. // 参数3:拷贝的起始位置
  76. // C++的string 到 C语言的string也就是字符数组
  77. char arr[20] = {0};
  78. s8.copy(arr,6,1);
  79. cout << arr << endl; // BCDEFG
  80. // C++string 到C string 用到了C语言中strcpy
  81. // c_str C++的字符串转换成C语言的字符数组
  82. // c_str 返回一个const char *
  83. char c[20] = {0};
  84. strcpy(c,s8.c_str());
  85. cout << c << endl; // ABCDEFGH
  86. return 0;
  87. }
复制代码

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Honkers

荣誉红客

关注
  • 4008
    主题
  • 36
    粉丝
  • 0
    关注
这家伙很懒,什么都没留下!

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2025 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行