一.结构体
1.概念:
结构体(struct)是一种用户自定义复合数据类型,其中可以包含不同类型的不同成员
2.结构体的应用场景:
我们在使用多个变量描述一个对象时,虽然也可以做到,但是难免显得杂乱和繁琐,比如我们在描述一个人时,经常会使用例如姓名、年龄、性别等等属性来描述他,那么在这种情况下,我们使用结构体就会显得一切都有序起来
3.结构体的声明定义和使用的基本语法:
#include"iostream"
using namespace std;
int main() {
// 声明结构体
/*
struct 结构体类型 {
成员1类型 成员1名称;
...
成员N类型 成员N名称;
};
*/
// 结构体声明案例:
// 在此案例中我们声明了一个Student结构体,也就是一个学生对象,其中包含name,age,gender三个属性
struct Student {
string name;
int age;
string gender;
};
// 在结构体变量的声明中,可以在前面带上struct关键字(也可以省略不写)
// 但是还是建议写上,可以清晰的知道变量是自定义结构体类型的
Student stu1; // 创建一个叫做stu1的结构体变量
struct Student stu2; // 创建一个叫做stu2的结构体变量
// 为结构体变量赋值
stu1 = {"zhansgan", 18, "男"};
stu2 = {"lisi", 19, "女"};
// 若是想要输出结构体变量,不能使用cout << stu1 << endl;的形式
// 因为结构体变量是一个整体的包装,无法直接cout输出
// 那么如果想要输出的话,就需要访问它的每一个成员进行输出,访问语法:结构体变量.成员名称
cout << stu1.name << endl;
cout << stu1.age << endl;
cout << stu1.gender << endl;
cout << stu2.name << endl;
cout << stu2.age << endl;
cout << stu2.gender << endl;
//我们还可以在初始化一个结构体变量时就对其中的数值进行定义:
struct Student stu3 = {"wangwu", 20, "男"};
cout << stu3.name << endl;
cout << stu3.age << endl;
cout << stu3.gender << endl;
return 0;
}
4.结构体成员默认值:
在我们设计一个结构体的时候,可以根据需要向成员设置默认值
如下:
#include"iostream"
using namespace std;
int main() {
// 定义一个Student结构体
struct Student {
string name;
int age = 18; // 默认年龄为18
string gender = "男"; // 默认性别为男
};
// 创建两个结构体变量,一个使用结构体成员默认值,一个不使用
struct Student stu1 = {"zhangsan"};
struct Student stu2 = {"lisi", 20, "女"};
cout << stu1.name << endl;
cout << stu1.age << endl;
cout << stu1.gender << endl;
cout << endl;
cout << stu2.name << endl;
cout << stu2.age << endl;
cout << stu2.gender << endl;
return 0;
}
5.结构体数组
作为用户自定义数据类型,结构体是支持数组模式的
#include"iostream"
using namespace std;
int main() {
/*
// 声明数组对象
[struct] 结构体类型 数组名[数组长度];
// 赋值数组的每一个元素
数组名[0] = {a, b, ...};
数组名[1] = {c, d, ...};
...
//声明和赋值同步的快捷写法
[struct] 结构体类型 数组名[数组长度] = {{第一个数组中的数值}, {第二个数组中的数值}, ..., {第n个数组中的数值}};
*/
struct Student {
string name;
int age;
string gender;
};
struct Student stu[3]; // 结构体数组对象的声明
// 对创建的结构体数组对象进行赋值
stu[0] = {"zhangsan", 18, "男"};
stu[1] = {"lisi", 19, "女"};
stu[2] = {"wangwu", 20, "男"};
// 使用for循环遍历结构体数组对象中的成员
for (int i = 0; i < 3; i++) {
cout << stu[i].name << endl;
cout << stu[i].age <<endl;
cout << stu[i].gender << endl;
cout << endl;
}
// 在对结构体数组声明的同时对其进行赋值:
struct Student student[3] = {
{"chen", 21, "男"},
{"zhang", 22, "女"},
{"li", 23, "男"}
};
for (int i = 0; i < 3; i++) {
cout << student[i].name << endl;
cout << student[i].age <<endl;
cout << student[i].gender << endl;
cout << endl;
}
return 0;
}
6.结构体指针
作为一种数据类型,结构体同样支持使用指针
(1).引入已经存在的结构体地址
(2).通过new操作符申请指针空间
注意:在我们使用指针变量访问结构体成员时需要更换操作符号为:->
#include"iostream"
using namespace std;
int main() {
struct Student {
string name;
int age;
string gender;
};
// 先创建一个结构体对象
struct Student stu = {"zhangsan", 18, "男"};
// 引入已经存在的结构体地址
struct Student *p1 = &stu; // 注意这里的取地址符,也就是&,指针中存储的是地址,必须加上这个符号获取该变量的地址
// 通过结构体指针访问结构体的成员,其中我们需要使用的符号是:->
cout << p1->name << endl;
cout << p1->age << endl;
cout << p1->gender << endl;
// 通过new操作符申请指针空间
struct Student *p2 = new Student {"lisi", 19, "女"};
cout << p2->name << endl;
cout << p2->age << endl;
cout << p2->gender << endl;
return 0;
}
7.结构体指针数组
在结构体中可以使用指针数组,其主要用于动态内存分配,方便管理大量结构体占用的内存
(1).引入已经存在的结构体数组地址
(2).通过new操作符申请指针数组空间
注意:在我们使用指针变量访问结构体指针数组的成员时需要更换操作符号为一开始的:.
#include"iostream"
using namespace std;
int main() {
struct Student {
string name;
// 设置了默认值,省事儿
int age = 18;
string gender = "男";
};
// 引入已经存在的结构体数组地址
// 创建一个结构体数组
struct Student stu1[3] = {{"zhangsan"}, {"lisi"}, {"wangwu"}};
// 数组的存储方式本来就是地址,所以不需要使用取地址符号取出该变量对应的地址
struct Student *p1 = stu1;
// 若要取出结构体指针数组中的成员不再是使用->符号,而是回到.
for (int i = 0; i < 3; i++) {
cout << p1[i].name << endl;
cout << p1[i].age << endl;
cout << p1[i].gender << endl;
}
// 通过new操作符自行申请结构体数组的空间(通过delete操作符回收)
struct Student *p2 = new Student[3] {{"chen"}, {"li"}, {"wang"}};
for (int i = 0; i < 3; i++) {
cout << p2[i].name << endl;
cout << p2[i].age << endl;
cout << p2[i].gender << endl;
}
// 在使用结束之后使用delete操作符释放申请的空间,可以减轻内存压力
delete[] p2;
return 0;
}
二.函数
1.函数的概念:
函数是一个提前封装好的,可以重复使用的,完成特定功能的独立代码单元
2.使用函数的好处:
(1).提前封装:
提前准备好了代码逻辑
(2).可重复使用:
可以多次调用
(3).完成特定功能:
只用来完成我们所希望它完成的部分特定功能
3.函数的使用场景:
我们可以将针对特定功能的、有重复使用需求的代码提前封装到函数内,以便我们在需要的时候随时调用
4.函数的结构:
函数返回值类型 函数名(传入参数1, 传入参数2, ..., 传入参数n) { // 共同构成了函数声明
函数体;
return 返回值;
}
5.基础函数语法
注意:return语句执行之后函数就会立刻结束;函数不可以被定义在main函数内部
#include"iostream"
using namespace std;
// 编写一个函数返回两个传入参数中的较大值
int max(int a, int b) {
int max = a > b ? a : b;
return max;
}
// 注意在return语句执行之后函数就会立刻结束,以及函数不可以被定义在main函数的内部
int main() {
// 调用函数
int a = 10, b = 20;
cout << max(a, b) << endl;
return 0;
}
6.无返回值函数与void类型
函数的返回值并非是必须提供的,也就是说可以声明一个不提供返回值的函数
声明一个不提供返回值的函数时需要做到以下几点:
①:声明函数返回值类型为void(void表示“空”,用于声明无返回值函数)
②:不需要写return语句
③:调用者无法得到返回值
#include"iostream"
using namespace std;
// 编写一个无返回值函数
void love(string name) {
cout << "I love you, my lovely lover——" << name << endl;
}
int main() {
// 调用函数
love("unmasked lover");
love("my beauty");
return 0;
}
7.空参函数
除了返回值以外,函数的传入参数也是可选的,也就是声明一个不需要传入参数的函数(括号是不能省略的嗷)
#include"iostream"
using namespace std;
// 编写一个无返回值,同时无参的函数
void love() {
cout << "I'll betray my instinct to fall in love with you, until the end of my life" << endl;
}
int main() {
// 调用函数
while(1) {
love(); // 无限循环,记得手动暂停嗷(别在没设置停止条件的时候这么写,我写着玩的)
}
return 0;
}
8.函数嵌套调用
函数作为一个独立的代码单元,可以在函数内调用其他函数,这种嵌套调用关系没有任何限制,可以根据需要无限嵌套
#include"iostream"
using namespace std;
void yesturday() {
cout << "I just want to love you" << endl;
}
void today() {
cout << "for three days," << endl;
}
void tomorrow() {
cout << "yesturday, today, and tomorrow." << endl;
}
// 写出本函数调用上方的三个函数,需要注意的是我们需要先定义再调用,也就是说如果我们把函数定义在了调用处的下方就会报错
void love() {
yesturday();
today();
tomorrow();
}
int main() {
// 调用函数
while(1) {
love(); // 无限循环,记得手动暂停嗷(别在没设置停止条件的时候这么写,我写着玩的)
}
return 0;
}
9.参数的值传递和地址传递
在这之前学习的函数的形参声明其实都是在使用“值传递”的参数传入方式,但是像这样的值传递方式会出现问题,例如:
#include"iostream"
using namespace std;
// 这是一个交换两数之值的函数,但是值传递方式根本无法达到我们想要的效果
void exchange(int a, int b) {
int temple = a;
a = b;
b = temple;
}
int main() {
int a = 10, b = 20;
exchange(a, b);
cout << a << " " << b << endl;
// 你会发现像这样值传递的函数根本无法做到交换两个变量的值,这是因为交换的只是传递过去的形参的值,对两个变量中存储的数值而言并没有影响
return 0;
}
那么为了不出现这样的问题,我们就需要进行地址(指针)的传递,将函数体的效果直接作用于内存上
#include"iostream"
using namespace std;
// 地址传递方式下的值交换函数
void exchange(int *a, int *b) {
int temple = *a;
*a = *b;
*b = temple;
}
int main() {
int a = 10, b = 20;
exchange(&a, &b);
// 地址传递方式之下就能够完成我们所想要的值交换效果了
cout << a << " " << b << endl;
return 0;
}
10.函数传入数组
由于数组对象本身只是第一个元素的地址,所以数组传参不区分值传递还是地址传递,从本质上来说都是地址传递(在传入数组时一般建议附带数组长度的传入,因为C++不会检查数组的内存边界)
#include"iostream"
using namespace std;
// 传入函数后的数组由于已经变成了指针的形式,所以大小固定为8
void function(int arr[]) {
cout << sizeof(arr) << endl;
}
int main() {
// 但是在main函数中计算的数组大小为数组长度*数据类型的大小
int arr[3];
// 所以在这里计算出的结果就是3*4(int类型数据的大小)
cout << sizeof(arr) << endl;
function(arr);
return 0;
}
也因为传入函数后的数组有着这样的特点,所以我们很难在函数中计算出该数组的长度,所以一般都会在传入数组后顺带传入该数组的长度
#include"iostream"
using namespace std;
// 在main函数中计算出数组的长度传入函数中,我们在函数里面很难获取数组长度
void function(int arr[], int length) {
for (int i = 0; i < length; i++) {
cout << arr[i] << endl;
}
}
int main() {
int arr[10];
for (int i = 0; i < 10; i++) {
arr[i] = i;
}
int length = sizeof(arr) / sizeof(arr[0]);
function(arr, length);
return 0;
}
11.函数的引用传参
函数的形参还有引用传参这一形式
引用指的是变量的一个别名,它是某个已存在变量的另一个名字
注意:引用不是指针,指针可以修改指向,但是引用不行
引用的特点:
①:引用在创建后不可更改(更改它的指向到其他内存区域)
②:因为引用的不可更改特性,所以引用必须初始化
③:因为引用的必须初始化特性,所以引用不可为空(不可被修改)
引用的语法:
主要使用&表明引用
数据类型& 引用名 = 被引用变量;
举例:
#include"iostream"
using namespace std;
int main() {
int a = 10;
int& b = a;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
return 0;
}
虽然不可以改变引用的指向,但是可以改变引用指向中的内容,例如:
#include"iostream"
using namespace std;
int main() {
int a = 10;
int& b = a;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
a = 20;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
a = 30;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
return 0;
}
12.引用传参
除了值传递和地址传递以外,函数也可以使用引用传递的形式传参,同时引用传参也是最为常见的传参方式
各种函数传参方式的对比:
①:值传递虽然会复制值,但是无法对实参本身产生影响
②:地址传递虽然会复制地址,可以对实参本身产生影响,但是指针的写法麻烦
③:引用传递可以像普通变量那样操作,但是对实参本身可以产生影响
#include"iostream"
using namespace std;
// 定义形参,使用&表示传递引用的对象
void exchange(int &a, int &b) {
int temple = a;
a = b;
b = temple;
}
int main() {
int a = 10, b = 20;
exchange(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
return 0;
}
方式/特征 | 形参定义 | 传递内容 | 对实参的影响 | 是否直接内存操作 |
---|---|---|---|---|
值传递 | 普通变量(int) | 赋值变量值(数据传递) | 无 | 否 |
地址传递 | 指针变量(int*) | 赋值指针值(地址传递) | 有 | 是 |
引用传递 | 引用变量(int &) | 复制引用对象 | 有 | 否 |
13.返回指针的函数以及局部变量的生命周期
函数的返回值可以是一个指针(内存地址),语法为:
返回值类型 * 函数名(传入的形参) {
函数体;
return 指针;
}
注意:
①:在声明中,提供*,表示返回值是指针
②:return所返回的返回值一定要是一个指针变量
但是可能出现语法正确,但无法正常返回结果的情况,例如:
#include"iostream"
using namespace std;
// 定义了一个加法函数
int* add(int a, int b) {
int sum = a + b;
return ∑
}
// 虽然像这样的代码是符合语法的,但是无法正常返回结果
// 因为sum变量是一个局部变量,它的作用域仅在函数内,函数一旦执行完毕,sum变量就会被销毁,根本传不出去
// 这就是C++的静态内存管理的弊端,如果需要进行规避,那么就要用到动态内存管理(new和delete)
int main() {
int a = 10, b = 20;
int* sum = add(a, b);
cout << sum << endl;
return 0;
}
局部变量:
①:在函数内部创建的变量
②:其作用范围仅在函数内部,函数一旦执行完毕就会将此变量销毁并且将其占用的内存空间释放
上述代码想要正确表达出效果的解决方式:
#include"iostream"
using namespace std;
int* add(int a, int b) {
// 动态分配一块空间给sum,这样sum才能正确地传递出去
int* sum = new int;
*sum = a + b;
return sum;
}
int main() {
int a = 10, b = 20;
int* sum = add(a, b);
cout << *sum << endl;
// 使用完毕之后记得delete删除动态分配出去的内存,减轻内存压力
delete sum;
return 0;
}
14.static关键字
(1).static是C++中的一个关键字,并且在众多的编程语言中都有static的应用
(2).static表示静态(将内容存入静态内存区域),可以修饰变量和函数
(3).当static修饰变量时,比如函数中的局部变量,可以将其的生命周期延长到整个程序的运行周期
函数内局部变量的特点: | 函数内static修饰的变量的特点: |
---|---|
函数运行结束后销毁 | 程序运行结束后销毁 |
static关键字的语法:
在变量类型之前加入static关键字即可
例如:
#include"iostream"
using namespace std;
// 使用static关键字将sum变量由局部变量转换为全局变量之后本函数依然能正常生效
int* add(int a, int b) {
static int sum = a + b;
return ∑
}
// 在本例中,sum变量将会获得:
// ①:仅被初始化一次(在函数第一次调用时)
// ②:将持续存在(转换为全局变量,不会因函数运行结束而销毁)
int main() {
int a = 10, b = 20;
int* sum = add(a, b);
cout << *sum << endl;
return 0;
}
15.函数返回数组
由于数组对象本身是第一个元素的地址,所以返回数组的本质就是返回指针
语法要求按照返回指针声明返回值,例如:
变量类型* 函数名() {
...;
return 数组名;
}
其中需要注意的是:返回的数组不可以是局部变量(因为其生命周期仅限函数内),可以返回全局数组或者static修饰的数组
错误案例:
int* function() {
int arr[] = {1, 2, 3};
return arr;
}
// 这样的方式就根本传递不出数组,因为该数组是在函数内部创建的,属于局部变量,一旦函数运行完毕就销毁
正确示范:
int* function() {
static int arr[] = {1, 2, 3};
return arr;
}
int* function() {
int* arr = new int[3]{1, 2, 3};
return arr;
}
int arr[3] = {1, 2, 3};
int* function() {
return arr;
}
16.函数返回数组(改进)
前言:
一般不建议使用函数返回数组的方式
注意:
①:函数接受数组指针传入
②:调用函数前需要自行创建数组
③:传入数组到函数中进行操作
#include"iostream"
using namespace std;
// 定义一个函数为数组中每个元素都+1,因为实际操作的是地址,所以能够正常生效
void plus_one(int* arr, int length) {
for (int i = 0; i < length; i++) {
arr[i] += 1;
}
}
int main() {
int arr[3] = {0, 1, 2};
plus_one(arr, 3);
for (int i = 0; i < 3; i++) {
cout << arr[i] << endl;
}
return 0;
}
17.函数默认值
在定义函数的时候我们可以指定函数的默认值,也就是说,调用函数不传入实参,则使用函数默认值进行代替
注意:
每个提供默认值的参数,其右侧参数也必须提供默认值(当不传递参数时,使用提供的默认值)
使用默认值的函数的案例:
#include"iostream"
using namespace std;
void love(const string& name = "beauty") {
cout << "I love you, my " << name << endl;
}
int main() {
love();
love("little girl");
return 0;
}
错误写法:
void add(int x = 3, int y, int z)
void add(int x, int y = 6, int z)
正确写法:
void add(int x = 3, int y = 6, int z = 9)
void add(int x, int y = 6, int z = 9)
void add(int x, int y, int z = 9)