1.初识
1.1第一个C++程序
1 2 3 4 5 6 7 8 #include <iostream> using namespace std;int main () { cout <<"hello world" << endl; system ("pause" ); return 0 ; }
system("pause")
会让程序暂停:
或者:
1 2 3 4 #include <iostream> int main () { std::cout << "hello world" << std::endl; }
main是一个程序的入口,每个程序都必须有这么一个函数,有且仅有一个
1.2注释
单行注释://内容
多行注释:/*内容*/
,通常放在一段代码上面
1.3变量
给内存 空间起名,方便操作(不然只能使用内存编码来取值了)
数据类型 变量名=初始值
输出变量的值 cout << "a=" << a << endl;
1 2 3 4 5 6 7 #include <iostream> using namespace std;int main () { int a = 10 ; cout <<"a=" << a << endl; return 0 ; }
1.4常量
用于记录程序中不可更改的数据
#define
宏常量:#define 常量名 常量值
通常在文件上方(#上面)定义,表示一个常量值
const
修饰的量:const 数据类型 常量名=值
1 2 3 4 5 6 7 8 9 10 #define PI 3.14 #include <iostream> using namespace std;int main () { const int h = 4 ; int a = 10 ; cout <<"面积=" << a * PI << endl; cout << "体积=" << a * PI * h << endl; return 0 ; }
修改常量会报错
1.5关键字
1.6标识符命名规则
标识符不能是关键字
标识符只能由字母、数字、下划线 组成
第一个字符必须为字母或下划线
标识符中字母区分大小写
2.数据类型
给数据分配合适的内存空间
不指定数据类型,无法分配内存
2.1整型
## 2.2关键字sizeof
统计数据类型所占内存大小`sizeof(数据类型 或者 变量)`
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #define PI 3.14 #include <iostream> using namespace std;int main () { const int h = 4 ; short a = 10 ; int b = 10 ; long c = 10 ; long long d = 10 ; cout << "宏常量所占空间为" << sizeof (PI) << endl; cout << "const常量所占空间为" << sizeof (h) << endl; cout << "short变量所占空间为" << sizeof (a) << endl; cout << "int变量所占空间为" << sizeof (b) << endl; cout << "long变量所占空间为" << sizeof (c) << endl; cout << "long long变量所占空间为" << sizeof (d) << endl; return 0 ; }
2.3实型(浮点型)
区别是有效数字范围不同
浮点数默认为double,float类型变量数字后加f
3.14
算3位有效数字,默认情况下,小数最多只显示6位有效数字
1 2 3 4 5 6 7 8 9 10 #include <iostream> using namespace std;int main () { const int h = 4 ; float f = 3.14494138f ; double d = 3.14494138 ; cout << "f =" << f << endl; cout << "d =" << d << endl; return 0 ; }
2.4科学计数法e
eN
:表示10的N次方(可正可负)
1 2 3 4 5 6 7 8 9 10 11 #include <iostream> using namespace std;int main () { float a = 3e2f ; float b = 3e-2 ; long long c = 10e2 ; cout << "a =" << a << endl; cout << "b =" << b << endl; cout << "c =" << c << endl; return 0 ; }
2.5字符型char
char 变量名='内容'
记住单引号,单引号内只有1个字符
C和C++的字符型都只有1字节,不是存字符本身,而是存ASCII编码
查看ASCII编码:(int)字符
1 2 3 4 5 6 7 8 #include <iostream> using namespace std;int main () { char ch = 'a' ; cout << "ch的ASCII码为" << (int )ch << endl; cout << "ch的占用内存大小" << sizeof (ch) << endl; return 0 ; }
可以直接用ASCII给字符型赋值
1 2 3 4 5 6 7 8 9 #include <iostream> using namespace std;int main () { char ch1 = 'a' ; char ch2 = 98 ; cout << "ch1的ASCII码为" << (int )ch1 << endl; cout << "ch2=" << ch2 << endl; return 0 ; }
A=65;a=65
2.6转义字符
不能显示出来的ASCII字符
单个\
无法输出
\t
:加上前面一共占8位,对齐效果
2.7字符串型char[]或string
方式1:C语言风格char 变量名[]="字符串值"
,注意与java不同,[]在变量名后面
方式2:C++风格string 变量名="字符串值"
,string要包含#include <string>
头文件
只有双引号
1 2 3 4 5 6 7 8 9 #include <iostream> using namespace std;int main () { char str1[] = "hello world1" ; string str2 = "hello world2" ; cout << "str1=" << str1 << endl; cout << "str1=" << str2 << endl; return 0 ; }
2.8布尔类型bool
true (1)或者false (0),bool类型只有1个字节
1 2 bool flag=true ;cout<<flag<<endl;
非0的数字都能代表真,0代表假
bool类型赋值用数字:只要非0都为真
1 2 3 4 5 6 7 #include <iostream> using namespace std;int main () { bool flag = 3 ; cout << "flag=" << flag << endl; return 0 ; }
2.9数据的输入cin
从键盘获取数据cin>>变量
1 2 3 4 5 6 7 8 9 #include <iostream> using namespace std;int main () { int a; cout << "请输入a的值=" ; cin >> a; cout << "a=" << a << endl; return 0 ; }
2.10输出cout
换行输出:cout << "内容" << endl;
不换行输出:cout << "内容" ;
只换行:cout<<endl;
3.运算符
3.1算术运算符
1 2 3 int a=1 ;int b=2 ;cout << a/b << endl;
两个整数相除,结果依然为整数,将小数部分直接去除
除数不能为0
两个小数可以相除
1 2 cout << "10÷20=" << 10 /20 << endl;
取模%本质上是求余数:两个小数不能取模,只有整型变量可以取模 ,A%B
:B也不能为0
3.2赋值运算符
前置递增:先让变量+1,然后进行表达式运算
后置递增:先进行表达式运算,后让变量+1
3.3比较运算符
记住括号
3.4逻辑运算符
C++里,除了0都为真
没有&
和|
,记住括号
1 2 3 4 5 6 7 8 int a = 10 ;int b = 2 ;int c = 0 ;cout << "!a=" << !a << endl; cout << "a&&b=" << (a && b )<< endl; cout << "a && c=" << (a && c )<< endl; cout << "a || c=" << (a || c) << endl; cout << "a || b=" << (a || b) << endl;
4.流程控制语句
4.1选择结构
if语句
单行:if(条件){条件满足执行语句}
多行:
1 2 if (条件) {条件满足执行语句} else {条件不满足执行语句}
多条件:
1 2 3 4 if (){} elseif (){}...... else {都不满足}
嵌套:更精准判断
if后面不要加分号,if条件后加分号if();
,后面的语句什么情况都会执行
1 2 3 4 int score;cout << "请输入分数:" << endl; cin >> score; if (score > 60 ) cout << "及格了" << endl;
3数比较:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> using namespace std;int main () { int a, b, c; int max; cout << "请输入小猪a的体重" << endl; cin >> a; cout << "请输入小猪b的体重" << endl; cin >> b; cout << "请输入小猪c的体重" << endl; cin >> c; if (a > b){ max = a; if (a > c) max = c; } else { max = b; if (c > b) max = b; } cout << "最重的体重为" << max << endl; }
三目运算符
格式:表达式 ?为真执行语句:为假执行语句
将后面的执行语句作为返回值返回
在C++中,返回的是变量,可以继续赋值
1 2 3 4 5 6 7 8 #include <iostream> using namespace std;int main () { int a = 10 ; int b = 20 ; int c = (a > b) ? a : b; cout << "c=" << c << endl; }
在C++中,返回的是变量,可以继续赋值
1 2 3 (a > b) ? a : b = 100 ; cout << "a=" << a << endl; cout << "b=" << b << endl;
switch语句
执行多条件分支语句,只能判断整型和字符型
执行效率高
1 2 3 4 5 6 7 8 9 10 11 12 13 switch (表达式){ case 结果1 : 执行语句; break ; case 结果2 : 执行语句; break ; …… default : 执行语句; break ; }
电影评分:
10-9:经典
8-7:非常好
6-5:一般
5以下:烂片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 case 10 :case 9 : cout << "经典" << endl; break ; case 8 :case 7 : cout << "非常好" << endl; break ; case 6 :case 5 : cout << "一般" << endl; break ; default : cout << "烂片" << endl; break ;
注意
1: switch语句中,表达式类型只能是`整型或者字符型
2: case里如果没有break,那么程序会一直向下执行
总结:
与if语句比,对于多条件判断时,switch的结构清晰,执行效率高,缺点是switch不可以判断区间
4.2循环
避免死循环
while循环(随机数)
while(循环条件){循环结构}
屏幕打印0~9:
1 2 3 4 5 6 int num = 0 ;while (num < 10 ){ cout << num << endl; num++; }
猜数字游戏:系统随机生成一个1~100之间的数字,玩家猜测,猜错提示过大或过小,猜对提示正确,并且退出游戏
生成随机数rand()
,生成N个随机数rand()%N
生成100个随机数rand()%100
:0到99
生成随机各位数:令a=rand()%10
,生成几位数就除以几位数然后取余
生成范围在A-B之间的随机数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <cstdlib> #include <ctime> int main () { int A = 10 ; int B = 20 ; std::srand (std::time (0 )); int random_number = A + (std::rand () % (B - A + 1 )); std::cout << "随机数: " << random_number << std::endl; return 0 ; }
添加随机数种子,利用当前系统时间生成随机数,防止每次随机数都一样
1 2 srand ((unsigned int )time (NULL ));int ra = rand () % 100 +1 ;
do…while
会先执行一次循环语句do{循环语句} while(循环条件);
注意分号
1 2 3 4 5 6 int num = 0 ;do { cout << num << endl; num++; } while (num<10 );
水仙花数:一个3位数,每位上的3次幂之和等于这个数,求出所有水仙花数
1 2 3 4 5 6 7 8 9 10 11 int num = 100 ;int ge, shi, bai;do { ge = num % 10 ; shi = (num % 100 ) / 10 ; bai = num / 100 ; if ((ge * ge * ge) + (shi * shi * shi) + (bai * bai * bai) == num) cout << num << endl; num++; } while (num < 1000 );
结果:153、370、371、407
for循环
格式:for(){}
1 2 for (int num = 0 ; num < 10 ; num++) cout << num << endl;
敲桌子游戏:0~100,十位或个位含7,或者7的倍数,打印“敲桌子”,否则打印数字
1 2 3 4 5 6 7 8 for (int num = 1 ; num <= 100 ;num++){ int ge, shi; ge = num % 10 ; shi = num / 10 % 10 ; if (num % 7 == 0 || ge == 7 || shi == 7 ) cout << "敲桌子" << endl; else cout << num << endl; }
嵌套循环:10×10矩阵
1 2 3 4 5 6 7 8 for (int i = 0 ; i < 10 ; i++){ for (int j = 0 ; j < 10 ; j++) { cout << "* " ; } cout << "\n" ; }
换行 还可以:cout<<endl;
99乘法表:
1 2 3 4 5 6 7 8 for (int i = 1 ;i < 10 ; i++){ for (int j = 1 ; j <= i; j++) { cout << j << "*" << i << "=" << j * i << "\t" ; } cout << endl; }
4.3跳转语句
break
出现在switch条件语句中,作用是终止case并跳出switch出现在循环语句中,作用是跳出当前的循环语句出现在嵌套循环中,跳出最近的内层循环语句
continue
在循环语句中跳过本次循环,继续执行下一次循环
goto
可以无条件跳转
语法:goto 标记;
入伙标记的名称存在,执行到goto语句时,会跳转到标记的位置
结果为:1、5
不推荐:来回跳转看不明白,影响结构
5.数组
连续内存,每个元素数据类型相同
通过下标访问(从0开始)
5.1一维数组
定义
方式1:数据类型 数组名[数组长度];
方式2:数据类型 数组名[数组长度]={值1,值2,值3......};
方式3:数据类型 数组名[]={值1,值2,值3......};
初始化数组时没有填完,会用0补充剩余数据,定义时必须有初始长度
数组名作用:统计数组长度、获取数组首地址
取址符号&:取数组第一个元素地址
数组名是常量,不可以赋值arr=100
求数组里最大的数:
数组元素逆置:
想象2个指针。一个从0向右移动++
,一个从末尾向左移动--
1 2 3 4 5 6 7 8 9 10 11 12 13 int arr[5 ] = {1 ,2 ,3 ,4 ,5 };int t;int start = 0 ; int end = sizeof (arr) / sizeof (arr[0 ])-1 ;while (start<end) { t = arr[start]; arr[start] = arr[end]; arr[end] = t; start++; end--; } for (int i = 0 ; i < 5 ; i++) cout << arr[i] << endl;
冒泡排序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int arr[10 ] = {4 ,2 ,8 ,0 ,5 ,7 ,1 ,3 ,9 ,11 };int t;for (int i = 0 ; i < 10 ; i++){ for (int j = i+1 ; j < 10 ; j++) { if (arr[j] < arr[i]) { t = arr[j]; arr[j] = arr[i]; arr[i] = t; } } } for (int i = 0 ; i < 10 ; i++) cout << arr[i] << " " ;
5.2二维数组
方式1:数据类型 数组名[行数][列数]
方式2:数据类型 数组名[行数][列数]={ {数据1,数据2},{数据3,数据4} }
注意里面是逗号不是分号
方式3:数据类型 数组名[行数][列数]={数据1,数据2,数据3,数据4}
方式4:数据类型 数组名[ ][列数]={数据1,数据2,数据3,数据4}
1 2 3 4 5 6 7 8 9 10 11 12 方式1 : int arr1[2 ][3 ];arr1[0 ][0 ]=0 ; arr1[0 ][1 ]=1 ; arr1[0 ][2 ]=2 ; 方式2 : int arr2[2 ][3 ]={ {1 ,2 ,3 }, {4 ,5 ,6 } };
1 2 3 count << "二维数组占用的内存空间为:" << sizeof (arr) <<endl; count << "二维数组第一行占用的内存空间为:" << sizeof (arr[0 ]) <<endl; count << "二维数组第一行占用的内存空间为:" << sizeof (arr[0 ][0 ]) <<endl;
6.函数
6.1定义
语法:返回值类型 函数名(参数列表){函数体; return表达式;}
不能写到main函数里面
6.2调用
语法:函数名(参数)
6.3值传递
形参发生不会影响实参
6.4声明
函数的声明可以多次,但定义只有1次
记住分号
1 2 3 4 5 6 7 8 9 10 11 12 13 int max (int a, int b) ;int max (int a, int b) ;void main () { cout << max (1 ,8 ) << endl; } int max (int a, int b) { return a > b ? a : b; }
当函数定义在main函数后面时,可以先在前面添加声明防止报错(2022版本已经不用了 )
6.5函数的分文件编写
函数分文件编写一般有4个步骤
1.创建后缀名为.h
的头文件
2.创建后缀名为.cpp
的源文件
3.在头文件中写函数的声明
4.在源文件中写的数的定义
步骤图片
让两者关联起来:cpp文件添加#include "头文件"
双引号表示自定义头文件
如果函数里面有cout等,要在自定义头文件里面加上#include <iostream>
7.指针
作用:通过指针间接访问内存
内存编号是从0开始记录的,一般用16进制表示
可以用指针变量保存地址
7.1定义
语法:数据类型 *变量名
与变量概念进行区分
假设int a=10
,a这块内存保存10这个数,地址为0x0000,int *p
,p指针可以用来保存a的地址,即保存0x0000
7.2使用、解引用
让指针记录a的地址p=&a;
1 2 3 4 5 int a = 10 ;int * p;p = &a; cout << "a的地址=" << &a << endl; cout << "p的值=" << p << endl;
可以通过解引用(指针前面加*
)的方式找到指针指向的内存:*p表示找到指针P指向的数据(即变量a)
7.3指针所占内存空间
除了char指针4字节,其余8字节
7.4空指针
指针变量指向内存编号为0的空间
用途:初始化指针变量
空指针指向的内存空间不可以访问
只要*p
的操作都不可以
0~255是系统占用内存,都不可以访问
7.5野指针
指针变量指向非法的内存空间
int* p = (int *)0x1100;
0x1100是一个16进制数,通过int*
强制转换为地址,没有权限操纵地址里面的数,因为没有申请
输出地址里面的内容,编译报错:
在程序中,尽量避免出现野指针
空指针和野指针都不是我们申请的
7.6修饰指针const
const修饰指针:常量指针
const修饰常量:指针常量
const既修饰指针,又修饰常量
7.7指针和数组
利用指针访问数组元素int *p=arr;
arr就是数组首地址
p++;
往后移
7.8指针和函数
利用指针作为函数参数,可以修改实参的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 void change1 (int a, int b) { int t = a; a = b; b = t; cout << "change1函数内:" << endl; cout << "a=" << a << "\t" ; cout << "b=" << b << endl; } void change2 (int * p1, int * p2) { int t = *p1; *p1 = *p2; *p2 = t; cout << "change2函数内:" << endl; cout << "a=" << *p1<< "\t" ; cout << "b=" << *p2 <<endl; } void main () { int a = 10 ; int b = 30 ; change1 (a, b); cout << "change1函数执行完后:" << endl; cout << "a=" << a << "\t" ; cout << "b=" << b << endl; change2 (&a, &b); cout << "change2函数执行完后:" << endl; cout << "a=" << a << "\t" ; cout << "b=" << b << endl; }
第二个传的地址,故函数外部值也交换了
封装一个函数,利用冒泡排序,实现对整型数组的升序排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void order (int *arr,int length) { int i, j; for (i = 0 ; i < length; i++) { for (j = i+1 ; j < length; j++) { if (arr[i] > arr[j]) { int t = arr[i]; arr[i] = arr[j]; arr[j] = t; } } } } void main () { int arr[] = {4 ,3 ,6 ,9 ,1 ,2 ,10 ,8 ,7 ,5 }; order (arr, 10 ); for (int i = 0 ; i < 10 ; i++) cout << arr[i] << endl; }
8.结构体
结构体属于用户自定义数据类型,允许用户存储不同的数据类型
8.1定义
语法:struct 结构体名{结构体成员列表};
放main外面
1 2 3 4 5 6 7 8 struct student { string name; int age; int score; };
通过结构体创建变量 :
方式1:struct 结构体名 变量名
方式2:struct 结构体名 变量名={成员1值,成员2值}
方式3:定义结构体时顺便创建变量
通过.
访问结构体中属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct student s1;s1. name = "zhangsan" ; s1. age = 19 ; s1. score = 79 ; struct student s2 = {"李四" ,18 ,90 };struct student { string name; int age; int score; }s3;
创建变量时struct关键字可以省略:student s1;
定义时不能省略
8.2结构体数组
将自定义的结构体放入到数组中方便维护
语法:
1 struct 结构体名 数组名[元素个数]={{},{},{}……};
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 struct student { string name; int age; int score; }; void main () { struct student arr_s[5 ] = { {"zhangsan" ,19 ,77 }, {"lisi" ,18 ,86 }, {"wangwu" ,20 ,90 } }; int length = sizeof (arr_s)/sizeof (arr_s[0 ]); for (int i = 0 ; i < length; i++) { cout << "name=" << arr_s[i].name << "\tage=" << arr_s[i].age << "\tscore=" << arr_s[i].score << endl; } }
8.3结构体指针
通过指针访问架构体中成员
通过结构体指针访问结构体属性 ->
例如:p ->age = 20;
不是*p->age;
注意指针P的数据类型不是int * P
8.4结构体嵌套
多层嵌套外层.内层.内层的属性
t.stu.name = "小明";
老师教的学生姓名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 struct student { string name; int age; int score; }; struct teacher { string name; int age; int id; student stu; }; void main () { teacher t; t.id = 10000 ; t.name = "王老师" ; t.stu.name = "小明" ; }
8.5结构体做函数参数
值传递:返回值类型 函数名(结构体类型 结构体名){}
地址传递:将函数中的形参改为指针,可以减少内存空间(4字节),而且不会复制新的副本出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 struct student { string name; int age; int score; }; void PrintStudent1 (student stu) { cout << "学生姓名:" << stu.name << "\t学生年龄:" << stu.age << "\t学生成绩:" << stu.score << endl; } void PrintStudent2 (student *p) { cout << "学生姓名:" << p->name << "\t学生年龄:" << p->age << "\t学生成绩:" << p->score << endl; } void main () { student s={"张三" ,19 ,89 }; PrintStudent1 (s); PrintStudent2 (&s); }
值传递中,形参改变,实参不变
8.6结构体中const的使用
用const防止修改误操作
1 2 3 4 5 void PrintStudent1 (const student stu) { stu.age = 20 ; }
void PrintStudent1(const student *p)
:常量指针不能改值p->age=20;
,可以改指向
9.内存分区模型
C++程序在执行时,将内存大方向划分为4个区域
代码区∶存放函数体的二进制代码,由操作系统进行管理
全局区:存放全局变量和静态变量以及常量
栈区︰由编译器自动分配释放,存放函数的参数值,局部变量等
堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
不同区域的数据拥有不同生命周期
9.1程序运行前
在程序编译后,生成了exe可执行程序 ,未执行该程序前 分为两个区域
代码区:
存放CPU执行的机器指令
代码区是共享 的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
代码区是只读 的,使其只读的原因是防止程序意外地修改了它的指令
全局区:
全局变量和静态变量存放在此
全局区还包含了常量区,字符串常量和其他常量(const修饰的变量)也存放在此
该区域的数据在程序结束后由操作系统释放
全局变量:定义在文件里面,main函数外面
1 2 #include <iostream> int a = 10 ;
静态变量:在普通变量前面加static:static int a=10;
## 9.2程序运行后
栈区:
由编译器自动分配释放,存放函数的参数值,局部变量等
注意事项:不要返回局部变量的地址,因为栈区开辟的数据由编译器自动释放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int * func () { int a = 10 ; return &a; } int main () { int *p = func (); cout << *p << endl; cout << *p << endl; return 0 ; }
堆区:
由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
在C++中主要利用new在堆区开辟内存
1 2 3 4 5 6 7 8 9 10 11 12 13 int * func () { int * a = new int (10 ); return a; } int main () { int *p = func (); cout << *p << endl; cout << *p << endl; return 0 ; }
堆区数据由程序员管理开辟和释放
堆区数据利用new关键字进行开辟内存
9.3关键字new
堆区开辟的数据,由程序员手动开辟,手动释放
开辟: new 数据类型(初始值)
,利用new创建的数据,会返回该数据对应的类型的指针
释放:,释放利用操作符delete
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int * func () { int * a = new int (10 ); return a; } int main () { int *p = func (); cout << *p << endl; cout << *p << endl; delete p; return 0 ; }
堆区开辟数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void main () { int * arr = new int [10 ]; for (int i = 0 ; i < 10 ; i++) { arr[i] = i + 100 ; } for (int i = 0 ; i < 10 ; i++) { cout << arr[i] << endl; } delete [] arr; system ("pause" ); }
new int[10]
代表创建10个int型元素,返回其地址,用int型指针接受,名称arr不重要
释放数组时要加中括号 :delete[] arr;
PS:int* arr = int[10];
并不能创建数组
10.引用(难)
10.1基本使用
给变量起别名
语法:数据类型 &别名=原名
别名可以和本名一样:int &a=a;
1 2 3 4 5 6 7 8 9 10 11 12 void main () { int a = 10 ; int & b = a; cout << "a = " << a << endl; cout << "b = " << b << endl; b = 100 ; cout << "a = " << a << endl; cout << "b = " << b << endl; }
变量本质是内存,通过(原名、引用)改变数据都是操纵 了内存里面存储的数据
10.2注意事项
1 2 3 4 5 6 7 8 9 10 11 12 void main () { int a = 10 ; int b = 20 ; int & c = a; c = b; cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; }
int &c;
错误,引用必须初始化
int& c = a;
一旦初始化后,就不可以更改
c = b;
这是赋值操作,不是更改引用
10.3引用做函数参数
函数传参时,可以利用引用的技术让形参修饰实参 可以简化指针修改实参
值传递 :
1 2 3 4 5 6 7 8 9 10 11 void mySwap01 (int a, int b) { int temp = a; a = b; b = temp; } void main () { int a = 10 ; int b = 20 ; mySwap01 (a, b); cout << "a:" << a << " b:" << b << endl; }
地址传递 :
1 2 3 4 5 6 7 8 9 10 11 void mySwap02 (int * a, int * b) { int temp = *a; *a = *b; *b = temp; } void main () { int a = 10 ; int b = 20 ; mySwap02 (&a, &b); cout << "a:" << a << " b:" << b << endl; }
引用传递 :
1 2 3 4 5 6 7 8 9 10 11 12 void mySwap03 (int & a, int & b) { int temp = a; a = b; b = temp; } void main () { int a = 10 ; int b = 20 ; mySwap03 (a, b); cout << "a:" << a << " b:" << b << endl; }
结果:
a:10 b:20:值传递,形参不会修饰实参
a:20 b:10:地址传递,形参会修饰实参
a:20 b:10:引用传递,形参会修饰实参
总结:通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单
10.4引用做函数返回值
不要返回局部变量引用
如果函数的返回值是一个引用,这个函数调用可以作为左值(等号左边)test02()=1000;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 int & test01 () { int a = 10 ; return a; } int & test02 () { static int a = 20 ; return a; } void main () { int & ref = test01 (); cout << "ref = " << ref << endl; cout << "ref = " << ref << endl; int & ref2 = test02 (); cout << "ref2 = " << ref2 << endl; cout << "ref2 = " << ref2 << endl; test02 () = 1000 ; cout << "ref2 = " << ref2 << endl; cout << "ref2 = " << ref2 << endl; }
总结:
不能返回局部变量的引用
如果函数做左值,那么必须返回引用
10.5引用的本质
在C++里,引用的本质是一个指针常量:详细
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void func (int & ref) { ref = 100 ; } void main () { int a = 10 ; int & ref = a; ref = 20 ; cout << "a:" << a << endl; cout << "ref:" << ref << endl; func (a); cout << "a:" << a << endl; cout << "ref:" << ref << endl; }
10.6常量引用
常量引用主要用来修饰形参,防止误操作
在函数形参列表中,可以加const修饰形参 ,防止形参改变实参
引用必须引一块合法的内存空间 :
1 2 3 4 int a=10 ;int &ref =a;int &ref=10 ;
加入const后:const int& ref = 10;
可以了,编译器加入临时值,自动优化代码:
1 2 int temp = 10 ; const int & ref = temp;
加入const后不可以修改变量:ref = 100
报错
函数中利用常量引用防止误操作修改实参:引用通常用来修饰形参
1 2 3 4 5 6 7 8 9 void showValue (const int & v) { cout << v << endl; } void main () { int a = 10 ; showValue (a); }
修饰的是函数里面的形参而非主函数里面的a变量本身
11.函数提高
11.1函数默认参数
在C++中,函数的形参列表中的形参是可以有默认值的。
语法: 返回值类型 函数名 (参数= 默认值){}
如果自己传入了数据,就用传入的数据做实参,否则使用默认值
如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
如果函数声明有默认值,函数实现的时候就不能有默认参数
1 2 3 4 5 6 7 8 9 10 11 12 13 int func (int a, int b = 10 , int c = 10 ) { return a + b + c; } int func2 (int a = 10 , int b = 10 ) ;int func2 (int a, int b) { return a + b; } void main () { cout << "ret = " << func (20 , 20 ) << endl; cout << "ret = " << func (100 ) << endl; }
11.2函数占位参数
函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
语法:返回值类型 函数名 (数据类型){}
1 2 3 4 5 6 7 8 9 void func (int a, int ) { cout << "this is func" << endl; } void main () { func (10 , 10 ); }
11.3函数重载
函数名可以相同,提高复用性
必须同一个作用域下
函数名称相同
函数参数类型不同 或者 个数不同 或者 顺序不同
函数的返回值不可以作为函数重载的条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void func () { cout << "func 的调用!" << endl; } void func (int a) { cout << "func (int a) 的调用!" << endl; } void func (double a) { cout << "func (double a)的调用!" << endl; } void func (int a ,double b) { cout << "func (int a ,double b) 的调用!" << endl; } void func (double a ,int b) { cout << "func (double a ,int b)的调用!" << endl; }
1.重载+引用
2.重载+默认参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 void func (int & a) { cout << "func (int &a) 调用 " << endl; } void func (const int & a) { cout << "func (const int &a) 调用 " << endl; } void func2 (int a, int b = 10 ) { cout << "func2(int a, int b = 10) 调用" << endl; } void func2 (int a) { cout << "func2(int a) 调用" << endl; } void main () { int a = 10 ; func (a); func (10 ); }
12.封装
C++面向对象的三大特性为:封装、继承、多态
C++认为万事万物都皆为对象,对象上有其属性和行为
将属性和行为作为一个整体,表现生活中的事物
将属性和行为加以权限控制
语法:class 类名{ 访问权限: 属性 / 行为 };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const double pi=3.14 ;class circle { public : int r; void c () { cout<<"圆周长为:" << 2 * pi * this ->r<<endl; } }; void main () { circle c; c.r = 4 ; c.c (); }
注意public权限与实例化
类在设计时,可以把属性和行为放在不同的权限下,加以控制。
访问权限有三种:
public:公共权限,类内可以访问 类外可以访问
protected:保护权限,类内可以访问 类外不可以访问,子类可以访问
private:私有权限,类内可以访问 类外不可以访问,子类不可以访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Person { public : string m_Name; protected : string m_Car; private : int m_Password; public : void func () { m_Name = "张三" ; m_Car = "拖拉机" ; m_Password = 123456 ; } }; void main () { Person p; p.m_Name = "李四" ; }
将所有成员属性设置为私有
可以自己控制读写权限
对于写权限,我们可以检测数据的有效性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class Person {public : void setName (string name) { m_Name = name; } string getName () { return m_Name; } int getAge () { return m_Age; } void setAge (int age) { if (age < 0 || age > 150 ) { cout << "你个老妖精!" << endl; return ; } m_Age = age; } void setLover (string lover) { m_Lover = lover; } private : string m_Name; int m_Age; string m_Lover; }; void main () { Person p; p.setName ("张三" ); cout << "姓名: " << p.getName () << endl; p.setAge (50 ); cout << "年龄: " << p.getAge () << endl; p.setLover ("苍井" ); }
13.对象的初始化和清理
使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供 ,但是编译器提供的构造函数和析构函数是空实现。
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数:主要作用在于对象销毁前 系统自动调用,执行一些清理工作。
构造函数语法:类名(){}
构造函数,没有返回值也不写void
函数名称与类名相同
构造函数可以有参数,因此可以发生重载
程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法: ~类名(){}
析构函数,没有返回值也不写void
函数名称与类名相同,在名称前加上符号 ~
析构函数不可以有参数,因此不可以发生重载
程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
13.1构造函数的分类
按参数分为: 有参构造和无参构造
按类型分为: 普通构造和拷贝构造
拷贝构造函数语法
1 2 3 4 5 6 Person (const Person& p) { age = p.age; cout << "拷贝构造函数!" << endl; }
构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Person {public : Person () { cout << "无参构造函数!" << endl; } Person (int a) { age = a; cout << "有参构造函数!" << endl; } Person (const Person& p) { age = p.age; cout << "拷贝构造函数!" << endl; }
13.2构造函数的使用
三种调用方式:括号法、显示法、隐式转换法
括号法,常用:
Person p1(10);
调用无参构造函数==不能加括号==,如果加了编译器认为这是一个函数声明person p1();
1 2 3 person p1; person p2 (10 ) ;person p3 (p2) ;
显式法:
1 2 3 person p1; person p2=person (10 ); person p3=person (p2);
Person(10)单独写就是匿名对象 当前行结束之后,马上析构回收匿名对象
不要利用拷贝构造函数初始化一个匿名对象person(P3);
编译器会认为是`person(P3)===person P3对象实例化
隐式转换法:
1 2 person p4=10 ; person p5=person (p4);
13.3拷贝构造函数调用时机
①使用一个已经创建完毕的对象来初始化一个新对象
1 2 person p1 (10 ) ;person p2 (p1) ;
②值传递的方式给函数参数传值
1 2 3 4 5 6 7 8 9 void doWork (Person p) {} void test () { Person p; doWork (p); }
③以值方式返回局部对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 class Person {public : Person () { cout << "无参构造函数!" << endl; mAge = 0 ; } Person (int age) { cout << "有参构造函数!" << endl; mAge = age; } Person (const Person& p) { cout << "拷贝构造函数!" << endl; mAge = p.mAge; } ~Person () { cout << "析构函数!" << endl; } public : int mAge; }; void test01 () { Person man (100 ) ; Person newman (man) ; Person newman2 = man; } void doWork (Person p1) {}void test02 () { Person p; doWork (p); } Person doWork2 () { Person p1; cout << (int *)&p1 << endl; return p1; } void test03 () { Person p = doWork2 (); cout << (int *)&p << endl; } void main () { test03 (); }
13.4构造函数调用规则
c++编译器至少给一个类添加3个函数:
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则:
如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
如果用户定义拷贝构造函数,c++不会再提供其他构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class Person {public : Person () { cout << "无参构造函数!" << endl; } Person (int a) { age = a; cout << "有参构造函数!" << endl; } Person (const Person& p) { age = p.age; cout << "拷贝构造函数!" << endl; } ~Person () { cout << "析构函数!" << endl; } public : int age; }; void test01 () { Person p1 (18 ) ; Person p2 (p1) ; cout << "p2的年龄为: " << p2. age << endl; } void test02 () { Person p1; Person p2 (10 ) ; Person p3 (p2) ; Person p4; Person p5 (10 ) ; Person p6 (p5) ; } void main () { test01 (); }
13.5深拷贝与浅拷贝(未看)
P110
13.6初始化列表
语法:构造函数():属性1(值1),属性2(值2)... {}
类里有ABC三个变量,构造函数:
1 2 3 4 5 6 7 Person (int a,int b,int c){ m_A = a; m_B = b; m_C = c; }
初始化列表 :注意冒号位置
1 2 3 4 5 6 7 Person (int a,int b,int c):m_A (a),m_B (b),m_C (c){ m_A = a; m_B = b; m_C = c; }
13.7类作为成员
C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员
1 2 3 4 5 class A {}class B { A a; }
当类中成员是其他类对象时,我们称该成员为 对象成员
构造的顺序是 :先调用对象成员的构造,再调用本类构造
析构顺序与构造相反
13.8静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员变量
所有对象共享同一份数据
在编译阶段分配内存
类内声明,类外初始化
静态成员函数
所有对象共享同一个函数
静态成员函数只能访问静态成员变量
静态成员变量不属于某个对象,所有对象都共享同一份数据,因此静态成员变量有两种访问方式
①通过对象进行访问
1 2 Person p; cout << p.m_A<<endl;
②通过类名进行访问
1 cout << Person::m_A<<endl;
静态函数有两种访问方式
①通过对象进行访问
②通过类名进行访问
③
静态成员函数可以访问静态成员变量,不可以访问非静态成员变量
静态成员函数也有访问权限的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Person { public : static int m_A; private : static int m_B; }; int Person::m_A = 10 ;int Person::m_B = 10 ;void test01 () { Person p1; p1. m_A = 100 ; cout << "p1.m_A = " << p1. m_A << endl; Person p2; p2. m_A = 200 ; cout << "p1.m_A = " << p1. m_A << endl; cout << "p2.m_A = " << p2. m_A << endl; cout << "m_A = " << Person::m_A << endl; } int main () { test01 (); return 0 ; }
14. C++对象模型和this指针
14.1成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储,只有非静态成员变量(不包括函数)才属于类的对象上
C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
是4不是5,说明非静态成员变量属于类的对象上,同理可验证静态成员变量不属于类的对象上
3.2 this指针
P115
this指针指向被调用的成员函数所属的对象
this指针的用途:
①当形参和成员变量同名时,可用this指针来区分
1 2 3 4 5 6 7 8 9 class Person { public : Person (int age) { this ->age=age; } int age; };
②在类的非静态成员函数中返回对象本身,可使用return *this
1 2 3 4 5 6 Person&PersonAddAge (Person &p) { this ->age += p.age; return *this ; }
引用方式返回的是本身,值的方式返回的是复制的另一个对象
引用指向本身内存,不用引用就是拷贝了,而拷贝指向另一个内存,不加引用的话返回的就不是p2了,而是别的Person 对象(这个对象值会一直加,最后值是40)
如果不用引用的方式返回,相当于返回与p2不同的另一个Person(只是age都是20),那么后续的加年龄操作与p2就没有关系了
this指向p2的指针,而*this指向的就是p2这个对象本体,返回本体要用引用+*this
3.3空指针访问成员函数
类:
1 2 3 4 5 6 7 8 9 10 public : void showClassName () { cout << "this is Person class" << endl; } void showPersonAge () { cout << "age = " << thim_Age << endl; } int thim_Age;
调用:
1 2 3 Person *p =NULL ; p->showClassName (); p->showPersonAge ();
空指针调用成员函数:第一个可以运行,第二个函数程序出错
报错原因:传入的指针里面为空,第二个函数调用了成员属性 ,可以修改为:为空就直接返回,
1 2 3 4 5 6 7 8 9 void showPersonAge () { if (this == NULL ) { return ; } cout << "age = " << thim_Age << endl; }
3.4 const修饰成员函数
常函数:就是const
修饰的成员函数
语法:void ShowPerson() const {}
,const加在括号后面
成员函数后加const后我们称为这个函数为常函数
常函数内不可以修改 成员属性
成员属性声明时加关键字mutable 后,在常函数中依然可以修改
1 2 3 void ShowPerson () const { mA = 100 ;
①隐含在每一个成员函数内部都有一个this指针,mA = 100;
相当于this->mA = 100;
this指针的本质是一个指针常量(Person* const this;
):指针的指向不可修改(创建对象,调用指针,this指针指向对象 ),故不可以修改this指向(但是可以修改this指向的值this->mA = 100;
):
1 2 3 4 5 void showPerson () const { this = NULL ; }
②如果想让this指针指向的值也不可以修改,需要声明常函数
③在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改
④const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量。声明:mutable int m_B;
1 2 3 4 5 6 7 8 9 10 class Person { public : mutable int m_B; public : void ShowPerson () const { this ->m_B = 100 ; } };
常对象:
声明对象前加const称该对象为常对象const Person person;
常对象只能调用常函数,不能调用普通函数
常对象不能修改成员变量的值,但是可以访问,但是常对象可以修改mutable修饰成员变量
1 2 3 4 5 6 7 8 void test01 () { const Person person; cout << person.m_A << endl; person.m_B = 100 ; }
15.友元friend
在程序里,有些私有属性 也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的目的就是让一个函数或者类 访问另一个类中私有成员
友元的三种实现:
全局函数做友元
类做友元
成员函数做友元
4.1 全局函数做友元
只需要把类外的全局函数的声明粘贴到类的首行,加上friend和分号 ,之后全局函数就能访问私有变量了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Building { friend void goodGay (Building * building) ; public : Building () { this ->m_SittingRoom = "客厅" ; this ->m_BedRoom = "卧室" ; } public : string m_SittingRoom; private : string m_BedRoom; }; void goodGay (Building * building) { cout<<"好基友正在访问:" <<building->m_SittingRoom<< endl; cout << "好基友正在访问:" <<building->m_BedRoom<< endl; }
4.2 类做友元
一个类可以访问另一个类的私有成员
类外写成员函数:
1 2 3 4 5 6 7 8 9 10 class Building { public : Building (); } Building::Building () { this ->m_SittingRoom = "客厅" ; this ->m_BedRoom = "卧室" ; }
注意成员属性前面没有类名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class goodGay { public : goodGay (); void visit () ; private : Building *building; }; class Building { friend class goodGay ; public : Building (); public : string m_SittingRoom; private : string m_BedRoom; }; Building::Building () { this ->m_SittingRoom = "客厅" ; this ->m_BedRoom = "卧室" ; } goodGay::goodGay () { building = new Building; } void goodGay::visit () { cout << "好基友正在访问" << building->m_SittingRoom << endl; cout << "好基友正在访问" << building->m_BedRoom << endl; } void test01 () { goodGay gg; gg.visit (); }
先创建一个goodGay对象,构造函数里面创建一个building对象,调用了building构造函数,把内部属性赋值,调用visit函数,访问sittingroom属性,卧室属性私有,无法访问
1 2 3 4 5 void GoodGay::visit () { cout << "好基友正在访问" << building->m_SittingRoom << endl; cout << "好基友正在访问" << building->m_BedRoom << endl; }
只需要把class class goodGay
放在另一个类首行,加上friend和分号即可
4.3 成员函数做友元
在被访问的类的首行:friend void 访问的类::成员函数();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class goodGay { public : goodGay () { building = new Building; } void visit () { cout<<"正在访问" <<building->m_SittingRoom<< endl; cout<<"正在访问" <<building->m_BedRoom<< endl; } void visit2 () { cout << "好基友正在访问" << building->m_SittingRoom << endl; } private : Building *building; }; class Building { friend void goodGay::visit () ; public : Building (); public : string m_SittingRoom; private : string m_BedRoom; }; Building::Building () { this ->m_SittingRoom = "客厅" ; this ->m_BedRoom = "卧室" ; } void test01 () { goodGay gg; gg.visit (); }
16.运算符重载
对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
5.1 加号运算符重载
作用:实现两个自定义数据类型相加的运算
定义一个person类,有A、B两属性,实例化2个对象p1,p2。person p3=p1+p2;
是让p1和p2的A、B属性分别相加后返回新的对象p3
成员函数:
1 2 3 4 5 6 7 Person operator +(const Person& p) { Person temp; temp.m_A = this ->m_A + p.m_A; temp.m_B = this ->m_B + p.m_B; return temp; }
本质:Person p3 =p1.operator+(p2);
简化为Person p3 =p1 + p2;
全局函数:
1 2 3 4 5 6 7 Person operator +(const Person& p1, const Person& p2) { Person temp (0 , 0 ) ; temp.m_A = p1. m_A + p2. m_A; temp.m_B = p1. m_B + p2. m_B; return temp; }
本质:Person p3 =operator+(p1,p2);
简化为Person p3 =p1 + p2;
运算符重载,也可以发生函数重载:
1 2 3 4 5 6 7 8 Person operator +(const Person& p2, int val) { Person temp; temp.m_A = p2. m_A + val; temp.m_B = p2. m_B + val; return temp; }
对于内置的数据类型的表达式的的运算符是不可能改变的
不要滥用运算符重载
5.2 左移运算符重载
可以输出自定义数据类型:P122-P126
17.继承
定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。
这个时候我们就可以考虑利用继承的技术,减少重复代码
6.1 继承的基本语法
语法:class 子类 : 继承方式 父类
子类:派生类
父类:基类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class BasePage { public : void header () { } }; class SpecialPage : public BasePage{ public : void content () { } };
派生类中的成员,包含两大部分:
一类是从基类继承过来的,一类是自己增加的成员
从基类继承过过来的表现其共性,而新增的成员体现了其个性
6.2 继承方式
继承方式一共有三种:
父类:private——>子类什么情况都无法访问,此外 :
公共继承public:继承后访问权限不变
保护继承protected:都变为保护权限protected
私有继承private:都变为私有权限private
6.3 继承中的对象模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Base {public : int m_A; protected : int m_B; private : int m_C; }; class Son :public Base{ public : int D; }; void main () { cout << "son大小:" << sizeof (Son) << endl; }
结论:父类中所有非静态成员属性都会被子类继承下去, 父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到
PS:利用工具查看
1.找到开发人员命令提示符
2.右键打开文件夹,复制路径
3.在步骤1的工具里进入类所在的cpp的路径
4.报告单个类的布局cl /d1 reportSingleClassLayoutXXX(类名) cpp文件名
,善用tab键,补全代码,注意是CL 和 D1
父类为Base类,继承了父类的ABC属性,以及自身的D属性
6.4 继承中构造和析构顺序
P48跳过
继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
6.5 继承同名成员处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象 ,访问到子类或父类中同名的数据呢?
访问子类同名成员 直接访问即可
访问父类同名成员 需要加作用域
当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数,如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
1 2 3 4 s.func (); s.Base::m_A; s.Base::func (); s.Base::func (10 );
总结:
子类对象可以直接访问到子类中同名成员
子类对象加作用域可以访问到父类同名成员
当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
6.6 继承同名静态成员处理方式
父类为Base类,继承了父类的ABC属性,以及自身的D属性
问题:继承中同名的静态成员在子类对象上如何进行访问?
同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象 和 通过类名)
访问子类同名成员 直接访问即可
访问父类同名成员 需要加作用域
通过对象访问同名静态函数 :
1 2 3 Son s; s.func (); s.Base::func ();
通过类名访问同名静态函数:出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
1 2 3 Son::func (); Son::Base::func (); Son::Base::func (100 );
通过对象访问同名静态属性 :
1 2 3 Son s; cout << "子类的m_A = " << s.m_A << endl; cout << "父类的m_A = " << s.Base::m_A << endl;
通过类名访问同名静态属性:
1 2 cout << "子类的m_A = " << Son::m_A << endl; cout << "父类的m_A = " << Son::Base::m_A << endl;
第一个::
表示通过类名方式访问,第二个::
表示访问父类作用域下,类似Son::(Base::m_A)
6.7 多继承语法
C++允许一个类继承多个类
语法: class 子类 :继承方式 父类1 , 继承方式 父类2...
1 2 3 class Son : public Base2, public Base1 { };
C++实际开发中不建议用多继承
多继承容易产生成员同名的情况
通过使用类名作用域可以区分调用哪一个基类的成员
1 2 3 Son s; cout << s.Base1::m_A << endl; cout << s.Base2::m_A << endl;
6.8 菱形继承
菱形继承(钻石继承):两个派生类继承同一个基类,又有某个类同时继承者两个派生类
羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性
羊驼继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以
1 2 3 4 5 class Animal { public : int m_Age; };
当菱形继承,两个父类拥有相同数据,需要加作用域区分
1 2 3 4 5 6 7 SheepTuo st; st.Sheep::m_Age = 18 ; st.Tuo::m Age = 28 ; cout << "st.Sheep::m_Age =" << st.Sheep::m_Age << endl; cout << "st.Tuo::m_Age" << st.Tuo::m_Age << endl;
上面的数据(年龄)只要一份即可,子类继承两份相同的数据,导致资源浪费以及毫无意义
利用虚继承可以解决菱形继承问题 :继承前加virtual
关键字后,变为虚继承,此时公共的父类Animal称为虚基类
1 2 3 class Sheep : virtual public Animal {};class Tuo : virtual public Animal {};class SheepTuo : public Sheep, public Tuo {};
现在数据都是28了,虚继承之后数据只有1个了 ,先改为18,再改为28,甚至可以直接通过对象访问
1 2 3 4 5 6 7 8 9 10 SheepTuo st; st.Sheep::m_Age = 18 ; st.Tuo::m Age = 28 ; cout << "st.Sheep::m_Age =" << st.Sheep::m_Age << endl; cout << "st.Tuo::m_Age" << st.Tuo::m_Age << endl; cout << st.m_Age << endl;
18.多态
7.1多态的基本语法
多态分为两类:
静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
动态多态: 派生类和虚函数实现运行时多态
静态多态和动态多态区别:
静态多态的函数地址早绑定 - 编译阶段确定函数地址
动态多态的函数地址晚绑定 - 运行阶段确定函数地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class animal { public : void speak () { cout << "动物在说话" << endl; } }; class cat :public animal{ public : void speak () { cout << "喵~" << endl; } }; void doSpeak (animal & dongwu) { dongwu.speak (); } void main () { cat mao; doSpeak (mao); }
地址早绑定 :在编译阶段确定函数地址,doSpeak()函数无论传什么都会走animal里面的成员函数
如果想要执行cat类的函数,函数地址不能提前绑定,需要在运行阶段绑定,需要地址晚绑定
1 2 3 4 5 6 7 8 9 class Animal { public : virtual void speak () { cout << "动物在说话" << endl; } };
函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了
执行结果为:喵~
动态多态满足条件 :
1.有继承关系
2.子类重写父类的虚函数(父类有virtual,子类virtual可写可不写)
3.重写:函数返回值类型、函数名、参数列表完全相同
动态多态的使用:父类的指针或引用,指向父子类对象animal &an=cat
7.2多态的深入剖析
1 2 3 4 5 6 7 8 class Animal { public : void speak () { cout << "动物在说话" << endl; } };
此时为空类,大小为1
1 2 3 4 5 6 7 8 class Animal { public : virtual void speak () { cout << "动物在说话" << endl; } };
此时大小为4字节,此时是指针
vfptr:virtual function pointer虚函数指针/表
指针指向虚函数表(vftable),表内记录虚函数的地址 &Animal::speak
(成员函数的函数地址要加作用域)
1 2 3 4 class Cat :public Animal{ public :};
当没有重写只有继承时:
1 2 3 4 5 6 7 8 class cat :public animal{ public : virtual void speak () { cout << "喵~" << endl; } };
当子类重写父类的虚函数时:
子类中的虚函数表内部会替换成子类的虚函数地址
父类中的虚函数表指向不变
当父类的指针或引用指向子类对象时,发生多态:他会从cat的虚函数表中寻找函数,在运行阶段发生动态多态是哪个对象就走哪个虚函数表
1 2 animal &ani=cat; ani.speak ();
7.3 多态案例:计算机类
分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类
多态的优点:
代码组织结构清晰
可读性强
利于前期和后期的扩展以及维护
常规实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Calculator {public : int getResult (string oper) { if (oper == "+" ) { return m_Num1 + m_Num2; } else if (oper == "-" ) { return m_Num1 - m_Num2; } else if (oper == "*" ) { return m_Num1 * m_Num2; } } public : int m_Num1; int m_Num2; }; void test01 () { Calculator c; c.m_Num1 = 10 ; c.m_Num2 = 10 ; cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult ("+" ) << endl; cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult ("-" ) << endl; cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult ("*" ) << endl; }
多态实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 class AbstractCalculator { public : virtual int getResult () { return 0 ; } int m_Num1; int m_Num2; }; class AddCalculator :public AbstractCalculator{ public : int getResult () { return m_Num1 + m_Num2; } }; class SubCalculator :public AbstractCalculator{ public : int getResult () { return m_Num1 - m_Num2; } }; class MulCalculator :public AbstractCalculator{ public : int getResult () { return m_Num1 * m_Num2; } }; void main () { AbstractCalculator* abc = new AddCalculator; abc->m_Num1 = 10 ; abc->m_Num2 = 10 ; cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult () << endl; delete abc; abc = new SubCalculator; abc->m_Num1 = 10 ; abc->m_Num2 = 10 ; cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult () << endl; delete abc; abc = new MulCalculator; abc->m_Num1 = 10 ; abc->m_Num2 = 10 ; cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult () << endl; delete abc; }
销毁释放堆区数据,指针指向没有变
提倡用多态,虽然代码量大
7.4 纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名(参数列表)=0;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
只要有一个纯虚函数,这个类称为抽象类
无法实例化对象
抽象类的子列必须重写纯虚函数,否则也属于抽象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 class drink {public : void step1 () { cout<<"煮水" << endl; } virtual void step2 () = 0 ; void step3 () { cout << "倒入杯中" << endl; } virtual void step4 () = 0 ; void step () { step1 (); step2 (); step3 (); step4 (); } }; class coffee :public drink{ virtual void step2 () { cout << "冲泡咖啡" << endl; } virtual void step4 () { cout << "加糖和牛奶" << endl; } }; class tea :public drink{ virtual void step2 () { cout << "冲泡茶叶" << endl; } virtual void step4 () { cout << "加柠檬" << endl; } }; void dodrink (drink * dr) { dr->step (); delete dr; } void main () { cout<<"煮茶步骤" << endl; dodrink (new tea); cout << "煮咖啡步骤" << endl; dodrink (new coffee); }
7.5 虚析构和纯虚析构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Animal { public : Animal () { cout<<"Animal构造函数" << endl; } ~Animal () { cout << "Animal析构函数" << endl; } virtual void speak () = 0 ; }; class Cat :public Animal{ public : Cat () { cout << "Cat构造函数" << endl; } ~Cat () { cout << "Cat析构函数" << endl; } virtual void speak () { cout<<"喵~" << endl; } }; void main () { Animal* animal = new Cat; animal->speak (); delete animal; }
应该先释放子类,后释放父类,但是没有。
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在析构释放时无法调用到子类的析构函数,导致子类如果有堆区属性,出现内存泄漏
解决方式:将父类 中的析构函数改为虚析构 或者纯虚析构
虚析构 :
1 2 3 4 virtual ~Animal (){ cout << "Animal析构函数" << endl; }
纯虚析构 :需要声明,也需要代码实现(与纯虚函数 (不需要代码实现)不同 ),有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Animal { public : Animal () { cout<<"Animal构造函数" << endl; } virtual ~Animal () = 0 ; virtual void speak () = 0 ; }; Animal ::~Animal () { cout << "Animal纯虚析构函数" << endl; }
虚析构和纯虚析构共性:
可以解决父类指针释放子类对象
都需要有具体的函数实现
虚析构和纯虚析构区别:
虚析构语法:virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0;
类名::~类名(){}
19. struct和class区别
C++中 struct和class唯一的区别 就在于 默认的访问权限不同
struct 默认权限为公共
class 默认权限为私有
1 2 3 4 5 6 7 8 class C1 { int m_A; }; struct C2 { int m_A; };
20.文件操作
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放,通过文件可以将数据持久化
C++中对文件操作需要包含头文件<fstream>
文件类型分为两种:
文本文件:文件以文本的ASCII码形式存储在计算机中
二进制文件 :文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们
操作文件的三大类:
ofstream:写操作
ifstream: 读操作
fstream : 读写操作
20.1文本文件
20.1.1写文件
写文件步骤如下:
包含头文件:#include <fstream>
创建流对象:ofstream ofs;
打开文件:ofs.open("文件路径",打开方式);
见下图
写数据:ofs << "写入的数据";
关闭文件:ofs.close();
文件打开方式:
文件打开方式可以配合使用,用|操作符
例如:用二进制方式写文件 ios::binary | ios:: out
1 2 3 4 5 6 7 8 9 #include <fstream> void main () { ofstream ofs; ofs.open ("51.txt" ,ios::out); ofs << "20220718" <<endl; ofs << "学习P143" << endl; ofs.close (); }
文件存储位置:
总结:
文件操作必须包含头文件 fstream
读文件可以利用 ofstream ,或者fstream类
打开文件时候需要指定操作文件的路径,以及打开方式
利用<<可以向文件中写数据
操作完毕,要关闭文件
20.1.2读文件
读文件步骤如下:
包含头文件:#include <fstream>
创建流对象:ifstream ifs;
打开文件并判断文件是否打开成功:ifs.open("文件路径",打开方式);
读数据: 四种方式读取
关闭文件:ifs.close();
四种读取方式:
第一种方式:
1 2 3 4 5 char buf[1024 ] = { 0 };while (ifs >> buf){ cout << buf << endl; }
第二种方式:
1 2 3 4 5 6 char buf[1024 ] = { 0 };while (ifs.getline (buf,sizeof (buf))){ cout << buf << endl; }
第三种方式:
1 2 3 4 5 6 string buf; while (getline (ifs, buf)){ cout << buf << endl; }
第四种方式:不推荐
1 2 3 4 5 char c;while ((c = ifs.get ()) != EOF){ cout << c; }
20.2二进制文件
以二进制的方式对文件进行读写操作
打开方式要指定为ios::binary
除了内置数据类型,还能操作自定义数据类型
20.2.1写文件
通过流对象操作文件
写文件:通过输出流对象ostream,调用write函数
函数原型:ostream& write(const char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间,写入的数据地址。len是读写的字节数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <fstream> #include <string> class Person { public : char m_Name[64 ]; int m_Age; }; void main () { ofstream ofs ("person.txt" , ios::out | ios::binary) ; Person p = {"张三" , 18 }; ofs.write ((const char *)&p, sizeof (p)); ofs.close (); }
重点:把数据地址转换为const char *
20.2.2读文件
二进制方式读文件主要利用流对象调用成员函数read
函数原型:istream& read(char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <fstream> #include <string> class Person { public : char m_Name[64 ]; int m_Age; }; void main () { ifstream ifs; ifs.open ("person.txt" , ios::in | ios::binary); if (!ifs.is_open ()) { cout << "文件打开失败" << endl; } Person p; ifs.read ((char *)&p, sizeof (p)); cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl; ifs.close (); }
1.包含头文件
2.创建流对象
3.打开文件,判断文件是否打开成功
4.读文件
5.关闭文件
文件输入流对象 可以通过read函数,以二进制方式读数据
21.模板
练习
1
学校正在做毕设项目,每名老师带领5个学生,总共有3名老师,需求如下:
设计学生和老师的结构体,其中在老师的结构体中,有老师姓名和一个存放5名学生的数组作为成员学生的成员有姓名、考试分数,创建数组存放3名老师,通过函数给每个老师及所带的学生赋值最终打印出老师数据以及老师所带的学生数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 struct student { string name; int score; }; struct teacher { string name; student stus[5 ]; }; void ValueTea (teacher *p,int len) { for (int i = 0 ; i < len; i++) { cout << "请输入第" << i+1 << "位老师的姓名:" ; cin >> p->name; for (int j = 0 ; j < 5 ; j++) { cout << "请输入该老师所教的第" <<j+1 <<"位学生的姓名:" ; cin >> p->stus[j].name; cout << "请输入该老师所教的第" << j + 1 << "位学生的分数:" ; cin >> p->stus[j].score; } p++; } } void PrintTea (teacher* p, int len) { cout << "打印函数" << endl; for (int i = 0 ; i < len; i++) { cout << "第" << i + 1 << "位老师的姓名" << p->name<<endl; for (int j = 0 ; j < 5 ; j++) { cout << "该老师所教的第" << j + 1 << "位学生的姓名" <<p->stus[j].name << endl; cout << "该老师所教的第" << j + 1 << "位学生的分数" <<p->stus[j].score << endl; } p++; } } void main () { teacher teas[3 ]; int len = sizeof (teas)/sizeof (teas[0 ]); ValueTea (teas,len); PrintTea (teas, len); }
注意:结构体大括号后面有分号,函数没有
2
设计—个英雄的结构体,包括成员姓名,年龄,性别;创建结构体数组,数组中存放5名英雄。通过冒泡排序的算法,将数组中的英雄按照年龄进行升序排序,最终打印排序后的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 struct hero { string name; int age; string sex; }; void order (hero he[], int length) { for (int i = 0 ; i < length; i++) { for (int j = i + 1 ; j < length; j++) { if (he[i].age > he[j].age) { hero t = he[i]; he[i] = he[j]; he[j] = t; } } } } void main () { struct hero heros[5 ] = { {"刘备" ,23 ,"男" }, {"关羽" ,22 ,"男" }, {"张飞" ,20 ,"男" }, {"赵云" ,21 ,"男" }, {"貂蝉" ,19 ,"女" } }; int len = sizeof (heros) / sizeof (heros[0 ]); order (heros, len); for (int i = 0 ; i < len; i++) { cout << "英雄姓名:" << heros[i].name << "\t英雄年龄:" << heros[i].age << "\t英雄性别:" << heros[i].sex << endl; } }
P72-P83