此专栏为移动机器人知识体系下的编程语言中的 C {\rm C} C++从入门到深入的专栏,参考书籍:《深入浅出 C {\rm C} C++》(马晓锐)和《从 C {\rm C} C到 C {\rm C} C++精通面向对象编程》(曾凡锋等)。
3.C++的数组和函数
3.1 一维数组概述
-
一维数组定义和初始化。
// 1.数组是一组相同类型数据的集合,数组中的每一个元素通过下标表示其在数组中的位置; // 2.一维数组的定义格式: 数据类型 数组名[常量表达式]; // 2.1 数据类型:数组中元素的类型; // 2.2 数组名:该数组的名称; // 2.3 常量表达式:表示数组中元素的个数,必须是一个整数; // 3.定义数组时对数组进行初始化; // 3.1 对数组中所有元素赋值; // 初始化赋值的一般形式: 数据类型 数组名[常量表达式]={值,值,...,值}; int array1[] = {1,2,3,4,5}; int array2[5] = {1,2,3,4,5}; // 3.2 对数组中部分元素赋值; int array3[5] = {1,2,3};
-
一维数组的引用。
// 数组必须先声明后使用,数组中元素由数组名和下标唯一标识的; // 数组元素的一般引用形式: 数组名[下标表达式]
-
一维数组的内存结构和寻址。
-
数组在内存中占有内存单元,它们存在于一组连续的存储单元中;
-
定义数组:
int array[5]={1,2,3,4,5};
-
上述数组存储结构如下:
-
编译器在内存中开辟长度为 5 5 5的区域,假设整型数据占两字节,该数组的起始地址为: 8000 8000 8000,如上数组内存分配如下表:
内存地址 内存地址 内存地址 内容 内容 内容 8000 8000 8000 1 1 1 8002 8002 8002 2 2 2 8004 8004 8004 3 3 3 8006 8006 8006 4 4 4 8008 8008 8008 5 5 5 -
数组中元素在内存中按顺序依次存放,地址是连续的;
-
数组名是该数组的起始地址,即 a [ 0 ] {\rm a[0]} a[0]元素的地址;
-
数组是按顺序存储的,元素寻址计算公式如下:
addr[i]=addr[0]+i*w // addr[i]:表示数组中第i个元素的地址; // w:表示每个元素占据的存储空间大小;
-
3.2 二维数组概述
-
二维数组的定义和初始化。
// 1.二维数组的定义格式: 数据类型 数组名[常量表达式1][常量表达式2]; // 常量表达式1表示第一维长度,称为行; // 常量表达式2表示第二维长度,称为列; int a[4][3]; // 定义一个大小为:4×3的二维整型数组; // 2.按行对二维数组初始化; int a[2][3] = {{1,2,3},{4,5,6}}; // 3.按数组排列顺序对二维数组初始化; int a[2][3] = {1,2,3,4,5,6}; // 4.对二维数组中部分元素初始化; int a[2][3] = {{1},{4}}; // 5.维度省略:对所有元素初始化时,可以不指定第一维的长度,但第二维度的长度不可省略; int a[][3] = {1,2,3,4,5,6};
-
二维数组的引用。
// 1.二维数组引用格式: 数组名[下标表达式1][下标表达式2]
-
实例 ( e x a m p l e 3 _ 1. c p p ) ({\rm example3\_1.cpp}) (example3_1.cpp):求二维数组的最小值。
/** * 作者:罗思维 * 时间:2023/10/08 * 描述:求二维数组中的最小值。 */ #include <iostream> using namespace std; int main() { int array[2][3] = {{1, 2, 3}, {4, 5, 6}}; int arrayMin = array[0][0]; // 假设二维数组中第一个元素为最小值; for (int i = 0; i < 2; i++) { for (int j = 0; j < 3; j++) { if (array[i][j] < arrayMin) { arrayMin = array[i][j]; } } } cout << "二维数组中的最小值为:" << arrayMin << endl; return 0; }
-
二维数组的内存寻址。
二维数组 a [ m ] [ n ] {\rm a[m][n]} a[m][n]中元素的寻址计算公式: a d d r [ i ] [ j ] = a d d r [ 0 ] [ 0 ] + ( i × n + j ) × w {\rm addr[i][j]=addr[0][0]+(i\times{n}+j)\times{w}} addr[i][j]=addr[0][0]+(i×n+j)×w, n {\rm n} n为二维数组中第二维的维数, w {\rm w} w为每个元素所占存储空间的大小。
3.3 多维数组概述
-
多维数组的定义和初始化。
// 1.多维数组定义格式: 类型标识符 数组名[常量表达式1][常量表达式2][常量表达式3]... int a[2][3][4]; float f[2][3][4][5]; // 2.多维数组初始化: int a[2][3][2] = {{{1,2},{3,4},{5,6}},{{7,8},{9,10},{11,12}}}; int a[2][3][2] = {1,2,3,4,5,6,7,8,9,10,11,12}; int a[2][3][2] = {{{1},{3},{5}},{{7},{9},{11}}}; int a[][3][2] = {1,2,3,4,5,6,7,8,9,10,11,12};
-
多维数组的引用。
// 1.多维数组的引用格式: 数组名[下标表达式1][下标表达式2][下标表达式3]...
-
三维数组的内存寻址。
三维数组 a [ m ] [ n ] [ a ] {\rm a[m][n][a]} a[m][n][a]中元素的寻址计算公式: a d d r [ i ] [ j ] [ k ] = a d d r [ 0 ] [ 0 ] [ 0 ] + ( i ∗ n + j ∗ a + k ) ∗ w {\rm addr[i][j][k]=addr[0][0][0]+(i*n+j*a+k)*w} addr[i][j][k]=addr[0][0][0]+(i∗n+j∗a+k)∗w, n {\rm n} n为第二维维数, a {\rm a} a为第三维维数, w {\rm w} w为每个元素所占的内存大小。
3.4 字符数组概述
-
字符数组定义:用来存放字符量的数组称为字符数组,即字符数组中每一个元素都是字符类型;
-
字符数组的定义和初始化:
// 1.定义字符数组: char c[12]; // 定义一维字符数组; char c[3][4]; // 定义二维字符数组; // 2.字符数组初始化; char c[12] = {'W','i','l','l','a','r','d'}; char c[] = {'W','i','l','l','a','r','d'};
-
字符数组的引用 ( e x a m p l e 3 _ 2. c p p ) ({\rm example3\_2.cpp}) (example3_2.cpp):通过下标引用。
/** * 作者:罗思维 * 时间:2023/10/08 * 描述:定义一个字符数组,输出字符数组的内容。 */ #include <iostream> using namespace std; int main() { char charArray[5][5] = {{'J', 'i', 'n', 'D', 'i'}, {'S', 'i', 'W', 'e', 'i'}}; for (int i = 0; i < 5; i++) { for (int j = 0; j <= 4; j++) { cout << charArray[i][j]; } cout << endl; } return 0; }
-
利用字符数组操作字符串。
字符串以’\ 0 0 0’作为结束符,当把一个字符串存入一个数组时,结束符’\ 0 0 0’也存入了数组,并以此作为该字符串结束的标志。
// 1.用字符串对数组初始化。 char c[] = {"Willard"}; char c[] = "Willard"; // 2.C++中的memset()函数原型如下: void *memset(void *dest,int c,size_t count); // 参数说明: // dest:目标缓冲区,即需要处理的字符串变量; // c:要设置的字符,即将目标缓冲区每一个地址(元素)上的值设置为这个字符; // count:字节数,即缓冲区的长度;
// example3_3.cpp /** * 作者:罗思维 * 时间:2023/10/08 * 描述:格式化字符数组。 */ #include <iostream> #include <cstring> using namespace std; int main() { char str[10]; memset(str, 0x00, sizeof(str)); // 字符数组格式化,全部赋值为空; cout << str << endl; // 输出内容为空; return 0; }
3.5 实战1
项目需求:用 C {\rm C} C++实现两个矩阵的乘法运算。
需求分析:矩阵乘法运算规律 M m 1 × n 1 × N n 1 × n 2 = Q m 1 × n 2 {\rm M_{m_1\times{n_1}}\times{N_{n_1\times{n_2}}}=Q_{m_1\times{n_2}}} Mm1×n1×Nn1×n2=Qm1×n2,矩阵 M {\rm M} M的列数必须等于矩阵 N {\rm N} N的函数,矩阵 Q {\rm Q} Q的行数等于矩阵 M {\rm M} M的列数,列数等于矩阵 N {\rm N} N的列数。
代码实现 ( p r o j e c t 3 _ 1. c p p ) ({\rm project3\_1.cpp}) (project3_1.cpp):
/**
* 作者:罗思维
* 时间:2023/10/08
* 描述:实现两个矩阵的乘法运算。
*/
#include <iostream>
using namespace std;
int main()
{
int arrayM[3][4] = {{1, 2, 3, 4}, {3, 4, 5, 6}, {7, 8, 9, 2}};
int arrayN[4][3] = {{8, 7, 6}, {1, 3, 4}, {5, 6, 7}, {8, 9, 6}};
int i, j, k;
int arrayQ[3][3] = {{0}, {0}, {0}};
// 输出矩阵M的元素
cout << "矩阵M如下所示:" << endl;
cout << "===========================" << endl;
for (i = 0; i < 3; i++)
{
for (j = 0; j <= 3; j++)
{
cout << arrayM[i][j] << "\t";
}
cout << endl;
}
cout << "===========================" << endl;
// 输出矩阵N的元素
cout << "矩阵N如下所示:" << endl;
cout << "===========================" << endl;
for (i = 0; i < 4; i++)
{
for (j = 0; j <= 2; j++)
{
cout << arrayN[i][j] << "\t";
}
cout << endl;
}
cout << "===========================" << endl;
// 对矩阵进行乘法运算;
cout << "矩阵Q如下所示:" << endl;
cout << "===========================" << endl;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
for (k = 0; k < 4; k++)
{
// 将对应的行和列相应元素相乘并累加;
arrayQ[i][j] = arrayQ[i][j] + arrayM[i][k] * arrayN[k][j];
}
}
}
// 输出目标矩阵的元素;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
cout << arrayQ[i][j] << "\t";
}
cout << endl;
}
return 0;
}
3.6 函数概述和定义
-
函数可以看作程序员定义的功能模块,每个函数都实现一系列的操作,一个程序可以包含若干个函数,但只有一个 m a i n {\rm main} main函数,程序总是从 m a i n {\rm main} main函数处开始执行;
-
调用函数前必须先定义函数,函数定义由返回值类型、函数名、参数列表和函数体构成;
-
函数定义格式:
返回值类型 函数名(参数列表) { 语句序列; return 返回类型的值; }
- 返回值类型:函数一般有返回值,在函数名前声明其返回值类型,若无返回值,则返回值类型用 v o i d {\rm void} void;
- 函数名:要定义的函数名字,函数命名遵循一定的规则,且要有意义,尽量用英文表达出函数完成的功能;
- 参数列表:当调用函数时输入的值称为实参(实际参数),定义函数时的参数称为形参(形式参数);
- 函数体:函数要执行的操作,当返回类型为 v o i d {\rm void} void时,函数体不需要 r e t u r n {\rm return} return语句;
-
实例 ( e x a m p l e 3 _ 4. c p p ) ({\rm example3\_4.cpp}) (example3_4.cpp):根据输入字母,实现加减乘除计算。
/** * 作者:罗思维 * 时间:2023/10/08 * 描述:用函数实现简易的计算器。 */ #include <iostream> using namespace std; // 函数声明 void calcAdd(float a, float b); void calcSub(float a, float b); void calcMul(float a, float b); void calcDiv(float a, float b); int main() { char flag; float number1, number2; cout << "请输入两个数:"; cin >> number1 >> number2; cout << "请输入何种计算类型(A:加法,S:减法,M:乘法,D:除法,T:全部):"; cin >> flag; switch (flag) { case 'A': calcAdd(number1, number2); // 函数调用; break; case 'S': calcSub(number1, number2); break; case 'M': calcMul(number1, number2); break; case 'D': calcDiv(number1, number2); break; case 'T': calcAdd(number1, number2); calcSub(number1, number2); calcMul(number1, number2); calcDiv(number1, number2); break; default: break; } return 0; } // 函数定义 void calcAdd(float a, float b) { float addResult = 0; addResult = a + b; cout << "number1 + number2 = " << addResult << endl; } void calcSub(float a, float b) { float subResult = 0; subResult = a - b; cout << "number1 - number2 = " << subResult << endl; } void calcMul(float a, float b) { float mulResult = 0; mulResult = a * b; cout << "number1 * number2 = " << mulResult << endl; } void calcDiv(float a, float b) { float divResult = 0.0; if (b == 0) { cout << "Error.除法中分母不能为0." << endl; exit(1); } divResult = a / b; cout << "number1 / number2 = " << divResult << endl; }
3.7 函数原型
-
函数使用需要遵循"先声明后使用"原则,在调用函数前需要声明,函数原型声明由函数返回值类型、函数名和参数列表组成,语法如下:
返回值类型 函数名(参数列表);
-
参数列表由一系列以逗号分隔的参数类型和参数名组成,在函数声明时,参数名可以省略,在函数定义时,形参必须命名,参数列表可以为空,但不能省略,在没有任何形参时可以使用空参数或关键字 v o i d {\rm void} void;
-
函数原型的声明是一个语句,以分号结束;
-
在函数的定义和声明时,需保证函数原型与函数首部写法一致,即函数返回值类型、函数名、参数个数、参数类型和参数顺序保持一致;
-
在函数调用时,函数名、实参类型和参数个数应与函数原型一致;
-
函数定义的位置可在主函数后,亦可在主函数前,如果被调用函数定义在主函数前,则主函数可以直接调用此函数,不必进行声明,如果被调用函数定义在主函数后,则先声明后调用;
-
函数可以在头文件中声明,在源文件中定义,把声明放在头文件中可以使被调用函数与所有声明保持一致,如果函数的接口发生变化,则只需要修改唯一的声明,在使用时,定义函数源文件需要包含声明该函数的头文件;
-
实例 ( e x a m p l e 3 _ 5. h 、 e x a m p l e 3 _ 5. c p p 、 e x a m p l e 3 _ 5 _ m a i n . c p p ) ({\rm example3\_5.h、example3\_5.cpp、example3\_5\_main.cpp}) (example3_5.h、example3_5.cpp、example3_5_main.cpp):
// 函数声明 void func1(char); void func2(float); void func3(int);
#include <iostream> #include "example3_5.h" using namespace std; // 函数定义 void func1(char c) { cout << "字符内容为:" << c << endl; } void func2(float f) { cout << "浮点数内容为:" << f << endl; } void func3(int i) { cout << "整数内容为:" << i << endl; }
#include <iostream> #include "example3_5.h" using namespace std; int main() { // 函数调用 func1('W'); func2(3.14); func3(520); return 0; }
3.8 函数参数
-
C {\rm C} C++中,主函数可以使用函数名和一组由逗号分隔的实参来对函数进行调用,调用的结果是该函数返回值的类型;
-
在形参被成功初始化后,主函数被挂起,被调用函数开始执行,当被调用函数执行结束后,将被调用函数的返回值传入主函数,主函数继续执行;
-
定义函数时使用的是形参,函数被调用时使用的是实参,在 C {\rm C} C++中,函数参数传递的方式:值传递、引用传递和指针传递;
-
值传递:指实参向形参传递的是值,在函数被调用前,形参没有获得内存空间,在函数被调用时,形参才被分配内存单元,然后将实参的值复制到形参中;实参可以是常量、变量和表达式,实参的类型必须和形参类型相同,形参只是复制了实参的值,在被调用函数中形参值的修改不影响实参的值;
-
函数参数值传递实例 ( e x a m p l e 3 _ 6. c p p ) ({\rm example3\_6.cpp}) (example3_6.cpp):
/** * 作者:罗思维 * 时间:2023/10/10 * 描述:函数参数值传递实例。 */ #include <iostream> using namespace std; void Swap(int a, int b); // 函数声明; int main() { int number1 = 11, number2 = 22; cout << "number1 = " << number1 << ",number2 = " << number2 << endl; Swap(number1, number2); // 函数调用; /** * 结果: * number1 = 11,number2 = 22 * number1 = 11,number2 = 22 */ cout << "number1 = " << number1 << ",number2 = " << number2 << endl; return 0; } // 函数定义,实现两个数的交换; void Swap(int a, int b) { int temp; temp = a; a = b; b = temp; }
-
m a i n {\rm main} main函数参数。
// main函数形参定义格式: int main(int argc,char *argv[]) { ... } // 参数说明: // argv:字符串数组; // argc:传递给argv数组的字符串的个数;
-
可变参数长参数:
// 可变参数长参数定义格式: 类型标识符 函数名(...); 类型标识符 函数名(形参1,...);
3.9 函数作用域
- 在函数和类外定义的变量具有全局的作用,称为全局变量, C {\rm C} C++函数体一般包含在一对大括号中,称为语句块,在语句块中定义的变量只具有局部的作用域,即在该函数体中,这些变量称为局部变量,形参是局部变量,局部变量只在其局部的作用域中有效;
- 变量的生存周期从定义时开始,到退出作用域时销毁,局部变量在作用域内才可以使用它们,作用域外不可以使用这些变量;
- 主函数中的变量也是局部变量,只在主函数中有效,不能被其他函数使用,主函数也不能使用其他函数的局部变量;
- 在相同作用域中,变量不可以同名,在不同作用域中,可以使用相同的变量名,它们使用不同的内存,互不干扰;
- 全局变量定义在函数或类的外面,其作用域从定义位置到文件结束,文件中的所有函数都可以使用全局变量,全局变量遵循"先定义,后使用"原则;
- 减少使用全局变量:
- 在程序的整个执行过程中始终占用内存空间;
- 降低程序的可移植性和可读性;
- 当与局部变量同名时,全局变量在局部变量作用域中被局部变量屏蔽;
3.10 函数的嵌套与递归调用
-
函数嵌套:指调用函数时,被调用函数又调用了其他函数,形成嵌套调用关系;
-
函数嵌套实例 ( e x a m p l e 3 _ 7. c p p ) ({\rm example3\_7.cpp}) (example3_7.cpp):
/** * 作者:罗思维 * 时间:2023/10/10 * 描述:函数嵌套实例,计算两个整数的平方差及平方和。 */ #include <iostream> using namespace std; int DifferenceOfTwoSquares(int, int); int SquareSum(int, int); int main() { int number1 = 5, number2 = 3; cout << "5*5+3*3=" << SquareSum(number1, number2) << endl; cout << "5*5-3*3=" << DifferenceOfTwoSquares(number1, number2) << endl; return 0; } // 定义整数平方函数; int Square(int m) { return m * m; } // 定义平方差函数; int DifferenceOfTwoSquares(int a, int b) { return (Square(a) - Square(b)); } // 定义平方和函数; int SquareSum(int a, int b) { return (Square(a) + Square(b)); }
-
递归调用:指函数直接或间接地调用本身,在使用递归调用时,需要在函数内部设置递归终止条件;
-
递归调用实例 ( e x a m p l e 3 _ 8. c p p ) ({\rm example3\_8.cpp}) (example3_8.cpp):
/** * 作者:罗思维 * 时间:2023/10/10 * 描述:函数递归调用实例,输入一个数,计算其阶乘。 */ #include <iostream> using namespace std; int Recursive(int); int main() { int number; cout << "请输入一个大于等于0的整数:"; cin >> number; if (number < 0) { cout << "输入的整数小于0,输入错误,将退出程序." << endl; exit(1); } cout << number << "! = " << Recursive(number) << endl; return 0; } int Recursive(int n) { if (n == 0) { return 1; } else { return (n * Recursive(n - 1)); } }
3.11 内联函数
-
内联函数定义格式:
inline 类型标识符 函数名(参数列表) { 语句序列; return 返回值类型的值; }
-
内联函数在编译时,在调用处用函数体进行替换,没有非内联函数调用时的栈内存的创建和释放开销,节省了参数传递、控制转移等开销;
-
内联函数实例 ( e x a m p l e 3 _ 9. c p p ) ({\rm example3\_9.cpp}) (example3_9.cpp):
/** * 作者:罗思维 * 时间:2023/10/10 * 描述:内联函数实例,输入两个整数,求两个整数之和。 */ #include <iostream> using namespace std; // 声明内联函数; inline int Sum(int, int); int main() { int number1, number2; cout << "请输入两个整数:"; cin >> number1 >> number2; // 调用内联函数; cout << "整数之和为:" << Sum(number1, number2) << endl; return 0; } // 定义求和内联函数; inline int Sum(int a, int b) { return (a + b); }
-
内联函数使用注意事项:
- 内联函数应该简洁,只有几个语句,语句较多时,极大增加程序的代码量;
- 内联函数体内不能有循环语句、 i f {\rm if} if语句和 s w i t c h {\rm switch} switch语句;
3.12 函数模板
-
函数模板:指建立一个通用函数,函数中的数据类型不用具体指定,用一个虚拟的类型来代表,这个通用函数就是函数模板;
-
函数模板定义格式:
template <typename T> // T为虚拟的类型名; template <class T> template <class T1,typename T2>
-
函数模板只适用于函数功能相同、参数个数相同,但类型不同的情况,当函数的参数个数不相同时,不能使用函数模板;
-
函数模板实例 ( e x a m p l e 3 _ 10. c p p ) ({\rm example3\_10.cpp}) (example3_10.cpp):
/** * 作者:罗思维 * 时间:2023/10/10 * 描述:使用函数模板实现简易计算器功能。 */ #include <iostream> using namespace std; template <typename T1> T1 Sum(T1 a, T1 b) { return (a + b); } template <typename T2> T2 Sub(T2 a, T2 b) { return (a - b); } template <typename T3> T3 Mul(T3 a, T3 b) { return (a * b); } template <typename T4> T4 Div(T4 a, T4 b) { return (a / b); } int main() { int iNumber1, iNumber2; float fNumber1, fNumber2; cout << "请依次输入两个整数和两个浮点数:"; cin >> iNumber1 >> iNumber2 >> fNumber1 >> fNumber2; cout << "iNumber1 + iNumber2 = " << Sum(iNumber1, iNumber2) << endl; cout << "fNumber1 + fNumber2 = " << Sum(fNumber1, fNumber2) << endl; cout << "===============================" << endl; cout << "iNumber1 - iNumber2 = " << Sub(iNumber1, iNumber2) << endl; cout << "fNumber1 - fNumber2 = " << Sub(fNumber1, fNumber2) << endl; cout << "===============================" << endl; cout << "iNumber1 * iNumber2 = " << Mul(iNumber1, iNumber2) << endl; cout << "fNumber1 * fNumber2 = " << Mul(fNumber1, fNumber2) << endl; cout << "===============================" << endl; if ((iNumber2 != 0) && (fNumber2 != 0)) { cout << "iNumber1 / iNumber2 = " << Div(iNumber1, iNumber2) << endl; cout << "fNumber1 / fNumber2 = " << Div(fNumber1, fNumber2) << endl; } else { cout << "iNumber2或fNumber2分母为0." << endl; } return 0; }
3.13 实战2
项目需求:提示用户从键盘输入两个整数,求这两个数的最大公约数和最小公倍数。
代码实现 ( p r o j e c t 3 _ 2. c p p ) ({\rm project3\_2.cpp}) (project3_2.cpp):
/**
* 作者:罗思维
* 时间:2023/10/10
* 描述:提示用户从键盘输入两个整数,求这两个整数的最大公约数和最小公倍数。
*/
#include <iostream>
using namespace std;
int gcd(int, int);
int lcm(int, int);
int main()
{
int iNumber1, iNumber2;
cout << "请输入两个非负整数:";
cin >> iNumber1 >> iNumber2;
cout << "两个整数最大公约数为:" << gcd(iNumber1, iNumber2) << endl;
cout << "两个整数最小公倍数为:" << lcm(iNumber1, iNumber2) << endl;
return 0;
}
// 计算最大公约数;
int gcd(int m, int n)
{
if (m > n)
{
swap(m, n);
}
while (n > 0)
{
int r = m % n;
m = n;
n = r;
}
return m;
}
// 计算最小公倍数;
int lcm(int m, int n)
{
return (m * n / gcd(m, n));
}