<C++>3 函数和数组
本文最后更新于:2023年6月27日 上午
3 函数和数组
3.1 函数的定义和使用
函数:将一段代码封装起来,提高复用率
3.1.1 函数的定义
函数的定义一般有 5 个步骤:
-
返回值类型:可以无返回值(void)或返回一个值
-
函数名
-
参数列表:可以无参或设置任意个参数
参数可以不加标识符,但那个场合可能无法调用参数。
-
函数体语句
-
return 表达式:无返回值的场合可以没有
/*
返回值类型 函数名 (参数列表) {
函数体语句
return 表达式
}
*/
void repeat(int n) { // void 的场合是表示没有返回值
while (n-- > 0) {
cout <<
(n % 2 == 0 ? "耽误的时间太多,事情可就做不完了"
: "劳逸结合是不错,但也不要放松过头")
<< endl;
}
}
void repeat(double) { // 这样也行
cout << "前面的区域,以后再来探索吧!" << endl;
}
int getGameID() {
return 100278842;
}
3.1.2 函数的调用
语法:函数名(参数)
void keQing() {
repeat(1); //这就是调用啊!
}
3.1.3 函数的声明
先告诉编译器函数名称及调用方式。之后,再对函数主体进行定义
void chop(int times); //声明了该函数
int main() {
chop(20); //调用了该函数
return 0;
}
void chop(int times) { //这个场合,即使定义在调用之后,也能正常运行
while (times-- > 0) {
cout << "剑光如我,斩尽芜杂!" << endl;
}
}
函数可以多次声明,但定义只能进行一次
3.1.4 标识符的作用域与可见性
#函数原型作用域
函数声明中的标识符,其作用域仅限于该声明中参数列表的括号间,在程序其他地方不能引用该标识符。因此,在函数声明时往往不写形参名,而仅写参数类型
int add(int, int); // 声明时只写函数类型
int add(int a, int b) { // 定义时可以写形参名
return a + b;
}
#局部作用域
程序中使用大括号包裹的程序称为块。作用域局限在块内,即为局部作用域。
函数定义中,形参列表中形参的作用域即从形参声明处开始,到函数体结束为止。
函数体内声明的变量,其作用域从声明处开始,到声明所在块的大括号结束为止。
int act(int n) { // n 的作用域从此处开始,到函数结束为止
int b = 10; // b 的作用域从此处开始,到函数结束为止
for (int i = 0; i < b; i++) { // i 的作用域从此处开始,到该循环括号结束为止
n += i;
}
return n;
}
#类作用域
类可被看作是一组有名字的成员的集合。其成员具有类作用域。
在类内,如果没有声明同名的局部作用域标识符,则类作用域标识符可以直接访问。
在类外,通过表达式 实例名.对象名
或 类名::静态对象名
访问。或者 指针名->对象名
访问
#文件作用域
文件作用域也称单文件作用域。
定义在函数和块外的标识符具有文件作用域。其作用域从定义处(声明处)开始,到整个文件结束。
文件中定义的全局变量和函数都具有文件作用域
#程序作用域
程序作用域也叫多文件作用域。
属于程序作用域的有通过 extern 关键字修饰的外部变量和外部函数等
#命名空间作用域
一个命名空间确定了命名空间作用域。凡在命名空间内的,不属于前述几个作用域的标识符,都是命名空间作用域标识符。具有命名空间作用域的变量也称为全局变量。
在命名空间内部可以直接引用当前命名空间作用域的标识符。
从其他命名空间访问的场合,使用 命名空间名:: 标识符
的方式访问。也能使用 using
语句启用整个命名空间或暴露空间中某特定标识符,届时就能直接访问该空间全部或部分标识符。
#标识符的可见性
-
标识符必须声明在前,引用在后。
-
在同一作用域中,不能声明同名标识符。在没有互相包含关系的不同作用域中,标识符间互不影响。
-
具有相互包含关系的作用域中,外层标识符在内层可见。
内层声明了标识符的场合,那些外层的同名标识符变得不可见。这种机制称为隐藏机制。
3.1.5 函数的分文件编写
分文件编写,使代码结构更清晰
一般有 4 个步骤:
-
创建头文件
xxx.h
-
创建源文件
xxx.cpp
-
在头文件中声明函数
headF1.h:
#include <iostream> using namespace std; void chop(int times);
-
在源文件中定义函数
test.cpp:
#include "headF1.h" //包含自定义的头文件时使用 " " void chop(int times) { while (times-- > 0) { cout << "剑光如我,斩尽芜杂!" << endl; } }
-
在 main 文件中包含该自定义头文件,并调用
#include <iostream> using namespace std; #include "headF1.h" int main() { chop(5); return 0; }
3.1.6 默认参数
C++ 中,函数的形参列表可以有默认值
语法:返回值 函数名(参数 = 默认值) {}
注意事项:
-
如果某个参数有默认值,则该参数之后的所有参数也都要有默认值
-
如果我们传入了自己的数据,该数据会替代默认值。否则,使用默认值
-
如果函数声明有默认参数,则函数实现不能有默认参数。
即,该函数所有的声明和实现中,只能有一处出现默认参数。
否则,运行时报错
int met(int n1, int n2 = 0, int n3 = 0);
int met(int n1, int n2, int n3);
int met(int n1, int n2, int n3) {
return n1 + n2 + n3;
}
3.1.7 占位参数
C++ 中函数的形参列表中可以有占位参数,用来占位。调用函数时要填补该位置
语法:返回值 函数名(数据类型) {}
注意事项:
- 调用函数时要填补占位参数的位置
- 现阶段,无法调用占位参数。但后面的学习有办法调用。
- 占位参数可以有默认参数。
void println(int) {
cout << endl;
}
void print10(int = 10) {
cout << 10;
}
int main() {
print10();
println(10);
}
3.1.8 函数重载
函数名相同,但参数不同,则构成重载。可以提高代码复用性
函数重载满足条件:
- 在同一作用域下
- 函数名相同
- 函数参数的 类型、个数或顺序 不同
- 返回值不能作为重载条件。但在构成重载的函数之间,返回值可以不同
void println() {
cout << endl;
}
void println(int n) {
cout << n;
println();
}
#注意事项:
-
引用作为重载条件的场合:
const
的有无,也能作为重载的条件void met(int& n) { cout << n << endl; } void met(const int& n) { cout << n - 1 << endl; } int main() { int a = 10; met(a); //[1] 输出 10 met(10); //[2] 输出 9 }
-
这个场合,调用
void met(int& n)
-
这个场合,调用
void met(const int& n)
因为,传入的是常量,
int& n = 10
不成立而const int& n = 10
成立—— 见 [6.2.6 常量引用]
-
-
函数重载有默认参数的场合:
void met2(int n1, int n2 = 10) { cout << n1 + n2; } /* void met2(int n){ //出现二义性 cout << n1; } */
这个场合,默认参数本身相当于某种重载,再使用重载时,可能出现二义性
3.1.9 内联函数
在程序中使用函数的目的之一是增加代码复用率,提高程序的开发效率和可维护性
调用函数时,需要保存主调函数的现场和返回地址,在栈中为形参和局部变量分配存储空间,实现实参与形参的参数传递,再将程序转移到被调用函数的起始地址继续执行。执行结束后,要回收占用的存储空间,恢复主调函数的现场,取出返回地址,将返回值赋给函数调用本身,再返回地址开始处继续执行。
调用函数时会产生上述时间、空间开销。当函数体较简单,并被频繁调用时,上述开销会影响程序的性能。
为避免这种情况,C++ 引入了内联函数的概念。
inline 返回值类型 函数名(参数列表) {
...
}
使用内联函数时,编译器在编译时不产生函数调用,而是将程序中出现的每一个内联函数的调用表达式直接用其函数体替换。使用内联函数会让最终可执行程序的体积变大,是一种空间换时间的方式。
代码量较大的函数通常不定义为内联函数,因为其代码增加对时空效率的不利影响很可能超过了内联函数的速度改善。
**加入 inline 关键字并不能保证函数成为内联函数。**该关键字只是提示编译器,程序员希望该函数成为内联函数。但编译器可能根据其自身处理方式忽略这一提示,而生成函数调用。
3.2 数组
数组:一个相同类型元素的集合
特点:
-
数组中每个元素都是相同的数据类型
-
数组是由连续的内存位置组成的
3.2.1 一维数组
三种定义方式:
-
数据类型 数组名[数组长度];
int arr1[10];
-
数据类型 数组名[数组长度] = {值1, 值2, ...};
string strs[4] = {"make", "Poland", "disapper", "again"};
-
数据类型 数组名[] = {值1, 值2, ...};
char waifu[] = {'k', 'e', 'q', 'i', 'n', 'g'};
#动态内存分配:
数组的长度是声明时决定的。C++ 中,不允许定义元素个数不确定的数组(不允许变量作为数组长度)
C++ 语言提供了一种动态内存分配的机制。程序运行期间,根据实际需要,临时分配一段内存空间用以存储数据。这种分配是程序运行期间进行的,故而称为 动态内存分配。
使用 new 运算符实现动态内存分配,以分配任意大小的数组
int n = 15;
int* array = new int[n];
——见 [4.1 指针]
#数组名用途:
-
统计整个数组在内存中的长度
int all = sizeof(waife); //[1] int signal = sizeof(strs[0]); //[2] int num = sizeof(strs) / sizeof(strs[0]); //[3]
- 整个数组占用的内存空间
- 单个元素占用的内存空间
- 数组的元素个数
-
获取数组在内存中的首地址
cout << waifu; //[1] cout << (int)waifu; //[2] cout << &waifu[0]; //[3]
-
数组首地址(16 进制),该地址即首元素的首地址
-
数组首地址(10 进制)
-
某元素的首地址
&
是一个取址符
-
数组名是常量,不可以进行赋值操作
3.2.2 二维数组
四种定义方式:
-
数据类型 数组名[行数][列数];
char chess[8][8];
-
`数据类型 数组名[行数][列数] =