【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也存到了新的字符串里

posted @ 2025-04-21 14:56  plusu  阅读(59)  评论(0)    收藏  举报