c++进阶学习:https://blog.csdn.net/qq_66669252/article/details/154750732?spm=1011.2415.3001.5331

1:科学计数法:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	float f1 = 3e2; //科学计数法 300
	float f2 = 3e-2;// 0.03
	cout << "f1:" << f1 << endl;
	cout << "f2:" << f2 << endl;
	return 0;
}

输出结果:

2:sizeof()的作用

用于计算某个类型或对象在内存中所占的字节数。

int main()
{
	char ch = 'a';//字符型一个字节大小
	cout << sizeof(ch) << endl;
	cout << (int)ch << endl;//ascii 编码
	cout <<"int:"<<sizeof(int) << endl;
	cout << "short:" << sizeof(short) << endl;
	cout << "double:" << sizeof(double) << endl;
	cout << "flaot:" << sizeof(float) << endl;
	return 0;
}

运行结果:

3:字符串

char str[] = "hello";

定义了一个 字符数组,内容是 'h' 'e' 'l' 'l' 'o' '\0'

编译器会自动在字符串末尾加一个结束符 '\0',表示字符串结束。

stringC++ 标准库类,定义在 <string> 头文件中。

int main()
{
	char str[] = "hello";
	string st2 = "hello2";//字符串
	cout << str << endl;
	cout << st2 << endl;
	return 0;
}

输出结果:

4:布尔型

  • bool 是布尔类型,只能取两个值:truefalse

  • 在内存中:

    • true 通常存为 1

    • false 通常存为 0

int main()
{
	bool flag = true; // 布尔型
	cout << flag << endl;
	flag = false;
	cout << flag << endl;
	return 0;
}

输出结果:

5:++:前置递增,后置递增

  1. ++a(前置):先让变量自增,再参与表达式求值。

  2. a++(后置):先拿变量当前值参与表达式求值,再自增。

  3. 因此同样是从 10 开始,++a * 10 得到 110,而 a++ * 10 得到 100,但两条语句执行完后变量自身都变成了 11。

int main()
{
	int a = 10;
	int b = ++a * 10;//先让变量加1,再进行表达式运算
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
	int c = 10;
	int d = c++ * 10;//先进行表达式运算,再加1
	cout << "c=" << c << endl;
	cout << "d=" << d << endl;
	return 0;
}

运行结果:

6:if条件判断语句

//单分支:
if (条件) 
{
语句;
}

//双分支
if (条件)
{

}
else
{ 

}

//三分支
if (条件1) 
{
    …
}
else if (条件2)
{
    …
}
else if (条件3)
{
    …
} 
else 
{
    …
}

例子:

int main()
{
	int a = 0;
	cout << "请输入一个分数:" << endl;
	cin >> a;
	if (a > 700)
	{
		cout << "恭喜你,高于700分" << endl;
	}
	else if (a > 600 )
	{
		cout << "恭喜你,高于600分" << endl;
	}
	else
	{
		cout << "未达到600分" << endl;
	}
	return 0;
}

输出结果:

7:三木运算符

     条件 ? 表达式1 : 表达式2

  1. 先算“条件”

  2. 条件为真,整个表达式取“表达式1”的值

  3. 条件为假,整个表达式取“表达式2”的

    int main()
    {
    	int a, b, c;
    	a = 10;
    	b = 20;
    	c=(a > b ? a : b);
    	cout << c;
    	return 0;
    }

    输出结果:                                                                                 

8:switch语句

switch (整型表达式)          // ① 控制表达式
{
    case 整型常量表达式1:     // ② 标签
        语句序列1;
        break;               // ③ 跳出开关
    case 整型常量表达式2:
        语句序列2;
        break;
    ...
    default:                  // ④ 可选“兜底”
        语句序列n;
}

执行流程:

  1. 先计算“控制表达式”的值 。

  2. 依次与各个 case 标签做相等比较,找到匹配后从该标签开始顺序向下执行,直到遇到 break 或 switch 结束。

  3. 若全部不匹配且有 default,则从 default 开始执行;若无 default 则什么都不做。

例子:

int main()
{
	int a = 0;
		cout << "输入分数" << endl;

		cin >> a;
		switch (a)
		{
		case 10:
			cout << "10好";
			break;
		case 9:
			cout << "9好";
			break;
		default:
			cout << "差评";
		}
}

输出结果:

9:do while语句

do-while 的核心特征——“先干活,后检查”

语法模板:

do
{
    // 循环体:一条或多条语句
} while (条件表达式);   // 分号千万别漏!
int main()
{
    int num = 0;               // 1) 初始化
    do                        // 2) 直接进入循环体——无需任何条件
    {
        cout << num << endl;  // 3) 输出 0
        num++;                // 4) 变成 1
    } while (num < 10);       // 5) 判断 1<10 为真,继续
}

循环体共执行 10 次,输出 0 1 2 … 9,之后 num==10 时条件为假,退出。

输出结果:

10:continue语句

  • 只能出现在 循环体for / while / do-while)内

  • 遇到后立即停止本次迭代剩余语句,立即跳转到循环控制部分(对 for 就是“表达式3”即 i++

    int main()
    {
    	int i = 0;//输出奇数,偶数跳过
    	for (i = 0; i <=100; i++)
    	{
    		if (i % 2 == 0)
    		{
    			continue;//跳过本次,直接到下一次循环
    		}
    		cout << i << endl;
    	}
    	return 0;
    }
    

    输出结果:
    1 3 5 7 … 99(所有奇数) 

11:goto语句

goto 语句的核心功能是打破正常的顺序执行流程,直接跳转到指定标记处

  1. 首先执行 cout << "1" << endl;,输出 1

  2. 遇到 goto FLAG; 语句,程序会直接跳转到 FLAG: 标记所在的位置。

  3. 跳过了 cout << "2" << endl; 这行代码(因此不会输出 2)。

  4. 从 FLAG: 处继续执行,执行 cout << "3" << endl;,输出 3

int main()
{
	cout << "1" << endl;
	goto FLAG;
	cout << "2" << endl;
	FLAG:
	cout << "3" << endl;
	return 0;
}

输出结果:

12:数组

数组是相同类型元素的集合,定义时需指定元素类型和(通常需要)大小。

int main()
{
// 方式1:只指定大小,不初始化(元素值为随机值,取决于内存状态)
	int arr_1[4];
// 方式2:不指定大小,通过初始化列表自动推断大小
	int arr_2[] = { 1,2,3 };
// 方式3:指定大小,并通过初始化列表部分/全部初始化
	int arr_3[2] = { 4,5 };
	cout << arr_3[1] << endl;
//数组名在多数情况下会被隐式转换为数组首元素的地址(指针)
	cout << (int)arr_3 << endl;
	cout << (int)&arr_3[0] << endl;
	cout << (int)&arr_3[1] << endl;
	return 0;
}

输出结果:

13:二维数组

        定义格式:类型 数组名[行数][列数];

        二维数组在内存中是连续存储的,按 “行优先” 顺序排列(先存第 0 行所有元素,再存第 1 行所有元素)。

        第 0 行第 1 列元素(arr_1[0][1])的地址 = 第 0 行首地址 + 1× 元素类型大小(int 占 4 字节,因此比arr_1[0]大 4 字节)。

int main()
{
//局部二维数组(如arr)若未初始化,元素值为随机值(取决于内存状态);
//全局二维数组未初始化则默认值为 0。
	int arr[2][3];
	arr[0][0] = 1;
	arr[1][2] = 3;
//嵌套初始化列表(推荐,结构清晰)
	int arr_1[2][3] =
	{
		{1,2,3},
		{4,6,5}
	};
//扁平初始化列表(不推荐,可读性差)
	int arr_2[2][3] = { 1,2,3,4,5,6 };
//输出结果
//  第1行第1列(未赋值,输出随机值)
	cout << arr[1][1] << endl;
// 第1行第2列(已赋值3,输出3)
	cout << arr[1][2] << endl;
	cout << "第一行地址" << (int)arr_1[0] << endl;
	cout << "第二个元素首地址" << &arr_1[0][1] << endl;
	return 0;
}
 

输出结果:

14:指针

      1: 指针的类型

        指针是一种特殊的变量,它的 “值” 不是普通数据(如整数、字符),而是另一个变量的内存地址

        int* 是指针的类型,表示 “指向 int 的指针”(指针的类型必须与它指向的变量类型一致,否则可能导致数据访问错误)。

        执行p = &a后,指针p就 “指向” 了变量a,此时p中存储的是a在内存中的具体位置(如0x7ffd8a8b8a4c这类十六进制地址)。

        2:解引用

        指针最核心的功能是通过存储的地址,间接操作它所指向的变量,这一过程称为 “解引用”,用*运算符实现。

        *p中的*是解引用运算符,作用是 “根据 p 存储的地址,找到对应的变量”。

        3:指针的大小

        指针的大小在同一操作系统下是固定的。

        64 位系统中,内存地址用 64 位(8 字节)表示,因此任何类型的指针(int*char*double*等)大小都是 8 字节;32 位系统中指针大小为 4 字节。

int main()
{
	int a = 200;
	int* p;//指针:代表地址,可以存放a的地址
	p = &a;
	cout << "a的地址"<< p << endl ;
	//解引用:找到指针对应地址的数据
	*p = 1000;
	cout << a << endl;
	cout << sizeof(int*) << endl;//64位操作操作系统下:指针是 8个字节
	return 0;
 }

输出结果:

15:常量指针与指针常量的区别

        在 C++ 中,const与指针结合时,根据const修饰的对象不同,会产生 “常量指针” 和 “指针常量” 两种不同的类型。它们的核心区别在于允许修改的内容不同

        1:常量指针(const int* p

        定义形式const 类型* 指针名;const*左侧)

        含义:指针指向的数据是常量(不能通过指针修改),但指针本身存储的地址可以修改。

        2. 指针常量(int* const num

        定义形式类型* const 指针名;const*右侧)

        含义:指针本身是常量(存储的地址不能修改),但指针指向的数据可以修改(通过指针解引用操作)。

int main()
{
	int a = 10;
	int b = 20;
	//常量指针:指针指向的地址可以改,指针指向的值不能改
	const  int*p = &a;
	p = &b;//可以修改指向
	 
	//指针常量:
	int* const num = &b;//指向的值可以改,地址不可以

	//const修饰指针与常量
	const int* const num_2 = &b;
	return 0;
}

16:指针数组

        数组名在大多数情况下会被隐式转换为指向数组首元素的指针,即数组第一个元素的地址。

        指针可以进行算术运算(如p++p += n等),其移动的字节数由指针指向的数据类型决定(即 “步长” = 类型大小)。

        执行p++后,p从指向arr[0]变为指向arr[1](因为数组元素在内存中连续存储,arr[1]的地址 = arr[0]的地址 + 4 字节)。

int main()
{
	int arr[5] = { 1,2,3,45 };
	int* p = arr;//数组名字就是地址
	cout << "第一个元素:" << *p << endl;
	p++;//整形指针,地址偏移四个字节
	cout << "第二个元素:" << *p << endl;
	return 0;
}

输出结果:

17:指针与地址传递,通过解引用交换两个变量的值

  swap函数的功能是交换两个整数变量的值,它的参数是两个int*类型的指针(即专门用来接收整数地址的指针)

        因为 C++ 函数参数默认是 “值传递”(即形参是实参的拷贝),如果直接传int a, int b,函数内部修改的只是拷贝值,无法影响外部的原变量。而传递指针(地址),可以通过地址间接操作原变量。

   *p1 等价于 “p1指向的变量”(即main函数中的a);

         *p2 等价于 “p2指向的变量”(即main函数中的b);

        整个过程通过指针的解引用,直接修改了main函数中ab的内存值。

void swap(int* p1, int* p2)
{
	int flag = *p1;//对地址进行解引用 
	*p1 = *p2;
	*p2 = flag;
	
}
int main()
{
	int a = 10;
	int b = 20;
	swap(&a, &b);//传入a,b的地址
	cout << "a"<< a << endl;
	cout <<"b:"<< b << endl;

	return 0;
}

18:结构体

        定义格式:struct 结构体名 { 成员列表; };(注意末尾的分号不能省略)。

        结构体变量的成员必须通过成员访问运算符. 访问,格式为:结构体变量名.成员名

int main()
{
	//1:创建方式1:只有一个类型,还未定义变量
	struct student
	{
		string name;
		int age;
	};
//方式 1:先定义结构体类型,再单独创建变量
	struct student s1;
	s1.age = 19;
	s1.name = "juzi";
	cout << "姓名:" << s1.name<<"  " << "年龄:" << s1.age  << endl;
//方式2 创建变量时直接初始化(初始化列表)
	struct student s2 = { "chen",18 };
	cout << "姓名:" << s2.name<<"  " << "年龄:" << s2.age  << endl;
//方式3
	struct student2
	{
		string name;
		int age;
	}s3;
	s3.age = 41;
	s3.name = "xixi";
	cout << "姓名:" << s3.name << "  " << "年龄:" << s3.age << endl;
	return 0;
}

输出结果:

19:结构体数组

        结构体数组的定义格式为:结构体类型 数组名[数组大小];

        若要存储 100 个学生的信息,无需定义 100 个student变量,只需创建一个student arr[100]数组,通过下标即可快速访问任意学生的信息,极大简化了代码。

struct student 
{
	int age;
	string name;
};
int main()
{
	//结构体数组
	struct student arr[3]
	{
		{18,"chen"},
		{19,"juzi"},
		{20,"xixi"}
	};
	arr[2].age = 33;
	for (int i = 0; i < 3; i++)
	{
		cout << "姓名是:" << arr[i].name << "年龄是:" << arr[i].age << endl;
	}

	return 0;
}

输出结果:

20:结构体指针

        结构体指针是指向结构体变量的指针,定义格式为:结构体类型* 指针名;。它存储的是结构体变量在内存中的地址。

  &s1 是结构体变量s1的地址,赋值给指针p后,p就指向了s1在内存中的位置。

        当通过指针访问结构体成员时,不能直接使用.运算符(.用于结构体变量直接访问成员),而需要使用箭头运算符->,格式为:指针名->成员名

运算符:用于结构体变量直接访问成员,格式:结构体变量名.成员名(如s1.name)。

运算符:用于指向结构体的指针访问成员,格式:结构体指针名->成员名(如p->name)。

struct student {
	string name;
		int age;
};

int main()
{
	struct student s1 = { "hh",19 };
	struct student * p = &s1;//通过指针指向结构体变量
	cout << "姓名:" << p->name << "  年龄:" << p->age << endl;//通过指针来访问结构体成员
	return 0;
}

输出结果:

21:嵌套结构体

        结构体嵌套指的是在一个结构体(称为 “外层结构体”)的成员列表中,包含另一个结构体(称为 “内层结构体”)类型的成员。

  teacher结构体中,stustudent类型的成员,即 “老师” 结构体中包含了 “学生” 结构体,形成了嵌套关系。如 “老师” 不仅有自己的 ID 和年龄,还关联了一个 “学生” 的信息

        访问嵌套结构体的成员时,需要从外层到内层逐层使用.运算符。

        格式为:外层结构体变量.外层成员.内层成员

struct student
{
	string name;
	int age;
};
struct teacher
{
	int id;
	int age;
	struct student stu;//结构体嵌套,辅导的学生
};
int main()
{
	teacher chen;
	chen.age = 30;
	chen.id = 1001;
	chen.stu.age = 18;
	chen.stu.name = "xiaoju";
	cout <<"age:"<< chen.age <<"  id:" << chen.id <<"  student.age:" << chen.stu.age <<"  student.name:" << chen.stu.name << endl;
	return 0;
}

输出结果:

22:值传递与地址传递区别

值传递(printf_value函数

传递方式:将结构体变量的完整副本传入函数,形参是实参的拷贝。

代码中:printf_value(chen);调用时,会在内存中为形参sstruct student s)开辟一块新空间,将实参chen的所有成员(age=10name="juzi")完整复制到s中。

不能修改原数据:函数内部对形参s的修改(如s.age=20)只会影响副本,不会改变实参chen的值(因为两者是独立的内存空间)。

地址传递(printf_adj函数)

传递方式:将结构体变量的地址传入函数,形参是指向原结构体的指针(struct student* s)。代码中:printf_adj(&chen);

调用时,仅将chen的地址(如0x7ffd...)传入函数,形参s存储这个地址,指向原结构体chen不会复制整个结构体。                                                                                                 

特性能修改原数据:函数内部通过指针解引用(s->age=90)直接操作原结构体chen的内存,修改会反映到实参上(代码中chen.age最终会变为 90)。

struct student {
		int age;
		string name;
	};
void printf_value(struct student s)//重新开辟一个形参空间,复制实参的内容
{
	cout << "值传递" << s.age << s.name << endl;
};
//地址传递
void printf_adj(struct student *s)//指针传入,减少内存空间,不会复制新的副本出来
{
	s->age = 90;
	cout << "地址传递" << s->age << s->name << endl;
};
void printf_c_adj(const struct student* s)//加入const 不能修改,防止误操作,只能读
{
	
	cout << "地址传递" << s->age << s->name << endl;
};
int main()
{
	struct student chen;
	chen.age = 10;
	chen.name = "juzi";
	printf_value(chen);//结构体传变量会复制一份新的,结构体传入的是地址
	printf_adj(&chen);
	return 0;
}

输出结果:

23:结构体案例,结构体数组传参

        通过 “结构体嵌套数组” 和 “函数操作数组” 的方式,模拟了一个包含多名老师和对应学生信息的管理场景,涉及结构体嵌套、数组操作、函数传参。

  sizeof(teach)是整个数组的总字节数,sizeof(teach[0])是单个元素(teacher类型)的字节数,相除得到数组长度3。

struct student
{
	string name;
	int score;
};
struct teacher//嵌套结构体数组
{
	string name;
	struct student stu[5];
};
//赋值操作
void assignment(struct teacher s[],int len)
{
	string name_abc = { "abcde" };
	for (int i = 0; i < 3; i++)
	{
		s[i].name = "teacher";
		s[i].name += name_abc[i];
		for (int a = 0; a < 5; a++)
		{
			s[i].stu[a].name= { "student" };
			s[i].stu[a].name += name_abc[a];

			s[i].stu[a].score = 70;
		}
	}
}

//打印操作
void printf(struct teacher aa[], int len)
{
	for (int i = 0; i < 3; i++)
	{
		cout <<"老师姓名: "<< aa[i].name << endl;
		for (int b = 0; b < 5; b++)
		{
			cout << "学生姓名: " << aa[i].stu[b].name ;
			cout << "学生成绩:" << aa[i].stu[b].score << endl;
		}
	}
}
int main()
{
	struct teacher teach[3];//创建三个老师
	int len = sizeof(teach) / sizeof(teach[0]);
	assignment(teach,len);//传入数组地址,长度
	printf(teach, len);
	return 0;
}

24:堆区操作

堆区内存的管理完全由程序员控制,通过new运算符分配内存,delete运算符释放内存(若不手动释放,程序结束后由操作系统回收)

new int(10)的作用:在堆区开辟一块内存,存放整数10,并返回这块内存的地址。

指针p本身是局部变量(存放在栈区),但它存储的是堆区内存的地址 —— 这是堆区使用的典型方式:用栈区的指针指向堆区的内存。

  1. func()执行完return p;后,栈区的局部变量p(存储堆区地址的指针)会被自动销毁 —— 意思是 “栈区里专门存地址的那块小空间被回收了”。
  2. p指向的堆区内存(存储10的空间)完全没变化—— 堆区的地址(比如0x7ffd...)是内存的物理位置标识,不会因为栈区指针被销毁而消失,堆区数据10也依然存在。
  3. func()返回时,会把p存储的堆区地址(比如0x7ffd...)作为返回值,赋值给main()里的栈区指针y
  4. 此时func()p虽然被销毁了,但y已经拿到了堆区地址的 “副本”——y作为main()的局部变量,只要main()没执行完,它就一直有效,通过*y自然能访问到堆区的10
int* func()
{
	//堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
	int *p = new int(10);//放回数据10的地址信息
	//指针 本质也是局部变量,放在栈区,存放着数据10的地址信息
	return p;
}
int main()
{
	int* y = func();
	cout << *y << endl;
	cout << *y << endl;

	return 0;
}

运行结果

25:new在堆区开辟数组

语法:类型* 指针名 = new 类型[元素个数];会在堆区连续一块连续的内存空间,用于存储指定个数的数组元素,并返回数组首元素的地址。

堆区数组的释放必须使用 **delete[]**(带[]的形式)

int* func()
{
	int *p=new int(10);//返回该数据类型的指针
	return p;
	 
}
void test()
{
	int* p = func();
	cout << *p << endl;//堆区的数据由程序员管理
	delete p;//释放堆区数据
}
void test_2()
{
	int* arr = new int[10];//在堆区开辟数组 10代表数组有10个元素 返回数组的首地址
	for (int i = 0; i < 10; i++)
	{
		arr[i] = i + 100;//给10个元素赋值
	}
	cout << arr[0] << endl;
	cout << arr[1] << endl;
	cout << arr[9] << endl;
    //释放数组
	delete [] arr;
}
int main()
{
	test();
	test_2();
	return 0;
}

输出结果:

26:引用语法

引用的定义格式为:类型& 引用名 = 原变量名;,其中&是引用声明符(不是取地址运算符),表示 “定义一个引用”。

 核心特性:引用b没有自己的内存空间,它和原变量a共享同一块内存 —— 可以理解为 “ba的另一个名字”。

对引用的所有操作,本质上都是对它所关联的原变量的操作。

int main()
{
	int a = 10; //创建了一个存储单元
	int& b = a; //起了一个“第二个名字”来访问这块内存。b 看起来像一个变量,但底层没有自己的存储空间。  不能指向别人了
	b = 20;
	cout << "b="<<b << endl;
	cout << "a=" << a << endl;
	return 0;
}

输出结果:

27:引用传递,指针传递的区别

指针传递

指针是独立的变量(占内存空间,64 位系统中 8 字节),存储的是原变量的地址。

函数内部的指针参数(ab)是外部地址的拷贝(即指针本身是值传递),但因存储的是地址,通过解引用可操作原变量。

引用传递

引用不是独立变量,没有自己的内存空间,它是原变量的 “别名”(与原变量共享同一块内存)。

函数内部的引用参数(ab)本质上就是外部变量(cd)本身,操作引用等价于直接操作原变量。

////值传递
void swap_1(int a ,int b)
{
	int temp;
	temp = a;
	a = b;
	b = temp;
}

//地址传递
void swap_2(int * a,int * b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}

//引用传递
void swap_3(int &a,int&b)
{
	int temp = a;
	a = b;
	b = temp;
}

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int d = 40;
	swap_2(&a,&b);
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
	swap_3(c, d);
	cout << "c=" << c << endl;
	cout << "d=" << d << endl;
}

输出结果:

28:引用做函数返回值存在,静态变量与引用的结合,引用作为左值

1. 引用作为函数返回值的风险:返回局部变量的引用

函数返回引用时,本质是返回变量的 “别名”,但如果返回的是栈区局部变量,会导致未定义行为。栈区局部变量atest()函数执行结束后会被自动释放,其内存空间被回收(可能被其他数据覆盖)。此时返回的引用ref成为 “悬空引用”(指向已失效的内存)。

2. 安全的引用返回:返回静态变量或全局变量的引用

若函数返回的是全局区变量(如静态变量、全局变量)的引用,则是安全的,因为全局区变量的生命周期与程序一致(程序结束才释放)。

静态变量特性static int a在第一次调用test_1()时初始化,之后不会重新初始化,且内存不会随函数结束而释放,直到程序结束才销毁。

3. 引用返回的特殊用法:函数调用作为左值

若函数返回的是可修改的引用(非const引用),则函数调用表达式可以作为左值(即赋值符号=左边的值),直接给被引用的变量赋值。

int& test()
{
	int a = 10;//局部变量放在栈区中,会被销毁
	return a;
}

int& test_1()
{
	static int a = 100;//静态变量,放入全局区,程序结束后,才开始释放
	return a;
}
//引用就相当于指针常量,指向不能改,值可以
int main()
{
	
	int &ref=test();
	cout << "ref:" << ref << endl;//test();函数结束就销毁,所以会出现乱码,编译器有可能会保存一次
	cout << "ref:" << ref << endl;
	int& ref_2 = test_1();
	cout << "ref_2:" << ref_2 << endl;//静态变量,生命周期延长了
	test_1()= 1000;//引用的函数调用可以作为左值
	cout << "ref_2:" << ref_2 << endl;
	return 0;
}

输出结果

29:常量引用

常量引用是被const修饰的引用,定义格式为:const 类型& 引用名 = 原变量;。它的核心特性是:可以访问原变量,但不能通过引用修改原变量的值

void test_2(const int& ref)//防止函数对a变量进行操作
{
	//ref =10;报错操作
	cout << ref << endl;

}
int main()
{
	int a = 100;
	test_2(a);//const int &a=10;
	return 0;
}

30:形参列表

1.形参默认值的定义:为参数指定默认值

形参默认值是在函数定义或声明时,为形参指定一个默认值。当函数调用时若未传递对应实参,编译器会自动使用默认值填充。

int func(int a = 10, int b = 20, int c = 30)  // a、b、c分别指定了默认值10、20、30
{
    return a + b + c;
}

2. 形参默认值的调用规则:按顺序匹配,缺省补默认值

调用带默认值的函数时,实参与形参按从左到右的顺序匹配,未传递的参数自动使用默认值。

// 1. 不传任何实参:全部使用默认值
num = func();  // 等价于 func(10, 20, 30),返回10+20+30=60

// 2. 传递全部实参:覆盖所有默认值
num = func(1, 2, 3);  // a=1, b=2, c=3,返回6

// 3. 传递部分实参:按顺序匹配,剩余用默认值
num = func(1, 2);  // a=1, b=2(前两个参数被覆盖),c=30(未传递,用默认值),返回1+2+30=33

限制 1:默认值必须从右向左连续设置

若某个形参指定了默认值,其右侧(右边)的所有形参必须也指定默认值,不能出现 “左边有默认值,右边无默认值” 的情况。

// int func_1(int a = 1, int b)  // 错误!a有默认值,但右侧的b没有
// { ... }

限制 2:声明与实现中默认值不能重复

函数的声明(原型)和实现(定义)中,默认值只能出现一次,不能同时在声明和实现中指定。

// 声明时指定默认值
int func2(int a = 10, int b = 20);

// 实现时不能再指定默认值(否则编译报错)
int func2(int a, int b)  // 正确:仅使用声明中已指定的默认值
{
    return a + b;
}
int func(int a = 10, int b = 20,int c=30)
{
	return a + b + c;
}
//如果某个位置有了默认参数,从这个位置往后,左往右都必须要有默认值
//int func_1(int a = 1, int b)
//{
//
//}
//声明跟实现只能有一个有默认参数
int func2(int a = 10, int b = 20);
int func2(int a, int b)
{
	return a + b;
}

int main()
{
	int num;
	num= func();
	cout << num << endl;
	num = func(1,2,3);
	cout << num << endl;
	num = func(1,2);
	cout << num << endl;
	return 0;
}

输出结果:

31:函数重载

函数重载的核心要求是:函数名相同,但参数列表(参数的类型、数量或顺序)不同。编译器会根据调用时传递的实参,自动匹配最匹配的函数版本。

三个函数同名(均为func),但参数列表不同(无参、int参数、double参数),因此构成重载。

void func()
{
	cout << "打印" << endl;
}
void func(int a)
{
	cout << "打印int" << endl;
}
void func(double a)
{
	cout << "打印double" << endl;
}
void kik(const int& a)
{
	cout << "打印const int& a" << endl;
}
void kik( int& a)//是否含有const 也能作为函数重载条件
{
	cout << "打印const int& a" << endl;
}
int main()
{
	func();
	func(10);
	func(1.555);
}

32:this指针

this 指针就是对象用来指向“自己”的一个指针。

1:解决命名冲突

class Person {
public:
    int age;

    Person(int age) {
        // this 是一个指针,指向当前对象
        // this->age 意思是:当前对象的 age
        this->age = age; 
    }
};

为了告诉编译器:“左边的 age 是我这个对象的 age,右边的 age 是传进来的参数 age”,我们需要使用 this

2:链式编程 —— 返回对象本身 (*this)

  • this 是指针(地址)

  • *this 是解引用(对象本体

#include <iostream>
using namespace std;

class Person {
public:
    int age;

    Person(int age) {
        this->age = age;
    }

    // 注意返回值类型:Person& (引用,返回本体)
    Person& growUp() {
        this->age++; 
        // 返回 *this,就是返回当前对象自己
        return *this; 
    }
};

void test() {
    Person p1(10);

    // 链式编程
    // p1.growUp() 执行完后,返回了 p1 本体
    // 然后紧接着又调用 .growUp()...
    p1.growUp().growUp().growUp(); 

    cout << "最终年龄: " << p1.age << endl; // 输出 13
}

int main() {
    test();
    return 0;
}

33:运算符+重载

1:为什么要“重载”?

  • 对于整数 (int):编译器知道 1 + 1 = 2

  • 对于自定义类 (Person):如果不写代码,编译器不知道 Person p1 + Person p2 是什么意思。是把两人年龄相加?还是体重相加?还是把名字拼起来?

运算符重载的目的:赋予 +-*<< 等符号新的含义,让它们能直接用于你写的对象。

2:核心语法

C++ 提供了一个关键字:operator。 把它和运算符连起来,就构成了一个特殊的函数名

  • 函数名:operator+

3. 实战案例:让两个坐标相加

假设我们有一个 Point 类,代表坐标 (x, y)。我们要实现 p1 + p2,也就是让 xx 加,yy 加。

写法一:成员函数重载

这种写法是将运算符函数写在类在这个里面

逻辑p1 + p2 相当于 p1 调用了一个函数,参数是 p2。即 p1.operator+(p2)

#include<iostream>
using namespace std;

class Point
{
public:
    int m_X;
    int m_Y;

    Point(int x, int y) : m_X(x), m_Y(y) {} // 初始化列表写法

    // 成员函数重载 + 号
    // 返回值:Point (加完后生成一个新的点)
    // 参数:const Point& p (我们要加的那个点,只读,用引用节省内存)
    Point operator+(const Point& p)
    {
        Point temp(0, 0);
        // this->m_X 是自己的 x (左操作数)
        // p.m_X 是传进来的那个人的 x (右操作数)
        temp.m_X = this->m_X + p.m_X;
        temp.m_Y = this->m_Y + p.m_Y;
        return temp; // 返回结果
    }
};

void test01()
{
    Point p1(10, 10);
    Point p2(20, 20);

    // 编译器看到 + 号,且两边是 Point 对象,就会去找 operator+
    Point p3 = p1 + p2; 

    cout << "p3.x = " << p3.m_X << " p3.y = " << p3.m_Y << endl;
}

int main() {
    test01();
    return 0;
}

代码中:
Point p3 = p1 + p2;

编译器的实际执行逻辑:
Point p3 = p1.operator+(p2);

  • 调用者p1 主动调用了函数(所以函数里的 this 指向 p1)。

  • 参数p2 被当作参数传进去了(对应函数声明里的 const Point& p)。

  1. Point temp(0, 0);

    • 系统在栈区内存里,开辟了一个临时空间,造了一个叫 temp 的对象。

    • 此时 temp 的 x=0, y=0。

  2. temp.m_X = this->m_X + p.m_X;

    • this->m_X:取 p1 的 x(比如 10)。

    • p.m_X:取 p2 的 x(比如 20)。

    • 相加得 30,赋值给 temp 的 x。

  3. temp.m_Y = this->m_Y + p.m_Y;

    • 同理,算出 temp 的 y。

此时,函数内部的局部变量 temp 已经装好了结果 (30, 30)。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐