以下为个人学习笔记和习题整理
课程:计算机程序设计(C++)- 西安交通大学 @ 中国大学 MOOC
https://www.icourse163.org/course/XJTU-46006

# 课堂笔记

# 模块化程序设计

编写一个规模较大的程序,可按其功能划分为若干相对独立的模块。

好处:

  • 程序开发更易控制
  • 利于软件重用
  • 避免重复代码
  • 容易调试和维护

每个模块由一个函数实现。

# 主函数 main()

  • C++ 程序仅由一个主函数构成。
  • 可以由多个子函数组成
  • 程序的执行顺序:总是从 main 函数开始执行,其中可调用其他子函数,最后在主函数中结束。

# 库函数(标准函数)

由系统提供,可以直接使用,但需包含相应的头文件。如:

#include <iostream> // 标准输入 / 输出函数
#include <cmath> // 数学库函数
#include <cstring> // 字符串处理函数

# 用户定义函数

程序的主要逻辑

# 函数的定义

<函数值类型> <函数名>(<形式参数表 >) // 函数头
{ // 函数体
	<语句序列>
}

# 函数值类型

即函数的返回值类型

  1. 返回简单类型,如: intlongfloatdoublechar
  2. 返回结构类型
  3. 返回指针类型
  4. 返回引用类型

如果函数无任何返回值,这时函数的返回值类型应标记为 voidvoid 类型称为无类型或空类型。

# 形式参数表

即形参表,函数的参数表用于实现函数间的数据联系。

  • 说明格式: <类型1> <形参名1>, <类型> <形参名2>, ... , <类型n> <形参名n>

每个形参必须同时给出形参的类型和名称。
如: int max(int a,b) ,作为函数首部定义是错误的。
因为形参 b 缺少类型说明符,即使与 a 类型相同,但作为形参说明也不能省略类型符 int

  • 定义函数时需考虑设置形参,形参的个数及类型根据需要设定。
    也可以没有参数,称为无参函数。

  • 形参可以接收主调函数传递的实参的值。
    在函数中对形参进行处理,并将处理结果返回到主调函数。

  • 形参是在函数调用时分配存储空间,同时接收实参的值。
    当函数执行结束后,系统将自动释放形参所分配的存储空间。
    因此,形参属于函数的局部变量,只能在该函数中使用。

  • 当形参为引用类型或指针类型时,利用形参不仅可以访问实参的值,还可以改变实参的值。

# 函数体

{ } 括起来的语句序列构成,是实现函数功能的主体。

  • 函数的编写过程类似于主函数。

  • 在函数中可以调用其它函数。

  • 在函数体中,使用 return 语句返回函数执行的结果。

语句格式
return <表达式>; // 其中表达式类型应与函数返回值类型一致
  • 对于无返回值的函数,也可使用 return; 将控制返回到主调函数。

在一个函数中允许出现多个 return 语句,但在函数执行期间只能有一个语句起作用。
在函数体的最后位置,一个无返回值的 return; 语句可以省略。

# 函数的声明

  • 在 C++ 中,程序编译的单位是源程序文件(即源文件),一个由多个函数构成的程序可以组织存放在一个或多个源文件中。

  • 在源文件中,函数之间的排列顺序没有固定的要求,但要满足 “先定义后使用” 的原则。

  • 对于标准库函数的使用,在程序开头使用 #include 命令将所需的头文件包含进来即可。

  • 对于自定义的函数,要么在调用之前定义,要么在调用之前作函数声明
    函数的声明是指在函数被调用之前,对函数的类型、名称以及参数等信息所作的说明。
    格式如下:

    <类型名> <函数名>(<类型1> <形参1>, <类型2> <形参2>,...);
    // 或
    <类型名> <函数名>(<类型1>, <类型2>, ...); // 可省略形参

    在形式上就是在函数定义的首部后加分号 ; 构成。
    函数声明说明了函数所采用的形式,称为函数原型。

例子
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
	double Area(double a, double b, double c);
	// 或者
	// double Area(double, double, double);
	double a,b,c;
	cout<<"请输入三个边长:"<<endl;
	cin>>a>>b>>c;
	cout<<"面积为:"<<Area(a,b,c)<<endl;
	return 0;
}
double Area(double a, double b, double c)
{
	double p,s;
	p=(a+b+c)/2;
	s=sqrt(p*(p-a)*(p-b)*(p-c));
	return s; // 返回函数的值
}

# 函数的调用

函数定义后,并不能自动执行,必须通过函数调用来实现函数的功能。
函数调用,即控制执行某个函数。
C++ 中,主函数可以调用其它子函数,而其它函数之间也可以相互调用。

# 一般格式

<函数名>(<实际参数表>) // 有参调用
// 或
<函数名>() // 无参调用
  • <函数名> 为要使用的函数的名字。
  • <实际参数表> 是以逗号分隔的实参列表,必须放在一对圆括号中。
  • <实参表><形参表> 中参数的个数、类型和次序应保持一致。
  • 当调用无参函数时,函数名后的圆括号不能省略。

# 实参的几种形式

形参为简单类型变量,对应的实参可以是:常量,变量及表达式。
形参为数组,对应的实参为数组(名)。
形参为结构类型,对应的实参为结构类型变量。

调用已知三边求三角形面积的函数 Area。

double Area(double,double,double); // 函数声明
cout<<Area(4.0,5.0,6.0)<<endl; // 常量作实参
cout<<Area(a,b,c)<<endl; // 变量作实参
cout<<Area(a+1,b+1,c+2)<<endl; // 表达式作实参

函数调用出现在表达式中(适于有返回值的函数调用形式)
如,函数 max() 求两个数的最大值。函数原型如下:

float max(float x,float y);

该函数有返回值,调用时应出现在表达式中。

判断以下语句完成的功能
c=max(a,b); // 函数调用出现在赋值运算符右边的表达式中
d=max(c,max(a,b));// 函数调用同时出现在实参表达式中
cout<<max(a,b)<<endl;// 输出一个函数值

# 嵌套调用

函数的嵌套调用是指在调用一个函数的过程中,被调用的函数又调用了另一个函数。

# 参数的传递

三种方式:

# 值传递

调用时,将实参的值传递给对应的形参,这就是值传递。
在值传递过程中,改变形参的值,并不会改变实参的值。

  • 好处:
    减少函数之间的数据依赖,增强了函数自身的独立性。

  • 注意:
    函数的形参声明为简单类型或结构类型变量,实参与形参将采用值方式传递。

例子
void swap(int x, int y)
{
	int tmp;
	tmp = x;
	x = y;
	y = tmp;
}
int main()
{
	int a = 1, b = 2;
	cout << "Before exchange:a= " << a << ",b= " << b << endl;
	// a=1, b=2
	swap(a, b); // 独立语句调用
	cout << "After exchange:a= " << a << ",b= " << b << endl;
	// a=1, b=2
	return 0;
}

调用前实参 a,b 有自己的存储空间,并有初值。
调用函数时,为形参 x,y 分配存储空间,并接收实参的值。
返回到主函数,这时实参 a 和 b 的值没有改变。
函数中,是对 x 与 y 值进行交换。
执行结束后,x 与 y 将自动释放。

# 引用传递

引用是一种特殊的变量,它被认为是一个变量的别名。

# 引用的定义

格式
<数据类型> &<引用名>=<目标变量名>;

& 为引用(变量)的标志符号, <引用名> 是一个标识符。
<数据类型><目标变量> 的类型。

例子
int a,&b=a;
//a 是一个整型变量,b 是一个引用整型变量 a 的引用,
// 即 b 是 a 变量的一个别名。
// 这时,使用 a 与使用 b 是等价的。

# 使用说明

  • 定义一个引用,其实是为目标变量起一个别名。
  • 引用并不分配独立的内存空间,它与目标变量共用其内存空间。
  • 定义一个引用(变量)时,如果该引用不是用作函数的参数或返回值,则必须提供该引用的初值(即必须提供引用的目标变量名)。
  • 使用引用与使用目标变量效果是相同的。
int main()
{
	int a=2,&b=a;
	cout<<&a<<" "<<&b<<endl;// 输出变量的地址
	cout<<a<<" "<<b<<endl; // 输出变量的值
	return 0;
}

# 引用的传递

  • 为实现引用传递,这时函数的形参应定义为引用类型变量,而对应的实参应为变量名,该变量将作为引用的目标变量名。

  • 函数调用时,作为形参的引用变量并不分配新的内存空间,它将作为实参变量的别名与其共用内存。

  • 使用引用参数可以直接操作实参变量,从而能够实现通过修改形参的值而达到修改对应实参值的目的。

  • 通过设置多个引用参数,可以从函数中带回多个结果值。

  • 注意:引用作为函数形参,其引用的目标变量默认为调用该函数时对应的实参变量名,所以,在定义函数时,对于引用类型参数不必提供引用的初值。

例子:使用引用传递在被调函数中改变实参的值
//x,y 为引用参数,x 引用 a,y 引用 b
void swap(int &x, int &y)
{
	int tmp;
	tmp = x;
	x = y;
	y = tmp;
	// 交换 x,y 就是交换 a,b
}
int main()
{
	int a = 1, b = 2;
	cout << "Before exchange:a= " << a << ",b= " << b << endl;
	// a=1, b=2
	swap(a, b); // 独立语句调用
	cout << "After exchange:a= " << a << ",b= " << b << endl;
	// a=2, b=1
	return 0;
}

# 指针传递

# 为形参指定默认值

C++ 语言允许在函数定义时为形参指定默认参数值。这样,在函数调用时,如果没有提供实参,则形参自动使用其默认值。

void f(int x=0,int y=0); // 形参的默认值为 0
f();// 未提供实参值,形参 x,y 使用默认值 0
f(2,4);// 形参 x,y 将使用实参的值 2 和 4
f(1);// 形参 x 接收 1,而 y 使用 0

可以对部分形参定义默认值。
这时默认值应出现在从右到左的连续若干个形参中。

void f(int i,int j=2,int k=3);// 函数原型正确
void f(int i,int j,int k=3); // 函数原型正确
void f(int i=1,int j,int k=3);// 函数原型错误
void f(int i=1,int j,int k);// 函数原型错误

指定参数的默认值可以在函数定义中进行,也可以在函数原型中进行。
通常写在函数名在程序中第一次出现的位置。

# 数组作为函数参数

# 一维数组的传递

声明格式
<类型> <数组名>[]

其中, [] 中可以省略数组的长度值。(可认为形参数组与实参数组长度相同)
对应的实参应为同类型的一维数组名。(仅用数组名)
为了使函数知道需要处理的数组元素的个数,通常给函数再传递一个表示元素个数的整型数。

# 例子:元素之和

一维数组名作为函数的参数。编写函数,计算一个整型数组中从第 m 个元素(m 从 0 开始)开始的 n 个元素之和。

  • 函数设计:
    函数原型: int fun(int b[],int m,int n);
    功能:计算数组 b 中从第 m 个元素开始的 n 个元素之和。

  • 主函数设计:
    定义并初始化一个整型数组 a。
    测试 1: fun(a,0,10);//求从第0个元素开始的10个元素之和
    测试 2: fun(a,3,5); //求从第3个元素开始的5个元素之和

int fun(int b[],int m,int n)
{
	int i,s=0;
	for(i=m;i<m+n;i++)
	s=s+b[i];
	return s;
}
int main()
{
	int x,a[]={0,1,2,3,4,5,6,7,8,9};
	x=fun(a,0,10);
	cout<<x<<endl;
	x=fun(a,3,5);
	cout<<x<<endl;
	return 0;
}

# 例子:元素排序

编写一个函数 sort,对 n 个元素的一维整型数组 b 进行从小到大排序。

函数定义
void sort(int b[],int n) ;//b 为一维数组,n 为元素的个数
{
	// 选用一种排序方法对 n 个元素的 b 数组排序(代码略)
	// 对 b 数组排序,实际就是对实参数组排序
}
主函数中的函数调用语句
sort(a,10);
// 调用 sort 函数,对 10 个元素的整型数组 a 进行排序
// 函数调用后,并输出排序结果,即输出 a 数组中各个元素值。

# 二维数组的传递

声明格式
<类型> <数组名>[][<列数>]

其中, <列数> 为常数,不能省略,行数可缺省。
调用时的实参应为同类型的二维数组名。
且用作实参的二维数组,其列数必须与形参中的 <列数> 相同。

# 例子:矩阵转置

二维数组名作参数。编写一个函数,将 N 阶方阵转置(N<10)

  • 函数设计
void transmat(int a[][10],int n);
// 对 a 数组中前 n 行 n 列矩阵元素转置
  • 主函数设计
    定义一个 10 行 10 列的大数组 x,元素类型为整型;
    键盘输入一个整数 n (n<10);
    键盘输入一个 n×n 的方阵数据,并存放到 x 数组的前 n 行 n 列元素中。
    函数调用语句为: transmat(x,n);
    按 n 行 n 列输出转置矩阵。
void tranmat (int a[][10],int n)
{
	int t;
	for(int i=0;i<n-1;i++) {
		for(int j=i;j<n;j++)
		{
			t=a[i][j];
			a[i][j]=a[j][i];
			a[j][i]=t;
		}
	}
}
int main() {
	int x[10][10],n;
	cout<<"输入n(n<10):";
	cin>>n;
	cout<<"输入"<<n<<"行"<<n<<"列元素:"<<endl;
	for(int i=0;i<n;i++) {
		for(int j=0;j<n;j++) {
			cin>>x[i][j];
		}
	}
	tranmat(x,n);
	cout<<"转置矩阵结果如下:"<<endl;
	for(int i=0;i<n;i++) {
		for(int j=0;j<n;j++) {
			cout<<x[i][j]<<" ";
		}
		cout<<endl;
	}
}

# 结构体变量作函数参数

实参为结构体类型变量
形参为同类型的结构变量
传递方式为值传递

在函数调用时,系统首先为形参变量(即结构变量)分配存储空间,接着将实参变量(结构变量)各成员值传递给形参变量的各个成员。对形参成员的修改不会影响实参成员值的变化。

# 例子:输出结构变量成员

定义一个结构体类型:
struct student
{
	int stno;
	char name[20];
	int age;
};
void print(student s) // 输出
{
	cout<<s.stno<<endl;
	cout<<s.name<<endl;
	cout<<s.age<<endl;
}
// 主函数
int main()
{
	student stu={1001, "Li" ,19};
	print(stu); // 实参为结构变量
	return 0;
}

# 课堂讨论

  1. 什么时候会使用没有参数或没有返回值的函数?

只起到处理的作用(输入、输出、赋值等)时,没有参数或没有返回值,用来起过渡、衔接等作用,或者空函数留待未来完善。

  1. 什么时候需要函数的声明,函数的声明和定义有什么区别?

声明是不开辟内存的,仅仅告诉编译器,要声明的部分存在,要预留一点空间。定义则需要开辟内存。

  1. 函数的定义是一个完整的函数单元:
    包含函数类型、函数名、形参及形参类型、函数体等。
    在程序中,函数的定义只能有一次。
    函数首部与花括号间不加分号。
  2. 函数声明只是对编译系统的一个说明:
    函数声明是对定义的函数的返回值的类型说明,以通知系统在本函数中所调用的函数是什么类型。
    不包含函数体(或形参)。
    调用几次该函数就应在各个主调函数中做相应声明。
    函数声明是一个说明语句,必须以分号结束。
  1. 什么样的函数只能单独作为语句调用,不能放在表达式中?

无返回值函数。

  1. 什么时候需要引用传递?

需要返回多个结果;节省内存,提高运行速度;改变实参的值时。

  1. 为什么带默认值的参数要放在不带默认值的参数右边(不能交叉)。

因为在函数体内被传递的实参按从左向右依次初始化,如果带默认值的参数放在不带默认值的参数左边,传递少了或不传递则右边的形参没有被初始化,这是错误的,也就会失去了默认值的可不传递参数特性,所以带默认值的参数要放在不带默认值的参数右边。

  1. 形参是二维数组时,为什么列的大小不能省略?

数组是按行线性存入数据的,指定列号,相当于数组的行数和列数同时确定,如果不告诉编译器列号,会导致数据存储混乱,这和定义多维数组,一维长度可以没有,但是其他维长度必须有,道理是一样的。

  1. 如果想传递一个一维结构体数组给函数,形参和实参应是什么样的?

一维数组作为形参的声明格式: <函数类型> <函数名>(结构体类型 形参数组名[])
其中, [] 中可以省略数组的长度值。(可以认为形参数组与实参数组长度相同。) 仅用数名时,对应的实参应为同一类型的一维数组名。
实参为 <函数名>(结构体数组名)

# 随堂练习

  1. 关于 C++ 函数的说法哪个是不正确的?

    • 使用函数可以降低程序的复杂程度
    • 一个 C++ 程序可以不使用函数
    • 函数必须有函数名
    • 函数可以使得软件的开发和维护更容易
  2. 关于函数的定义不正确的是哪项?

    • 函数可以没有返回值
    • 函数可以没有参数
    • 函数体写在一对大括号中,当函数体只有一行语句时,大括号可以省略
    • 函数的形参是局部变量,只能在函数中使用
  3. 关于用户自定义函数的使用,下列哪个说法是正确的?

    • 函数在调用前必须先定义
    • 函数在使用前必须先声明
    • 函数在使用前必须先定义或声明
    • 有了函数的定义,可以不声明
  4. 关于函数的调用,下列哪项说法是正确的?

    • 函数的形参和实参是通过变量的名字结合的,所以顺序不要求一致,但个数必须一致。
    • 函数的实参可以是常量、变量或表达式。
    • 如果函数的实参是变量,前面也必须写出类型名。
    • 函数的调用只能出现在表达式中,不能单独加分号构成语句。
  5. 关于函数调用的值传递,下列哪项是正确的?

    • 值传递的传输方向是单向的,从主调函数到被调函数。
    • 值传递的传输方向是单向的,从被调函数到主调函数。
    • 值传递的数据传输方向是双向的。
    • 值传递时,实参和形参使用相同的存储空间。
  6. 关于函数调用的引用传递,下列哪项是正确的?

    • 引用传递的传输方向是单向的,从主调函数到被调函数。
    • 引用传递的传输方向是单向的,从被调函数到主调函数。
    • 引用传递的实参和形参使用相同的变量名。
    • 引用传递时,实参和形参使用相同的存储空间。
  7. C++ 中关于有默认值的形参,正确的描述是

    • 设置默认形参值时,可以在函数的声明和定义中同时设置默认值。
    • 只能在函数定义时设置默认形参值。
    • 应该从右边的形参开始依次向左设置默认形参值。
    • 所有参数必须全部设置默认值。
  8. 主函数中有声明语句

    int a[100], n=5;

    下列哪组实参和形参的对应是正确的?

    • f(a,n);
      int f(int a, int n);
    • f(a[100], n);
      int f(int a[], int n);
    • f(a, n);
      int f(int a[], int n);
    • f(a[100], n);
      int f(int a[100], int n);
  9. 有结构体 STU 如下:

    struct STU{
    	char name[20];
    	char number[10];
    	int score;
    };

    stu 是 STU 类型的变量。
    下列实参和形参的结合哪项是正确的?

    • f( stu );
      void  f(STU zhang[] ) ;
    • f(stu.name);
      void f(STU zhang);
    • f(stu.name);
      void f(STU zhang.name);
    • f(stu.name);
      void f(char name[20]);

    C

阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Ruri Shimotsuki 微信支付

微信支付

Ruri Shimotsuki 支付宝

支付宝

Ruri Shimotsuki 贝宝

贝宝