C++全系列学习基础篇,C++新手入门学习教程
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',表示字符串结束。
string 是 C++ 标准库类,定义在 <string> 头文件中。
int main()
{
char str[] = "hello";
string st2 = "hello2";//字符串
cout << str << endl;
cout << st2 << endl;
return 0;
}
输出结果:

4:布尔型
-
bool是布尔类型,只能取两个值:true或false。 -
在内存中:
-
true通常存为1 -
false通常存为0
-
int main()
{
bool flag = true; // 布尔型
cout << flag << endl;
flag = false;
cout << flag << endl;
return 0;
}
输出结果:

5:++:前置递增,后置递增
-
++a(前置):先让变量自增,再参与表达式求值。 -
a++(后置):先拿变量当前值参与表达式求值,再自增。 -
因此同样是从 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”的
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;
}
执行流程:
-
先计算“控制表达式”的值 。
-
依次与各个 case 标签做相等比较,找到匹配后从该标签开始顺序向下执行,直到遇到
break或 switch 结束。 -
若全部不匹配且有 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 语句的核心功能是打破正常的顺序执行流程,直接跳转到指定标记处
-
首先执行
cout << "1" << endl;,输出1。 -
遇到
goto FLAG;语句,程序会直接跳转到FLAG:标记所在的位置。 -
跳过了
cout << "2" << endl;这行代码(因此不会输出2)。 -
从
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函数中a和b的内存值。
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结构体中,stu是student类型的成员,即 “老师” 结构体中包含了 “学生” 结构体,形成了嵌套关系。如 “老师” 不仅有自己的 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);调用时,会在内存中为形参s(struct student s)开辟一块新空间,将实参chen的所有成员(age=10、name="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本身是局部变量(存放在栈区),但它存储的是堆区内存的地址 —— 这是堆区使用的典型方式:用栈区的指针指向堆区的内存。
- 当
func()执行完return p;后,栈区的局部变量p(存储堆区地址的指针)会被自动销毁 —— 意思是 “栈区里专门存地址的那块小空间被回收了”。 - 但
p指向的堆区内存(存储10的空间)完全没变化—— 堆区的地址(比如0x7ffd...)是内存的物理位置标识,不会因为栈区指针被销毁而消失,堆区数据10也依然存在。 func()返回时,会把p存储的堆区地址(比如0x7ffd...)作为返回值,赋值给main()里的栈区指针y。- 此时
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共享同一块内存 —— 可以理解为 “b是a的另一个名字”。
对引用的所有操作,本质上都是对它所关联的原变量的操作。
int main()
{
int a = 10; //创建了一个存储单元
int& b = a; //起了一个“第二个名字”来访问这块内存。b 看起来像一个变量,但底层没有自己的存储空间。 不能指向别人了
b = 20;
cout << "b="<<b << endl;
cout << "a=" << a << endl;
return 0;
}
输出结果:
27:引用传递,指针传递的区别
指针传递:
指针是独立的变量(占内存空间,64 位系统中 8 字节),存储的是原变量的地址。
函数内部的指针参数(a、b)是外部地址的拷贝(即指针本身是值传递),但因存储的是地址,通过解引用可操作原变量。
引用传递:
引用不是独立变量,没有自己的内存空间,它是原变量的 “别名”(与原变量共享同一块内存)。
函数内部的引用参数(a、b)本质上就是外部变量(c、d)本身,操作引用等价于直接操作原变量。
////值传递
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. 引用作为函数返回值的风险:返回局部变量的引用
函数返回引用时,本质是返回变量的 “别名”,但如果返回的是栈区局部变量,会导致未定义行为。栈区局部变量a在test()函数执行结束后会被自动释放,其内存空间被回收(可能被其他数据覆盖)。此时返回的引用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,也就是让 x 和 x 加,y 和 y 加。
写法一:成员函数重载
这种写法是将运算符函数写在类在这个里面。
逻辑: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)。
-
Point temp(0, 0);-
系统在栈区内存里,开辟了一个临时空间,造了一个叫
temp的对象。 -
此时
temp的 x=0, y=0。
-
-
temp.m_X = this->m_X + p.m_X;-
this->m_X:取p1的 x(比如 10)。 -
p.m_X:取p2的 x(比如 20)。 -
相加得 30,赋值给
temp的 x。
-
-
temp.m_Y = this->m_Y + p.m_Y;-
同理,算出
temp的 y。
-
此时,函数内部的局部变量 temp 已经装好了结果 (30, 30)。
更多推荐



所有评论(0)