C++笔记

C++笔记

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=10const 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();
}
  • 静态成员函数

  • 特点

    所有对象共享一个函数
    静态成员函数只能访问静态成员变量。
  • 访问静态成员函数的方法
    1.通过对象进行访问
    2.通过类名来访问
  • 例程
#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之间的一种权限,在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元技术。

三种实现

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元

全局函数做友元

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值