C++笔记
- 1.注释
- 2.变量
- 3.常量
- 4.关键字/标识符
- 5.整型 int
- 6.sizeof()
- 7.浮点型float
- 8.5科学计数法
- 9.字符型char
- 10.转义字符
- 11.字符串型string
- 12.布尔类型bool
- 13.数据的输入
- 14.运算符
- 15.程序流程结构
- 16.switch语句
- 17.随机数函数
- 18.数组
- 20.案例:数组元素逆置
- 21.案例:冒泡排序
- 22.二维数组
- 23.二维数组名
- 24.案例:考试成绩统计
- 25.函数
- 26.指针
- 27.指针和数组
- 28.指针和函数
- 29.指针配合数组和函数的案例
- 30.结构体
- 31.结构体数组
- 32.结构体指针
- 33.结构体嵌套结构体
- 34.结构体做函数参数
- 35.结构体中const的使用
- 36.结构体案例
- 37.内存四区
- 38.new操作符
- 39.引用
- 40.引用做函数参数(引用传递)
- 41.引用做函数的返回值
- 42.引用的本质
- 43.常量引用
- 44.函数提高
- 45.类和对象 important
- 46.案例
- 47.案例2
- 48.对象的初始化和清理
- 49.构造函数的分类及调用
- 50.拷贝构造函数的调用时机
- 51.构造函数调用规则
- 51.深拷贝与浅拷贝
- 52.初始化列表
- 53.类对象作为类成员
- 54.静态成员
- 55.C++对象模型和this指针
- == 56.this指针==
- 57.空指针访问成员函数
- 58.const修饰成员函数
- 59.友元
1.注释
①单行注释
//
②多行注释/* */
/*注释内容 */
2.变量
给一段指定的内存起名
意义:方便我们管理内存空间
语法:
数据类型 变量名 = 变量初始值;
int a = 10;
3.常量
记录程序中不可更改的数据。
①宏常量
#define 常量名 常量值
#define day 7
②const
这种定义方式与变量定义相似,但区别是定义之后的值不能再被修改
const 数据类型 变量名 = 常量值;
4.关键字/标识符
C++中预先保留的单词
标识符命名规则:
①不可以是关键字
②只能由字母数字下划线组成
③第一位必须是字母或下划线
5.整型 int
- 给变量分配合适的内存空间
- 不同的数据类型所占用的空间不同,取值范围不同
短整型
short
2字节
-215 ~ 215-1整型
int
4字节
-231 ~ 231-1
6.sizeof()
求数据类型占用内存大小的函数
语法:
sizeof(数据类型)
或
sizeof(变量)
7.浮点型float
类型:
单精度:float 4 7位有效数字
双精度:double 8 15-16位有效数字
注:默认情况下,编译器会把小数当成双精度double,可以在数字后加f转换成float
Eg:3.14f
注:默认情况下输出一个小数,会显示出六位有效数字。如果想多显示,需要额外配置
8.5科学计数法
3e2 = 3*10^2
3e-2=3*10^-2
9.字符型char
语法:
char 变量名 = '字符';
注意:字符型变量只能用来存储一个字符,不能存储字符串
占用1字节
存储:字符型变量并不是把字符本身放到内存中存储,而是存储相应的ASCII码
10.转义字符
用于表示一个不能显示出来的ASCII字符
Eg:\n 换行
11.字符串型string
C语言风格
char 变量名[] = “字符串”
C++
string 变量名 = “字符串”
要包含string头文件
12.布尔类型bool
真或假 0代表假,其余数都代表真
Bool 变量名 = true或者false;
13.数据的输入
Cin >> 变量
14.运算符
①%取余运算
②++递增运算符
③- -递减运算符
④!非 &&与 ||或
⑤三目运算符
语法:
表达式1?表达式2:表达式3
c=(a > b ? a : b);
注:递增递减运算符分前置后置
++a会先让变量+1,在进行表达式的计算
a++会先进行表达式的计算,在让变量+1
15.程序流程结构
顺序结构、选择结构、循环结构
16.switch语句
表达式只能是整型或字符型
语法:
switch(表达式)
{
Cause 结果1:
执行语句1;
Break;
/*
中间判断语句和结果语句
*/
Default:
执行语句n;
Break;
}
注:表达式符合‘结果’的时候执行语句,
表达式都不符合所列写的结果的时候,执行最后的default
17.随机数函数
生成0-99的随机数
伪随机数:
注:这样生成的是伪随机数,因为每一次的随机数都是固定的
Rand()%100;
真随机数: 在上面的基础上添加这两行
#include<ctime>
srand(unsigned int)time(NULL)
18.数组
一个集合,用于存放相同数据类型的数据
①定义:
语法:
数据类型 数组名 [数组长度];
数据类型 数组名 [数组长度] = {值};
数据类型 数组名 = {值};
②特点
放在一块连续的内存空间中,数组中每个元素都是相同的数据类型。
注意:定义数组的时候必须有初始长度
如果在初始化数据时没有全部填写会用0来填补剩余数据
③数组名
1.可以来统计整个数组在内存中的长度 :利用sizeof函数
#include<iostream>
using namespace std;
int main()
{
int arr[10];
cout << "整个数组占用的空间:" << sizeof(arr) << endl;
cout << "每个元素占用的空间:" << sizeof(arr[0]) << endl;
cout << "数组中元素的个数:" << sizeof(arr)/sizeof(arr[0]) << endl;
}
2.可以获取数组在内存中的首地址。
#include<iostream>
using namespace std;
int main()
{
int arr[10];
cout << "数组的首地址:" << arr << endl;
}
注:数组名是一个常量,不可进行赋值操作
19. 案例:5只小猪称体重
本质:五个数字排大小
思路:先设定一个最大值,然后用数组中的每一个数字去和最大值进行一个比较,更新最大值。
用while循环写:
#include<iostream>
using namespace std;
int main()
{
int arr[5] = { 300,350,400,370,280};
int max = 0;
int i = 0;
while (i < 5)
{
if (arr[i] > max)
{
max = arr[i];
}
i++;
}
cout << "最大值为" << max << endl;
}
错误日志:声明数组的时候,不同数字要用 “,” 分隔
用for循环写:
#include<iostream>
using namespace std;
int main()
{
int arr[5] = { 300,350,400,370,280};
int max = 0;
for(int i = 0 ;i<5 ;i++)
{
if (arr[i] > max)
{
max = arr[i];
}
i++;
}
cout << "最大值为" << max << endl;
}
错误日志:for循环中的格式用 “;”
20.案例:数组元素逆置
描述:声明一个五个元素的数组,并将元素逆置
#include<iostream>
using namespace std;
int main()
{
int arr[5] = { 300,350,400,370,280};
int arr2[5];
int i = 0;
int j = 0;
while (i < 5)
{
int j = 4 - i;
arr2[j] = arr[i];
i++;
}
cout << "数组逆序输出为" << endl;
for (j = 0;j < 5;j++)
{
cout << arr2[j] << endl;
}
}
错误笔记:①j的运算规则应该是4-i
②并且j的运算语句一定要在循环体内,否则是一个固定的数字,起不到标号的作用。
优化: j写成4-i不太好,因为只是针对5个元素的数组这一种情况
#include<iostream>
using namespace std;
int main()
{
int arr[5] = { 300,350,400,370,280};
int arr2[5];
int i = 0;
int j = 0;
while (i < 5)
{
int j = sizeof(arr)/sizeof(arr[0])-1 - i;
arr2[j] = arr[i];
i++;
}
cout << "数组逆序输出为" << endl;
for (j = 0;j < 5;j++)
{
cout << arr2[j] << endl;
}
}
实现版本2
思路:只用一个数组来来回折腾,缺点是思路稍微麻烦一点,而且需要一个变量来作为“中间人”
#include<iostream>
using namespace std;
int main()
{
int arr[5] = { 300,350,400,370,280};
int start = 0;
int temp = 0;
while (start < 3)
{
int end = 0;
end = sizeof(arr) / sizeof(arr[0]) - 1 - start;
temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
start++;
}
cout << "输出逆序后的数组" << endl;
for(int j = 0;j<5;j++)
{
cout << arr[j] << endl;
}
}
错误笔记:①再一次把end的更新规则放在循环体外了 ②循环只能执行三次,执行五次的话就是调换了两遍。
21.案例:冒泡排序
思路:每次冒出数组中的最大值,实现的的方法就是让左边的数和右边的数进行比较,如果左大于右就交换。重复执行这个算法,就能逐一筛选出最大值的降序排列。
#include<iostream>
using namespace std;
int main()
{
int arr[9] = {4,2,8,0,5,7,1,3,9};
int i = 0;
int j = 0;
int temp = 0;
int n = 0;
//开始冒泡排序
while (n < 8)
{
//内层循环对比
while (i < 8)
{
if (arr[i] > arr[i + 1])
{
temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
}
i++;
}
i = 0;
n++;
}
cout << "输出逆序后的数组" << endl;
for(int j = 0;j<9;j++)
{
cout << arr[j] << endl;
}
}
优化:存在的问题是每次都对比8次,因为最后面的一个数已经是最大的了,在下一次不需要排序了。
while (i < 8-n)
改成这样就可以了。
22.二维数组
①定义方式
用四种种方法声明三行四列的二维数组
int arr[3][4];
/*直接赋值的定义方法 */
int arr2[2][3] =
{
{1,2,3},
{4,5,6}
};
int arr3[2][3] = {1,2,3,4,5,6};
int arr[][4]={1,2,3,4}
注意 :在定义二维数组的时候可以省略行数,但是列数不可以省略。 第四种方法只声明列数
打印二维数组用双层for循环
#include<iostream>
using namespace std;
int main()
{
int arr[2][3];
arr[0][0] = 1;
arr[0][1] = 2;
arr[0][2] = 3;
arr[1][0] = 4;
arr[1][1] = 5;
arr[1][2] = 6;
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 3; j++)
{
cout << arr[i][j] << endl;
}
}
}
23.二维数组名
可以用来查看数组所占用的内存空间
可以用来查看二维数组的首地址
与一维数组的用途一样
①查看数组所占用的空间
4*6 = 24(整型占4字节)
#include<iostream>
using namespace std;
int main()
{
int arr[2][3] =
{
{1,2,3},
{4,5,6}
};
cout << sizeof(arr) << endl;
}
②二维数组第一行所占用空间
#include<iostream>
using namespace std;
int main()
{
int arr[2][3] =
{
{1,2,3},
{4,5,6}
};
cout << sizeof(arr[0]) << endl;
}
注意:在二维数组中arr[0]代表第一行的所有数据
③查看二维数组的首地址
cout << arr<< endl;
cout << int(arr) << endl;
24.案例:考试成绩统计
描述:三名同学的考试成绩如下,输出总成绩
#include<iostream>
using namespace std;
int main()
{
int arr[3][3] = {
{100,100,100},
{90,50,100},
{60,70,80}
};
int sum1 = 0;
for (int i = 0; i < 3;i++)
{
sum1 = arr[i][0] + arr[i][1] + arr[i][2];
cout <<"第"<<i+1<<"个人的总分为" << sum1 << endl;
sum1 = 0;
}
}
25.函数
①定义
返回值类型 函数名 参数表列 函数体语句 return表达式
语法:
返回值类型 函数名( 参数列表)
{
函数体语句;
return 表达式;
}
②调用
说明:函数调用里面的参数叫形式参数,简称形参
在函数定义的时候,形式参数并没有真实数据,在函数调用的时候,实参的值会传递给形参
③值传递
做值传递的时候,函数形参发生改变,并不会影响实参。
④函数的常见样式
无参无返
#include<iostream>
using namespace std;
void test01()
{
cout << "hello world";
}
int main()
{
test01();
}
有参无返
#include<iostream>
using namespace std;
void test02(int a )
{
cout << "hello world"<<a;
}
int main()
{
test02(100);
}
无参有返
#include<iostream>
using namespace std;
int test03()
{
cout << "hello world";
return 1000;
}
int main()
{
int num1 = test03();
cout << num1;
}
有参有返
#include<iostream>
using namespace std;
int test04(int a)
{
cout << "hello world"<<a<<endl;
return 1000;
}
int main()
{
int num1 = test04(100);
cout << num1<<endl;
}
⑤函数的声明
#include<iostream>
using namespace std;
//函数声明的意义:提前告诉编译器函数的存在
int max(int a, int b);
int main()
{
cout<<max(20, 30);
}
int max(int a, int b)
{
return a > b ? a : b;
}
注意:函数在main函数之后,必须要声明。并且函数声明可以有多次,但是定义只能有一次。
⑥函数的分文件编写
1.创建后缀名为.h的头文件 3.在头文件中写函数的声明
创建swap.h文件并如下代码
void swap(int a, int b);
2.创建后缀名为.cpp的源文件 4.在源文件中写函数的定义
为了声明和swap.h文件是配套的需要include swap.h
#include"swap.h"
#include<iostream>
using namespace std;
void swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
cout << a << endl << b;
}
5.在原来的文件中引用
#include<iostream>
using namespace std;
#include"swap.h"
int main()
{
swap(20, 30);
}
26.指针
指针声明三部曲
声明 变量,声明 指针,建立指针和变量之间的关系。
可以通过指针间接访问内存
指针就是地址
①如何定义指针
声明指针
数据类型 * 指针变量名
int * p;
让指针记录变量a的地址 &取址符号,让p记录a的地址
P = &a;
②怎么使用指针
可以通过解引用的方式来找到指针指向的内存
指针前加*代表解引用,找到指针指向的内存中的数据
*p=a
声明指针也可以有这种写法
这种写法更加简洁,相当于*是声明指针,p=&a表示p取a的地址。
int *p=&a;
③指针所占内存空间
指针存的是地址,和什么类型的指针没关系
在32位操作系统下占用4个字节,64位操作系统 8个字节
#include<iostream>
using namespace std;
#include"swap.h"
int main()
{
int a = 10;
int* p;
p = &a;
double b = 2;
double* p2;
p2 = &b;
cout << sizeof(p2) << endl;
cout << sizeof(p) << endl;
}
④空指针
指向编号为0的空间的指针
一般用于初始化指针,
空指针指向的编号为0的空间是不能访问的
0到255之间的内存编号是系统占用的,不可以访问。
int * p = NULL;
⑤野指针
指针变量指向非法的内存空间
int * p = (int * )0X1100;
(int*)就是把这个16进制的数强行转换成地址
在程序中要避免野指针
⑥const修饰指针
①Const修饰指针:常量指针(指向可以改,值不可以改)
const int * p = &a;
#include<iostream>
using namespace std;
#include"swap.h"
int main()
{
int a = 10;
const int* p = &a;
p = NULL;
cout << p;
}
指针的指向可以修改,指针指向的值不可以修改。
②const修饰常量,指针常量(const直接修饰指针,指针的指向不能修改)
int * cosnt p = &a;
#include<iostream>
using namespace std;
int main()
{
int a = 10;
int * const p = &a;
*p = 20;
cout << a;
}
指针的指向不可以修改,但指针指向的值可以修改
③const即修饰指针又修饰常量
const int * const p = &a;
指针的指向和指针指向的值都不可以修改。
27.指针和数组
利用指针访问数组中的元素。
#include<iostream>
using namespace std;
int main()
{
int arr[] = {1,2,3,4,5,6};
//arr就是数组的首地址
int* p = arr;
//输出数组中的第一个元素
cout << *p << endl;
cout << "arr数组的首地址是" << p << endl;
//让指针向后偏移四个字节,因为指针是int型
p = p + 1;
cout << "arr数组的第二个元素的地址是" << p << endl;
cout << *p << endl;
}
QUESTION:为什么p+1之后就能神奇的对应到第二个元素?为什么p+1之后对应的地址增加了4?
ANSWER:因为是整型指针,++之后往后偏移四个字节。
用for循环实现用指针遍历数组
#include<iostream>
using namespace std;
int main()
{
int arr[] = {1,2,3,4,5,6};
//arr就是数组的首地址
int* p = arr;
//输出数组中的第一个元素
for (int i = 0; i < 6; i++)
{
cout << "第" << i + 1 << "个元素的地址是" << p<<endl;
cout << "第" << i + 1 << "个元素的值是" << *p<<endl;
p++;
}
}
28.指针和函数
①值传递(复习)
#include<iostream>
using namespace std;
void swap01(int a, int b)
{
int temp = a;
a = b;
b = temp;
cout << "函数中的形式参数" << "a=" << a << endl <<"b=" << b << endl;
}
int main()
{
int a = 10;
int b = 20;
swap01(a, b);
cout << "实际参数" <<"a=" << a << endl <<"b=" << b << endl;
}
②地址传递
神奇之处在于,能够通过函数把实参的值变了。
#include<iostream>
using namespace std;
void swap02(int* a, int* b)
{
//相当于就是把a,b的地址换了
int temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int a = 10;
int b = 20;
swap02(&a, &b);
cout << "实际参数" <<"a=" << a << endl <<"b=" << b << endl;
}
29.指针配合数组和函数的案例
案例描述:封装一个函数,利用冒泡排序,实现对整型数组的升序排序
#include<iostream>
using namespace std;
void bubbleSort(int * arr,int len)
{
for (int i = 0; i < len-1; i++)
{
for (int j = 0; j < len -1- i; j++)
{
if (arr[j] >arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
int main()
{
int arr[] = { 6,5,4,3,2,1 };
int len = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr,len);
for (int i = 0; i < len ; i++)
{
cout << arr[i] << endl;
}
}
注意:在把数组传进函数里时只需要传数组的首地址即可
30.结构体
用途:可以创建自定义的数据类型,允许用户存储不同的数据类型
其实自定义的数据类型就是一些集合组成的一个类型
①定义
1.
struct 结构体名 变量名
2.
struct 结构体名 变量名 ={成员1值,成员2值,...}
3.
定义结构体时顺便创建变量
②使用
方法1.
struct Student s1;
s1.name = "张三";
s1.age = 13;
s1.score = 100;
方法2
struct Student s1 = {"张三",13,100};
↓这种方法过于别扭了
方法3.
在定义结构体的时候顺便创建
#include<iostream>
using namespace std;
//创建学生数据类型
struct Student
{
//成员列表
//姓名
string name;
//年龄
int age;
//分数
int score;
}s3;
int main()
{
s3.name = "翠花";
s3.age = 15;
s3.score = 100;
cout << s3.name << endl;
}
注意:在调用结构体的时候struct关键字可以省略。
31.结构体数组
将自定义的结构体放在数组中方便维护。
①定义
struct 结构体名 数组名[]={{}, {},{}...}
struct Student stuArray[3] =
{
{"张三",18,100},
{"李四",28,99},
{"王五",38,98}
};
stuArray[2].score = 96;
②遍历结构体数组
int main()
{
struct Student stuArray[3] =
{
{"张三",18,100},
{"李四",28,99},
{"王五",38,98}
};
stuArray[2].score = 96;
cout << stuArray[2].score << endl;
for (int i = 0; i < 3; i++)
{
cout << "姓名" << stuArray[i].name << endl;
cout << "年龄" << stuArray[i].age << endl;
cout << "分数" << stuArray[i].score<< endl;
};
}
32.结构体指针
利用操作符 ->
可以用过结构体指针访问结构体属性
对于结构体来说直接用 . 就可以访问属性
但是对于结构体指针,要用->
int main()
{
Student s1 = {"翠花",10,66};
Student* p = &s1;
cout << "姓名" << p->name << "年龄" << p->age;
}
33.结构体嵌套结构体
被嵌套的结构体要先声明。
#include<iostream>
using namespace std;
//创建学生数据类型
struct gfriend
{
string name;
int age;
};
struct Student
{
//成员列表
//姓名
string name;
//年龄
int age;
//分数
int score;
struct gfriend g1;
};
int main()
{
Student s1 = {"翠花",10,66};
s1.g1.age = 15;
s1.g1.name = "西西";
cout << "姓名" << s1.g1.age << "年龄" << s1.g1.name;
}
34.结构体做函数参数
将结构体作为参数向函数中传递。
值传递
地址传递
#include<iostream>
using namespace std;
struct Student
{
string name;
int age;
};
void printStu(Student p)
{
//值传递
cout << p.name << endl << p.age;
}
void printStu2(Student *p)
{
//地址传递
cout << p->name << endl << p->age;
}
int main()
{
Student s1;
s1.name = "张三";
s1.age = 18;
printStu2(&s1);
};
注意:与前面相同,值传递不会修改实参,地址传递会修改实参。
35.结构体中const的使用
用来防止误操作。
36.结构体案例
案例描述:学校做毕设项目,每名老师带五个学生,总共三个老师,需求如下,设计老师和学生的结构体,其中在老师的结构体中,有老师姓名和一个存放五名学生的数组作为成员。学生的成员有姓名、老师分数,创建数组存放三名老师,通过函数给每个老师及所带的学生赋值,最后打印老师数据以及老师所带的学生数据。
37.内存四区
①代码区
存放函数体的二进制代码,由操作系统将进行管理。
写的所有代码都放在里面,程序运行前,在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域。代码区:存在CPU执行的机器指令(就是咱们自己写的代码),代码区的特点:共享:对于频繁被执行的程序,只需要在内存中有一份代码。只读:防止程序意外地修改他的指令。
②全局区
存放全局变量和静态变量以及常量
生成的时间与代码区相同。
存放全局变量、静态变量、常量(字符串常量 和 全局常量)。
③栈区(一般是函数内)
由编译器自动分配释放,存放函数的参数值,局部变量。
函数调用之后内存就释放掉了
注意:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放。
④堆区
由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
new关键字开辟到堆区
#include<iostream>
using namespace std;
int * func()
{
//利用new关键字将数据开辟到堆区,并且返回内存地址。
int* p = new int(10);
return p;
}
int main()
{
int* p = func();
cout << *p << endl;
}
38.new操作符
#include<iostream>
using namespace std;
int * func()
{
//利用new关键字将数据开辟到堆区,并且返回内存地址。
int* p = new int(10);
return p;
}
void test01()
{
int* p = func();
cout << *p << endl;
delete p;
cout << *p << endl;
}
void test02()
{
int * arr = new int[10];//代表数组有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;
}
int main()
{
test02();
}
39.引用
作用:给变量起别名
数据类型 &别名 = 原名
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a;
b = 20;
cout << a;
}
注意事项:
1.引用必须要初始化
2.引用一旦初始化后,就不可以更改了。
40.引用做函数参数(引用传递)
引用传递
形参会修饰实参
#include <iostream>
using namespace std;
void myswap01(int a, int b)
{
int tempt = a;
a = b;
b = tempt;
}
void myswap02(int *a, int *b)
{
int tempt = *a;
*a = *b;
*b = tempt;
}
void myswap03(int &a, int& b)
{
int tempt = a;
a = b;
b = tempt;
}
int main()
{
cout << "zhichuandi"<<endl;
int a = 10;
int b = 20;
myswap01(a, b);
cout << "a=" << a << endl;
cout << "b=" << b << endl;
cout << "dizhichuandi"<<endl;
int c = 10;
int d = 20;
myswap02(&c, &d);
cout << "a=" << c << endl;
cout << "b=" << d << endl;
cout << "yinyongchuandi" << endl;
int e = 10;
int f = 20;
myswap03(e, f);
cout << "a=" << c << endl;
cout << "b=" << d << endl;
}
41.引用做函数的返回值
不要返回局部变量的引用
函数的调用可以作为左值
42.引用的本质
引用的本质是一个指针常量,所以在初始化之后不能更改指针了,但值可以更改。
43.常量引用
修饰形参,防止误操作
正常来说
int &ref=10;
是不可以正常使用的,因为引用不能引用一个常数。
但是下面的语句可以
const int &ref=10;
这里相当于编译器帮我们修改了代码,
int tempt=10;
const int&ref = tempt;
44.函数提高
①函数的默认参数
如果传参数的话就用传的参数,如果没传参数就用默认值。
#include <iostream>
using namespace std;
int func(int a, int b=20, int c=30)
{
return a + b + c;
}
int main()
{
cout << func(10) << endl;
cout << func(10, 30) << endl;
}
注意事项:如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值。如果函数声明有默认参数,函数实现就不能有默认参数,声明和实现智能有一个有默认参数。
②函数占位参数
③函数重载
i)概述
函数名可以相同,提高复用性
条件: 同一个作用域下,函数名相同,函数参数类型不同或者个数不同或者顺序不同。
注意:函数的返回值不可以作为函数重载的条件。
#include <iostream>
using namespace std;
void func()
{
cout << "func的调用" << endl;
}
void func(int a )
{
cout << "func(int a )的调用" << endl;
}
int main()
{
func();
func(10);
}
ii)函数重载的注意事项
引用作为重载的条件
#include <iostream>
using namespace std;
void func(int &a)
{
cout << "func(int a )的调用" << endl;
}
void func(const int &a)
{
cout << "func(const int a )的调用" << endl;
}
int main()
{
int a = 10;
func(10);
func(a);
}
函数重载碰到默认参数
会报错,写函数重载尽量不要用默认参数
#include <iostream>
using namespace std;
void func(int a,int b=10)
{
cout << "func(int a )的调用" << endl;
}
void func(int a)
{
cout << "func(const int a )的调用" << endl;
}
int main()
{
int a = 10;
func(10);
}
45.类和对象 important
C++面向对象的三大特性:封装、继承、多态
C++认为万事万物都是对象,对象上有其属性和行为
①封装
#include <iostream>
using namespace std;
//设计一个圆类,求球的周长。
//周长公式:2*PI*RADIUS
//class代表设计一个类,类后面紧跟着就是类名称
class circle
{
//访问权限 公共权限
public:
//属性
int m_r;
int PI = 3.1415;
//行为
//获取圆的周长
double calculateZC()
{
return 2 * PI * m_r;
}
};
int main()
{
//通过一个圆类创建一个具体的圆
circle C1;
C1.m_r = 10;
cout << "圆的周长为" << C1.calculateZC() << endl;
}
实例化:通过一个类创建一个对象的过程。
类中的属性和行为统一称为成员。
属性也叫作成员属性、成员变量。
行为也叫成员函数、成员方法。
②封装的意义
类在设计时,可以把属性和行为放在不同的权限下,加以控制。
访问权限有一下三种:
public 公共权限 成员类内可以访问,类外可以访问
protected 保护权限 成员类内可以访问,类外不可以访问(父类中的保护权限子类也可以访问)
private 私有权限 类内可以访问 类外不可访问(父类中的私有权限子类不能访问)
注意:代码会报错
#include<iostream>
using namespace std;
class Person
{
public:
string name;
protected:
string car;
private:
int password;
public:
void func()
{
string name = "zhangsan";
car = "拖拉机";
password = 12345;
}
};
int main()
{
Person P1;
P1.car = "奔驰";//这里注意,car是private属性,所以在类外不能访问,只能在类内调用。
}
③class 和 struct的区别
struct默认权限是公共public
class默认权限是private
注意代码会报错
#include<iostream>
using namespace std;
class test {
int a = 100;
};
int main()
{
test A;
A.a = 1000;//这里就会报错因为class 默认权限是private,不能直接调用
}
④将成员属性设置为私有
优点:将所有成员属性设置为私有,可以控制自己控制读写的权限
优点2:对于写权限,我们可以检测数据的有效性。
#include<iostream>
using namespace std;
#include<string>
class Person
{
public:
//在公共的成员函数中为private属性赋值
//写姓名 设置
void setname(string name)
{
m_name = name;
}
string getname()
{
return m_name;
}
void setage(int age)
{
if (age < 0 || age>150)
{
cout << "你这个老妖精" << endl;
return;
}
m_age = age;
}
int getage()
{
return m_age;
}
private:
string m_name; //可读可写
int m_age; //可读可写,但是年龄的范围必须是0-150之间
string lover;//只写
};
int main()
{
Person zhangsan;
zhangsan.setname("张三");
cout << zhangsan.getname() << endl;
zhangsan.setage(10);
cout << zhangsan.getage() << endl;
}
46.案例
/*
* 利用成员函数判断
*
* 对于成员函数写法的理解,
* 为什么只需要传一个实例就可以呢?
* 因为在类中,本身相当于定义了一个类,在这个类中我可以访问private的属性,但是新传进来的C相当于在这个类之外另建了一个类,这个类就只能调用public的方法来使用。
*/
#include<iostream>
using namespace std;
//设计一个立方体类
//求出立方体的面积和体积
//分别用全局函数和成员函数判断两个立方体是否相等。
class CUBE
{
private:
double m_l;
double m_w;
double m_h;
public:
void setparma(double l, double h, double w)
{
m_l = l;
m_h = h;
m_w = w;
}
double get_l()
{
return m_l;
}
double get_h()
{
return m_h;
}
double get_w()
{
return m_w;
}
double get_cube_S()
{
return 2 * (m_l * m_w + m_l * m_h + m_w * m_h);
}
double get_cube_V()
{
return (m_l * m_w * m_h);
}
/*
* 利用成员函数判断
*
* 对于成员函数写法的理解,
* 为什么只需要传一个实例就可以呢?
* 因为在类中,本身相当于定义了一个类,在这个类中我可以访问private的属性,但是新传进来的C相当于在这个类之外另建了一个类,这个类就只能调用public的方法来使用。
*/
bool judgeCube2(CUBE &C)
{
if (m_h == C.m_h && m_l == C.m_l && m_w == C.m_w)
{
return true;
}
else
{
return false;
}
}
};
/*
利用全局函数判断两个CUBE是否相等
*/
bool judgeCUBE(CUBE &c1, CUBE &c2) //引用传递
{
if (c1.get_h() == c2.get_h() && c1.get_l() == c2.get_l() && c1.get_w() == c2.get_w())
{
return true;
}
}
int main()
{
CUBE C1;
C1.setparma(10, 10, 10);
cout << C1.get_cube_S() << endl;
cout << C1.get_cube_V() << endl;
CUBE C2;
C2.setparma(10, 10, 10);
bool ret= judgeCUBE(C1, C2);
if (ret)
{
cout << "c1和c2相等"<<endl;
}
else
{
cout << "c1和c2不相等"<<endl;
}
bool ret2 = C1.judgeCube2(C2);
if (ret2)
{
cout << "通过成员函数判断的结果为C1和C2相等"<<endl;
}
else
cout << "通过成员函数判断为C1和C2不相等"<<endl;
}
47.案例2
48.对象的初始化和清理
每个对象都有初始设置以及对象销毁前数据的设置
①构造函数和析构函数
编译器自动调用,完成对象初始化和清理工作
但是编译器提供的构造函数和析构函数是空实现。
构造函数:主要作用在于创建将对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。
析构函数:主要作用于对象销毁前系统自动调用,执行一些清理工作。
②语法
构造函数: 类名(){}
构造函数是没有返回值的,也不需要写void
函数名和类名相同
构造函数可以有参数,因此也可以使用 重载技术
程序在调用对象的时候会自动调用构造,无需手动调用。
#include<iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "Person构造函数的调用";
}
};
void test01()
{
Person p;
}
int main()
{
test01();
}
析构函数:~类名(){}
析构函数没有返回值,也不写void
函数名称与类名称相同,多加一个~
析构函数不可以有参数,因此无法使用重载技术
在对象销毁前会自动调用析构函数,无需手动调用,而且只会调用一次
#include<iostream>
using namespace std;
class Person
{
public:
/*
* 构造函数的使用
*/
Person()
{
cout << "Person构造函数的调用"<<endl;
}
/*
* 析构函数的使用
*/
~Person()
{
cout << "析构函数的使用"<<endl;
}
};
void test01()
{
Person p;//相当于一个局部变量,所以是栈上的数据,test01执行完毕之后就会释放这个对象。
}
int main()
{
Person C;
system("pause");
}
49.构造函数的分类及调用
两种分类方式
按参数分:有参构造和无参构造
按类型分:普通构造和拷贝构造
三种调用方式
括号法
在调用默认构造函数的时候不要加括号,因为编译器会把这种语句判定为函数声明。
显示法
隐式转换法
例程
#include<iostream>
using namespace std;
/*
*构造函数的分类以及调用
* 按照参数分类 无参构造(默认构造) 有参构造
* 按照类型 分类 普通构造函数 拷贝构造函数
*/class Person
{
public:
int age;
Person()
{
cout << "Person的无参构造函数的调用" << endl;
}
Person(int a)
{
age = a;
cout << "Person的有参构造函数调用"<<endl;
}
/*
拷贝构造函数
*/
Person(const Person & p)
/*
将传入的人身上的所有属性,拷贝到我身上
*/
{
age = p.age;
}
};
//调用
void test01()
{
//括号法
Person p1; //调用默认构造函数 注意:使用默认构造函数的时候不能加括号,否则就会认为是函数的声明
Person p2(10);//有参构造函数的调用
/*
*拷贝构造函数的调用
*/
Person p3(p2);
cout << "p2的年龄为:" << p2.age << endl;
cout << "p3的年龄为:" << p3.age << endl;
//显示法
Person p4; //默认构造
Person p5 = Person(10); //有参构造
Person p6 = Person(p5);
Person(10); //匿名对象:特点,当前行执行结束后,系统会立即回收掉匿名对象。
//不要利用拷贝构造函数 初始化匿名对象
Person(p2); //这会报错,因为这条语句等价于Person p2;,重定义了。 编译器会认为这是对象 的重声明
//隐式转换法
Person p4 = 10;
//相当于写了Person p4=Person(10);
}
int main()
{
test01();
}
50.拷贝构造函数的调用时机
三种情况:
①使用一个已经创建完毕的对象来初始化一个新对象。
#include <iostream>
using namespace std;
//拷贝构造函数的调用时机
//1.使用一个已经创建完毕的对象来初始化一个新对象。
class Person
{
public:
int m_age;
Person()
{
cout << "Person默认构造函数调用"<<endl;
}
Person(int age)
{
m_age = age;
cout << "Person有参构造函数的调用"<<endl;
}
Person(const Person& p)
{
cout << "PERSON 拷贝构造函数的调用" << endl;
m_age = p.m_age;
}
~Person()
{
cout << "Person析构函数调用" << endl;
}
};
void test01()
{
Person p1(20);
Person p2(p1);
}
int main()
{
test01();
}
②值传递的方式给函数参数传值
//2.值传递的方式给函数参数传值
#include <iostream>
using namespace std;
class Person
{
public:
int m_age;
Person(int age)
{
cout << "有参构造函数的调用";
m_age = age;
}
Person()
{
cout << "普通构造函数的调用"<<endl;
}
Person(const Person &p)
{
cout << "拷贝构造函数的调用" << endl;
m_age = p.m_age;
}
~Person()
{
cout << "析构函数的调用" << endl;
}
};
void doWork(Person p)
{
}
void test02()
{
Person p;
doWork(p);
}
//3.值方式返回局部对象
int main()
{
test02();
}
输出结果为:
普通构造函数的调用
拷贝构造函数的调用
析构函数的调用
析构函数的调用
这里为什么会调用拷贝构造函数呢?
答:实参传递给形参的时候会调用拷贝构造函数
③以值传递的方式返回局部对象。
//3.值方式返回局部对象
#include <iostream>
using namespace std;
class Person
{
public:
int m_age;
Person(int age)
{
cout << "有参构造函数的调用";
m_age = age;
}
Person()
{
cout << "普通构造函数的调用"<<endl;
}
Person(const Person &p)
{
cout << "拷贝构造函数的调用" << endl;
m_age = p.m_age;
}
~Person()
{
cout << "析构函数的调用" << endl;
}
};
Person doWork()
{
Person p1;
return p1;
}
void test03()
{
Person p = doWork();
}
int main()
{
test03();
}
输出:
普通构造函数的调用
拷贝构造函数的调用
析构函数的调用
析构函数的调用
原因在于:这里dowork函数中创建的p1对象,在函数调用完成之后就会释放,return的p1是重新创建的,所以这里调用了拷贝析构函数。
51.构造函数调用规则
默认情况下,C++编译器至少给一个类添加三个函数
1.默认构造函数(无参,函数体为空)
2.析构函数(无参,函数体为空)
3.拷贝构造函数,对属性进行值拷贝
构造函数的调用规则
·如果用户定义有参构造函数,C++不在提供默认无参构造,但是会提供默认拷贝构造
·如果用户定义拷贝构造函数,C++不会提供其他构造函数。
例程
//3.值方式返回局部对象
#include <iostream>
using namespace std;
//构造函数的调用规则:
//C++编译器会给每个类都添加至少三个函数
/*
* 默认构造函数 (空实现)
* 析构函数(空实现)
* 拷贝构造函数(值拷贝)
*/
class Person
{
public:
Person()
{
cout << "Person默认构造函数的调用" << endl;
}
Person(int age)
{
m_age = age;
cout << "person的有参构造函数的调用" << endl;
}
Person(const Person& p)
{
m_age = p.m_age;
cout << "拷贝构造函数"<<endl;
}
~Person()
{
cout << "析构函数" << endl;
}
int m_age;
};
void test01()
{
Person p;
p.m_age = 18;
Person p2(p);
cout << "P2的年龄是多少"<<p2.m_age<< endl;
}
int main()
{
test01();
}
输出结果是
Person默认构造函数的调用
拷贝构造函数
P2的年龄是多少18
析构函数
析构函数
如果把拷贝构造函数注释掉会发生什么事呢?
输出结果:
Person默认构造函数的调用
P2的年龄是多少18
析构函数
析构函数
这里可以看出还是执行了拷贝构造函数
51.深拷贝与浅拷贝
浅拷贝:简单地复制拷贝操作。
深拷贝:在堆区重新申请空间,进行拷贝操作。
例程
浅拷贝,把P1的属性传给P2
#include<iostream>
using namespace std;
class Person
{
public:
int m_age;
Person()
{
cout << "Person 默认构造函数的调用" << endl;
}
Person(int age)
{
cout << "Person 有参构造函数的调用" << endl;
m_age = age;
cout << "年龄" << m_age << endl;
}
Person(const Person& p)
{
m_age = p.m_age;
cout << "person 拷贝构造函数的调用" << endl;
cout << m_age << endl;
}
~Person()
{
cout << "析构函数的调用";
}
};
void test01()
{
Person P1(18);
Person P2(P1);
}
int main()
{
test01();
}
浅拷贝会带来问题,堆区内存重复释放
#include<iostream>
using namespace std;
class Person
{
public:
int m_age;
int* m_height;
Person()
{
cout << "Person 默认构造函数的调用" << endl;
}
Person(int age, int a)
{
cout << "Person 有参构造函数的调用" << endl;
m_age = age;
m_height = new int(a); //这里看似是用指针接受160,但是实际上new 在堆区申请空间后会返回一个空间地址,需要指针来接收。
}
Person(const Person& p)
{
m_age = p.m_age;
m_height = p.m_height;
cout << "person 拷贝构造函数的调用" << endl;
}
~Person()
{
//析构函数的作用:
//将堆区开辟的数据释放
if (m_height != NULL)
{
delete m_height;
m_height = NULL;
}
cout << "析构函数的调用";
}
};
void test01()
{
Person P1(18, 160);
Person P2(P1);
cout << "P1的年龄是" << P1.m_age << "P1的身高是" << *P1.m_height << endl;
cout << "P2的年龄是" << P2.m_age << "P2的身高是" << *P2.m_height << endl;
}
int main()
{
test01();
}
这里在析构函数中释放内存的逻辑:(非法操作)
1.在栈中后进的先出,也就是P2先释放,m_height指向一个堆区中的地址,将这个地址释放,等到P1再来释放的时候,这个地址已经是空的了,就存在一个重复释放的操作。
这个问题可以用深拷贝解决:
#include<iostream>
using namespace std;
class Person
{
public:
int m_age;
int* m_height;
Person()
{
cout << "Person 默认构造函数的调用" << endl;
}
Person(int age, int a)
{
cout << "Person 有参构造函数的调用" << endl;
m_age = age;
m_height = new int(a); //这里看似是用指针接受160,但是实际上new 在堆区申请空间后会返回一个空间地址,需要指针来接收。
}
Person(const Person& p)
{
m_age = p.m_age;
m_height = p.m_height;
//这一行是编译器的默认实现
//cout << "person 拷贝构造函数的调用" << endl;
//深拷贝的实现
m_height = new int(*p.m_height); //这里是核心实现
}
~Person()
{
//析构函数的作用:
//将堆区开辟的数据释放
if (m_height != NULL)
{
delete m_height;
m_height = NULL;
}
cout << "析构函数的调用";
}
};
void test01()
{
Person P1(18, 160);
Person P2(P1);
cout << "P1的年龄是" << P1.m_age << "P1的身高是" << *P1.m_height << endl;
cout << "P2的年龄是" << P2.m_age << "P2的身高是" << *P2.m_height << endl;
}
int main()
{
test01();
}
总结:如果属性有在堆区开辟的,一定要自己弄一个拷贝构造函数,防止浅拷贝带来的问题。
52.初始化列表
作用:C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)........{}
例程
这一种是写死的
#include<iostream>
using namespace std;
class Person
{
public:
int m_a;
int m_b;
int m_c;
//传统初始化操作
Person(int a, int b, int c)
{
m_a = a;
m_b = b;
m_c = c;
}
//初始化列表赋初值
Person() :m_a(10), m_b(20), m_c(30)
{
}
};
void test01()
{
//Person p(10, 10, 10);
Person p;
cout << "m_a=" << p.m_a;
cout << "m_b=" << p.m_b;
cout << "m_c=" << p.m_c;
}
int main()
{
test01();
}
这一种可以实时更改
#include<iostream>
using namespace std;
class Person
{
public:
int m_a;
int m_b;
int m_c;
//初始化列表赋初值
Person(int a,int b,int c) :m_a(a), m_b(b), m_c(c)
{
}
};
void test01()
{
//Person p(10, 10, 10);
Person p(30,20,10);
cout << "m_a=" << p.m_a;
cout << "m_b=" << p.m_b;
cout << "m_c=" << p.m_c;
}
int main()
{
test01();
}
53.类对象作为类成员
当其他类对象作为本类成员,构造函数先构造对象,再构造本身,析构的顺序与构造相反。
#include<iostream>
using namespace std;
class Phone
{
public:
Phone(string a)
{
band = a;
}
string band;
};
class Person
{
public:
Person(string a,string b):name(a),band(b)
{
}
string name;
Phone band;
};
void test01()
{
Person p("zhangsan", "huawei");
cout << "姓名" << p.name << endl;
cout << "手机" << p.band.band << endl;
}
int main()
{
test01();
}
54.静态成员
静态成员就是在成员变量和成员函数前面加上关键字static,称为静态成员
静态成员分为:
#include<iostream>
using namespace std;
class Person
{
public:
//所有对象共享同一份数据
//编译阶段就分配内存
//类内声明,类外初始化
static int m_A;
};
//类外初始化
int Person::m_A = 10;
void test01()
{
Person p;
cout << p.m_A << endl;
Person p2;
p2.m_A = 200;
cout << p.m_A << endl;
}
void test02()
{
//静态成员变量 不属于某个对象,所有对象共享同一份数据,
//因此静态成员变量有两种访问方式
//1.通过对象进行访问
Person p;
cout << p.m_A << endl;
//2.通过类名进行访问
cout << Person::m_A << endl;
}
int main()
{
test02();
}
静态成员变量也是有访问权限的
#include<iostream>
using namespace std;
class Person
{
public:
//所有对象共享同一份数据
//编译阶段就分配内存
//类内声明,类外初始化
static int m_A;
private:
static int m_B;
};
//类外初始化
ing Person::m_B = 200;
void test02()
{
//private权限的就不能访问、
cout << Person::m_B << endl;
}
int main()
{
test02();
}
#include<iostream>
using namespace std;
class Person
{
public :
static void func()
{
cout << "static func函数的调用" << endl;
}
};
void test01()
{
Person p;
p.func();
Person::func();
}
int main()
{
test01();
}
静态成员函数只能访问静态变量
静态成员函数也是有访问权限的
#include<iostream>
using namespace std;
class Person
{
public :
static void func()
{
m_A = 200;//静态的成员函数只能访问静态成员变量。
cout << "static func函数的调用" << endl;
}
//静态成员变量: 类内声明 类外初始化
private:
static int m_A;
int m_B;
};
int Person::m_A = 100;
void test01()
{
Person p;
p.func();
Person::func();
}
int main()
{
test01();
}
55.C++对象模型和this指针
成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于累的对象上。
空对象
空对象占用的内存空间是1
C++编译器会给每个空队形也分配一个字节空间,是为了区分空对象占内存的位置。
每个空对象也应该有一个独一无二的内存地址
#include<iostream>
using namespace std;
class Person
{
};
void test01()
{
Person p;
cout << sizeof(p) << endl;
}
int main()
{
test01();
}
非静态成员变量
占用内存4
#include<iostream>
using namespace std;
class Person
{
int m_A; //非静态 成员变量
};
void test01()
{
Person p;
cout << sizeof(p) << endl;
}
int main()
{
test01();
}
静态成员变量
如果此时,在class 中添加一行静态成员变量的声明
#include<iostream>
using namespace std;
class Person
{
int m_A; //非静态 成员变量
static int m_B;
};
int Person::m_B = 10;
void test01()
{
Person p;
cout << sizeof(p) << endl;
}
int main()
{
test01();
}
输出结果还是4,
静态成员变量,不属于类对象上。
非静态成员函数
#include<iostream>
using namespace std;
class Person
{
int m_A; //非静态 成员变量
static int m_B;
void func() //
{
}
};
int Person::m_B = 10;
void test01()
{
Person p;
cout << sizeof(p) << endl;
}
int main()
{
test01();
}
输出结果还是4!!
非静态成员函数,不属于类对象上。
静态成员函数
#include<iostream>
using namespace std;
class Person
{
int m_A; //非静态 成员变量
static int m_B;
void func() //
{
}
static void func02()
{
}
};
int Person::m_B = 10;
void test01()
{
Person p;
cout << sizeof(p) << endl;
}
int main()
{
test01();
}
输出结果依然是4
也就是说只有非静态成员变量属于类对象上。
== 56.this指针==
this指针指向被调用的成员函数所属的对象。
this指针用途:
- 当形参和成员变量同名时,可以用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this
#include<iostream>
using namespace std;
class Person
{
public:
//成员属性和形参名字一样
Person(int age)
{
//this指针指向 被调用的成员函数 所属的对象
this->age = age;
//被调用的成员函数是有参构造函数 所属的对象是P1,this指向p1
}
int age;
};
void test01()
{
Person p1(18);
cout << "p1的年龄" << p1.age << endl;
}
int main()
{
test01();
}
返回对象本身用*this
#include<iostream>
using namespace std;
class Person
{
public:
//成员属性和形参名字一样
Person(int age)
{
//this指针指向 被调用的成员函数 所属的对象
this->age = age;
//被调用的成员函数是有参构造函数 所属的对象是P1,this指向p1
}
int age;
void PersonaddPerson(Person &p)
{
this->age += p.age;
}
};
void test01()
{
Person p1(18);
cout << "p1的年龄" << p1.age << endl;
Person p2(100);
p2.PersonaddPerson(p1);
cout << p2.age;
}
int main()
{
test01();
}
但是我没加爽我还想加,
要怎么操作呢?
链式编程思想
简单来说,用值的方式返回,一定是创建了一个新的对象。
用引用的方式返回,他不会创建新的对象,会一直返回P2
具体参见拷贝构造函数的调用时机3
-
两个技术点:
return *this
Person&
#include<iostream>
using namespace std;
class Person
{
public:
//成员属性和形参名字一样
Person(int age)
{
//this指针指向 被调用的成员函数 所属的对象
this->age = age;
//被调用的成员函数是有参构造函数 所属的对象是P1,this指向p1
}
int age;
Person& PersonaddPerson(Person &p) //如果要返回他的本体,要用引用的方式返回。。如果不加&,相当于返回与p2不同的另一个Person,那么后续的加年龄惭怍与P2无关了
{
this->age += p.age;
return *this;
}
};
void test01()
{
Person p1(18);
cout << "p1的年龄" << p1.age << endl;
Person p2(100);
p2.PersonaddPerson(p1).PersonaddPerson(p1).PersonaddPerson(p1);
cout << p2.age;
}
int main()
{
test01();
}
57.空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针。
如果用到this指针,需要加以判断保证代码的健壮性。
58.const修饰成员函数
this指针的本质是一个指针常量。
指针的指向是不可以修改的。
所以this的指向是不能修改的。
#include "iostream"
using namespace std;
class Person
{
public:
void showPerson()
{
this = NULL;
}
int m_A;
};
void test01()
{
Person p;
p.showPerson();
}
int main()
{
test01();
}
可以看到,这里会报错,因为this指针是指针常量,他不能指向空指针,但是其指向的值可以改。
#include "iostream"
using namespace std;
class Person
{
public:
void showPerson()
{
this->m_A = 100;
}
int m_A;
};
void test01()
{
Person p;
p.showPerson();
}
int main()
{
test01();
}
但是,想让this指针即不能修改指向也不能修改值得话也是有办法的,就是这里提到的const修饰成员函数
#include "iostream"
using namespace std;
class Person
{
public:
void showPerson() const
{
this->m_A = 100;
}
int m_A;
};
void test01()
{
Person p;
p.showPerson();
}
int main()
{
test01();
}
就是这样,都动不了了(指向、值)。
但我就是任性,我就想改,也是有办法的。mutable
#include "iostream"
using namespace std;
class Person
{
public:
void showPerson() const
{
this->m_A = 100;
this->m_B = 100;
}
int m_A;
mutable int m_B;
};
void test01()
{
Person p;
p.showPerson();
}
int main()
{
test01();
}
常对象
#include "iostream"
using namespace std;
class Person
{
public:
int m_A;
mutable int m_B;
};
void test01()
{
const Person p;
p.m_A = 100;
}
int main()
{
test01();
}
这里类似,p.m_A是不能调用的。
#include "iostream"
using namespace std;
class Person
{
public:
int m_A;
mutable int m_B;
};
void test01()
{
const Person p;
p.m_A = 100;
p.m_B = 100;
}
int main()
{
test01();
}
但是p.m_B就是可以调用的。
常对象只能调用常函数,不能调用普通成员函数。
59.友元
就是介于public和private之间的一种权限,在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元技术。
三种实现
- 全局函数做友元
- 类做友元
- 成员函数做友元