C++基于过程的程序设计
1.函数基础
1.1 定义函数的一般形式
一个典型的函数定义包括:类型名、函数名、由0个或多个形参组成的列表以及函数体,函数体则包括声明部分和执行语句。
1.2 形参和实参
在定义函数时函数名后面括号中的变量名称为形式参数简称形参,在主调函数中调用一个函数时,函数名后面括号中的参数称为实际参数简称实参。
#include <iostream>
using namespace std;
void fact(int a,int n) //此处括号中定义的 a和 n就是 fact函数的形式参数
{ ...... } //此处省略函数体
int main()
{
int x,y;
cout<<"Please intput x and y";
cin>>x>>y;
fact(x,y); //此处括号中的 x和 y就是 fact函数的实际参数
return 0;
}
- 实参是形参的初始值,第一个实参初始化第一个形参,以此类推。
- 实参的类型必须与对应的形参类型相匹配。
- 在函数调用之前,形参并不占用内存中的储存单元,只有在函数被调用的时候才会给形参分配内存单元,调用结束后内存单元自动释放,所以形式参数也叫虚拟参数,并不是实际存在的数据。
1.3 函数声明
和变量一样,函数也必须在被调用之前进行声明,并且函数也只能定义一次,但是可以多次声明。
- 函数的声明与函数的定义非常类似,唯一的区别是函数的声明无需函数体,用一个分号代替即可。
- 函数的三要素(返回类型,函数名,形参类型),描述了函数的接口,说明了调用该函数需要的全部信息。函数声明也称为函数原型。
2.函数的调用
2.1 函数调用的一般形式
一般形式为:函数名( [实参表列] ),如果是调用无参函数,则实参表列可以没有,但是函数名后面的圆括号不可以省略,各个参数之间用逗号隔开而不是用分号。
found ( a,b,c ) ; //调用found函数,实参为a、b、c
2.2 函数调用的方式
1.把函数调用单独作为一个语句,并不需要函数返回一个值,只是要求函数完成一定的操作。
例:printstar(a,b); //这是一个语句,没有返回值
2 函数出现在一个表达式中,这时要求函数带回一个确定的值以参加表达式的计算。
例:c=2 * max(a,b); //调用max函数返回一个具体的值来参与运算。
3.函数调用作为另一个函数的实参。
例:m=max(a,sqrt(b));
2.3 参数传递
和其他变量一样,形参的类型决定了形参与实参的交互方式。当形参是引用类型时,我们说它对应的实参被引用传递或者函数被传引用调用,当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象。我们说这样的实参被值传递或者函数被传值调用。
指针形参:指针的行为和其他非引用类型一样。指针使我们可以间接地访问它所值的对象,所以通过改变作为形参的指针也可以修改它所指的对象的值。
2.4 函数的嵌套调用以及递归调用
函数的嵌套调用:C++不允许对函数进行嵌套定义,也就是说在一个函数中不能完整的包含另一个函数,每一个函数的定义都是互相平行和独立的。
int main()
{
int found () { .... }; //错误,函数found和函数find的定义都应该在main函数之外,相互平行且独立
void find () { .... };
}
int main () { ..... } //正确
int found () { ..... }
void find () { ..... }
函数执行嵌套调用都是从主函数(main函数)开始的,然后按照主函数中其他函数的声明顺序进行依次调用,直到主函数执行结束为止。
- 注意:如果子函数的定义在主函数之后,则在调用函数之前,需要对每一个被调用的函数进行声明(除非子函数的定义在主函数之前)。
函数的递归调用:在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。
int max(int x,int y)
{
int a,b;
b=max(a,b); //在执行max函数时,再一次对max函数进行了调用
return (2*b); }
包含递归调用的函数称为递归函数。
递归函数的应用是非常广泛的,这里举一个简单的例子向大家简单讲解一下:用递归的方法求n!
#include <iostream>
using namespace std;
int main()
{
int fact(int a); //函数fact定义在主函数之后,所以这里对其进行声明
int n,k;
cout<<"Please intput the number: ";
cin>>n;
k=fact(n);
cout<<n<<"!="<<k;
return 0;
}
int fact(int a)
{
int f;
if(a<0){
cout<<"n<0,data error!"<<endl;
f=-1;
}
else if(a==0||a==1) f=1; //n>1时进行递归调用
else f=fact(a-1)*a;
return f;
}
3.函数的重载
如果同一作用域内的几个函数名字相同但是形参列表不同,我们称之为重载函数,所谓重载,其实就是“一物多用”,一个函数有多种用途。
为什么要进行函数重载呢,例如一个求最大值的函数max可以求出两个数之间的最大值,但是如果是三个数呢,四个数呢,这就要调用max1或者max2函数了,因为函数名的不同,很容易让我们在编程的时候出现混淆,所以我们可以通过函数重载,把一系列求最大值的函数统称为max函数,调用时直接调用max函数即可,方便又快捷。
那如果函数名都统一为max,那么我们怎么知道要调用哪一个函数呢,该如何进行区分呢,对于重载函数来说,它们应该在形参数量或形参类型上有所不同以便于编译系统进行判断。
4.语句
C++语言中的大多数语句都以分号结束,一个表达式,比如 a+2,在末尾加上分号就变成了表达式语句。
4.1 条件语句
C++语言提供了两种按条件执行的语句。一种是if语句,它根据条件决定控制流;另外一种是Switch语句,它计算一个整型表达式的值,然后根据这个值从几条执行语句中选择一条。
4.1 if语句
if语句的作用是:判断一个指定的条件是否为真,若为假则执行另一条语句。
if(condition)
statement1
else //else的部分可以省略,即只进行if语句的判断
statement2
若condition为真,则执行statement1,若为假,则执行statement2。
4.2 switch语句
switch语句提供了一条便利的途径使得我们能够在若干固定选项中做出选择。
switch(表达式)
{
case 常量表达式1:语句1
case 常量表达式2:语句2
case 常量表达式n:语句n
default :语句n+1
}
当switch表达式的值与某一个case子句中的常量表达式的值相匹配时,就执行此case子句中的内嵌语句,若所有的case子句中的常量表达式的值都不能与switch表达式的值匹配,就执行default子句的内嵌语句。
case关键字和它对应的值一起被称为case标签。case标签必须是整型常量表达式,任意两个case标签的值不能相同,否则就会引发错误。
4.2 迭代语句
迭代语句通常称为循环,它重复执行操作直到满足某个条件才停下来。while和for语句在执行循环之前检查条件,do while语句先执行循环体,然后再检查条件。
4.2.1 while语句
只要条件为真,while语句就重复地执行循环体,特点是:先判断表达式,后执行语句。
while(condition)
statement
在while结构中,只要condition的求值结果为真就一直执行statement,直到得到false为止,condition不能为空。
4.2.2 for语句
C++中的for语句使用最为广泛和灵活,不仅可以用于循环次数已经确定的情况,而且可以用于循环次数不确定而只给出循环结束条件的情况。
for(表达式1;表达式2;表达式3)
statement
- 先求解表达式1。
- 求解表达式2,若其值为真,则执行statement。
- 求解表达式3。
- 若表达式2的值为假,循环结束。
for语句中的表达式1,一般为给循环变量赋初值,可以省略,但是其后的分号不可省略。表达式2为循环条件,不可省略,否则循环将无终止地进行下去。表达式3为循环变量增值,可以省略。
4.2.3 范围for语句
范围 for(range for)语句,对于遍历给定序列中的每个元素并对序列中的每个值执行某种操作是非常方便的。
for(declaration : expression)
statement
其中,expression部分是一个对象,用于表示一个序列;declaration部分负责定义一个变量,该变量将被用于访问序列中的基础元素。
每次迭代,declaration部分的那个变量,将被初始化为expression部分的下一个元素值。
使用范围for语句:
int main() {
vector<int> vi = {3, 5, 7};
for (auto &i : vi) {
i += 10;
cout << i << " ";
}
cout << endl;
return 0;
}
代码输出:13 15 17
上述代码等价于传统for语句:
int main() {
vector<int> vi = {3, 5, 7};
for (auto beg = vi.begin(), end = vi.end(); beg != end; ++beg) {
auto &i = *beg;
i += 10;
cout << i << " ";
}
cout << endl;
return 0;
}
由于范围for语句预存了end()的值,所以不能在遍历中进行序列元素的增加和删除。
4.3 转跳语句
4.3.1 break语句
break语句负责终止离它最近的while、do while、for、或switch语句,并从这些语句之后的第一条语句开始执行。break语句只能出现在迭代语句或者switch语句内部。
4.3.2 continue语句
continue语句(continue statement)终止最近的循环中的当前迭代并立即开始下一次迭代。continue 语句只能出现在 for、while 和 do while循环的内部,或者嵌套在此类循环里的语句或块的内部。和 break 语句类似的是,出现在嵌套循环中的continue 语句也仅作用于离它最近的循环。和 break语句不同的是, 只有当switch语句嵌套在迭代语句内部时,才能在switch里使用continue。
continue语句中断当前的迭代,但是仍继续执行循环。对于while或者do while语句来说,继续判断条件的值;对于传统的 for 循环来说,继续执行 for 语句头的 expression;而对于范围for 语句来说,则是用序列中的下一个元素初始化循环控制变量。
4.4 try语句块和异常处理
参考《C++ Primer 第五版》P172
4.4.1 throw表达式
程序的异常检测部分使用throw表达式引发的一个异常。throw表达式包含关键字throw和紧随其后的一个表达式,其中表达式的类型就是抛出的异常类型。throw表达式后面通常紧跟一个分号,从而构成一条表达式语句。
#include <iostream>
using namespace std;
int main()
{
int a=5,b=0;
if(b==0)
throw b; //抛出异常
return 0;
}
//output:
terminate called after throwing an instance of 'int'
4.4.2 try语块
使用ry语句块时,C++异常处理的流程为:
throw(抛出异常)–> try(检测异常) --> catch(捕获异常)
异常必须显式地抛出(throw),才能被检测(try)和捕获(catch)到;如果没有显式的抛出,即使有异常也检测不到。
所以必须使用 throw 关键字来显式地抛出异常,并且用catch捕获异常时,catch和throw的数据类型必须一致,否则无法捕获异常。
try {
...
}
catch (...) {
...
}
需要注意的是try之后可以跟多个catch子句,而不止一个。