【C++】重复代码分模块
初识函数
定义函数
返回类型 函数名称(形参)
{
函数体
}

调用函数
auto 返回值 = 函数名称(实参)

返回值

Void的类型的函数没有返回值

定义函数时可以使用auto,其返回值会根据函数返回类型决定

函数的栈区问题
可以看到,函数的形参并不会改变函数外实参的值,且函数的形参是单独分配的另一块栈区空间

函数与变量的作用域
全局变量
| 全局变量 | 函数外部声明的变量 |
|---|---|
| 作用域 | 覆盖整个程序 |
| 生命周期 | 与程序一致,从程序启动到结束始终存在 |

静态全局变量
| 特性 | 静态全局变量 | 普通全局变量 |
|---|---|---|
| 作用域 | 仅当前文件 | 整个程序(或文件,取决于链接性) |
| 链接性 | 内部链接性(仅当前文件可见) | 外部链接性(可被其他文件访问) |
| 访问方式 | 无法被其他文件直接访问 | 可通过extern在其他文件中访问 |
| 可以用指针在外部访问 |

局部变量
| 局部变量 | |
|---|---|
| 作用域 | 仅限于定义它的代码块 |
| 生命周期 | 从定义开始,到代码块结束 |
| 在栈上分配内存,代码块执行完毕后自动释放 |

静态局部变量
在 C++11 之前,静态局部变量的初始化不是线程安全的。
如果多个线程同时进入包含静态局部变量的函数,可能会导致未定义行为(例如,多次初始化或竞争条件)。通常需要手动使用互斥锁(mutex)来保护静态局部变量的初始化。
在 C++11 及之后,静态局部变量的初始化是线程安全的
| 静态局部变量 | |
|---|---|
| 作用域 | 仅限于定义它的代码块 |
| 生命周期 | 贯穿程序的整个运行期间 |
| 第一次运行时申请空间代码块执行完毕后自动释放 |
普通局部变量:
每次调用Function 时,都会被重新创建并初始化为 0。
它的生命周期仅限于函数的执行期间,函数返回后即被销毁。
因此,每次调用函数时,值都会重置为0,然后递增到1。
静态局部变量:
静态局部变量在程序的整个生命周期内只被初始化一次(首次进入函数时)。
它的生命周期贯穿整个程序运行期间,即使函数返回,它的值也会被保留。
因此,每次调用函数时,值都会在上一次的基础上递增。

函数指针和引用
指针
| . | . | . |
|---|---|---|
| 指针 | => | 内存地址 |
| 指针变量 | => | 存放指针的变量 |
&x 获取变量的内存地址

定义一个指针*p 指向变量的内存地址,
*p 输出结果为内存上的值
p指针本身输出结果为内存地址

指针本身也有地址&p

指针可以定义为空指针,但注意是nullptr;
而不是NULL,在C++中NULL只表示 0

引用(别名)
引用是变量的别名,一旦绑定到某个变量,就不能再指向其他变量。
语法:通过&符号声明
使用引用可以避免值传递带来的性能开销,同时允许函数修改原变量。
int a = 10;
int &ref = a; // ref 是 a 的引用
引用不存在空引用

引用的使用

指针和引用的对比
| 特性 | 引用 | 指针 |
|---|---|---|
| 声明 | int &ref = a; |
int *ptr = &a; |
| 初始化 | 必须初始化 | 可以不初始化(但未初始化的指针是危险的 |
| 重绑定 | 不可重绑定 | 可以指向其他变量(ptr = &b;) |
| 空值 | 不存在空引用 | 可以是空指针(nullptr) |
| 解引用 | 无需解引用操作,直接使用 | 需要解引用操作(*ptr) |
| 内存地址 | 引用与原变量共享内存地址 | 指针存储变量的内存地址 |
| 安全性 | 更安全,避免空指针问题 | 需要小心管理,避免野指针 |
指针和引用作为函数参数
| 特性 | 指针作为参数的优点 | 引用作为参数的优点 |
|---|---|---|
| 灵活性 | 支持动态内存分配和可选参数(可为空)可通过指针的引用修改指针本身的值 | 语法更简洁,无需解引用操作 必须绑定到有效对象,避免空指针问题 |
| 安全性 | 需要小心管理指针的生命周期,避免野指针或内存泄漏 | 引用必须初始化且不能为空,代码更安全 |
| 代码可读性 | 代码稍显复杂,需要检查空指针 | 代码更简洁,易读 |
| 应用场景 | 动态内存管理 ,可选参数处理 , 数组和复杂数据结构的操作 | 参数必须有效且不能为空,简化代码逻辑,提高可读性 |
指针作为参数

如果不更改变量,一定要用const

引用作为参数
引用同理,如果不更改变量,要用const

返回多个值
指针和引用一个是访问内存地址,一个是绑定变量,因此可以访问并修改函数外部定义的变量,从而当作返回值

普通函数修改的只是函数局部变量只在函数内,无法带出函数

指针和引用在返回值中的作用
返回一个指针,且指针类型能被自动推导

返回一个引用,但是引用类型不能被自动推导,需要auto &


函数默认值和重载
函数的
默认参数是指在定义函数时为参数指定一个默认值。当调用该函数时,如果没有为这些参数提供对应的实参,函数会使用定义的默认值。
函数的声明与定义
函数的声明,后面没有代码块

函数的定义,后面跟代码块

如果函数有声明,默认参数只能在声明中定义

如果有声明,又在定义中有默认参数,会报错

重载函数
重载函数是指在同一个作用域内定义多个同名函数,但这些函数的参数列表不同(参数的数量、类型或顺序不同)。
__FUNCSIG__是一个预定义宏,主要用于获取当前函数的签名(包括返回类型、函数名和参数类型)

函数数组和引用
在普通情况下,可以拿到数组占用大小

而在函数中,拿不到数组的大小
因为数组经过函数会变成指针,指针占用8字节

解决方法是传入大小的参数

因为会变成指针,因此如果返回数组,注意是指针类型

数组引用
如果函数的参数是引用的数组,可以通过函数拿到数组的大小

如果要返回数组,因为函数无法返回引用类型,所以就要先定义一个数组类型,然后把数组类型变为引用

Vector数组与函数
定义一个名为 TestVector 的函数,该函数接收一个 vector

在函数中引用数组

值得注意的是,函数中引用的数组是函数复制的vdatas,而不是函数外的vdatas
他们地址并不相同

且无法对函数外数组进行修改

但是我们可以通过函数的返回值将函数的操作带出来

且他们的地址是一样的

不过只是将函数复制的数组返回了出来,并不是之前的数组

在C++11之后,可以让函数不复制数组,而是直接用原数组
可以看到,函数内外数组首地址相同

当然,要注意是引用&,否则函数不复制遍历时还是会复制

本质是将数组这些内存移动(move)到了函数中,因此代价是函数外数组“空了”

如果还要数组,就直接用返回出来的数组也是一样的

Vector数组与函数的引用
如果函数传入的参数是引用的数组,会发生什么有趣的现象呢

经测试发现,原数组和函数引用的数组是同一地址,但是函数返回出来后地址改变

多文件编译与函数
.h(.hpp)文件和.cpp(.cc)文件
.h文件(头文件)是用于声明函数、类、结构体、宏等代码元素的文本文件。
它们为程序的不同部分提供了一种共享代码的方式,是模块化编程的关键组成部分。

添加头文件

选择和命名

.cpp文件(源文件)是用于实现函数、类方法、全局变量等代码元素的文本文件。
它们是C++程序的核心组成部分,包含具体的逻辑实现。

同理添加一个cpp文件

在.h文件中自带一条预处理宏
#pragma once是一个非标准但广泛支持的预处理指令,用于防止头文件被重复包含(即单个cpp中只会引用一次头文件)

声明一个全局变量
extern是一个存储类说明符,用于声明变量或函数在其他文件中定义。它主要用于实现跨文件的变量和函数共享。

在base16.cpp中进行定义和初始化

在另一个cpp文件中引入头文件的前提下,可以使用该变量

如果在.h中直接定义和初始化呢?
这样会导致在多个cpp文件引入.h时,变量重定义

预处理宏
在上文中说了#pragma once 的用法,它会使.h在单个cpp中只引用一次

通过以下方法也可以实现
#ifndef BASE16_H如果没有检测到BASE16_H宏,则执行下面的命令
#define BASE16_H定义一个BASE16_H宏
//宏执行的代码
#endif宏到此结束
该代码在第二次运行时,因为检测到了宏,所以不会执行

在编译效率上#pragma once 更高;
在兼容性上#ifndef全版本兼容
函数的声明和定义
在.h中声明函数

在.cpp中进行定义

在另一个.cpp文件中调用

如果要定义C语言函数,则使用extern "C"

在.cpp中定义时也要加

在另一个.cpp文件中调用

还可以声明一组C语言函数

定义同理

在使用C语言库时会用到
string字符串指针和vector的转换
字符串指针到vector的转换
定义一个
const char*类型的字符串指针cstr,指向字符串"测试const char * 到vector"。
使用strlen函数计算字符串的长度。strlen返回的是字符串中字节的数量,而不是字符的数量。

使用
vector<unsigned char>构造vector数组,将字符串cstr的内容复制到数组data中
cstr是指向字符串的指针,cstr + size + 1是字符串的末尾,size + 1 是为了包含字符串的终止符 \0。
每个字符(包括终止符 \0)都被逐字节复制到 vector
中。

data.data() 返回数组的底层数据指针,类型为 unsigned char*。
data.size()输出数组的大小即,字符串的字节数,包括终止符 \0。
size输出 strlen(cstr) 的结果,即字符串的字节数(不包括终止符 \0)。

输出结果如图

字符串数组到vector的转换
定义了一个字符数组
astr,并初始化为字符串"测试数组到vector"。
字符串字面量会包含一个隐式的终止符 \0,因此 astr 的实际大小会比字符串的可见字符数多 1。
sizeof(astr)返回的是整个数组的字节大小,包括字符串内容和终止符 \0。

使用
vector<unsigned char>的构造vector数组,将astr的内容复制到data中。
astr是指向数组的指针,astr + sizeof(astr)是数组的末尾
每个字符(包括终止符 \0)都被逐字节复制到数组中。

data.data()返回数组的底层数据指针,类型为unsigned char*。
sizeof(astr)输出数组的总字节数(包括终止符 \0)。
data.size()输出vector数组的大小(即字符串的字节数,包括终止符 \0)。

string字符串到vector的转换
代码如下

可以发现
vector数组并没有比str多一位/0,所打印的vector后面存在乱码
这是因为data中的内容不是以\0 结尾的有效字符串,直接输出会导致未定义行为或输出乱码。
解决方式是手动加一个/0
重新运行发现数组恢复到正常的内存大小19,且输出正常无乱码

另一种方法是将string字符串转化为前面的字符串指针类型,存入到vector数组中

vector转string
str本来内存为18,存到数组后变为19,又转化为字符串将\0也存到了新的字符串里


浙公网安备 33010602011771号