函数
函数从本质上来说是一个有名字的代码块
一个函数的结构为
返回值类型 函数名(形参列表){函数体 返回值}
1.返回值类型就是在函数中你要return的类型,可以为void类型,表示不返回任何东西,或者int、double、string以及你自定义的类型等等,·return不可以返回数组。
2.函数名
3.形参列表,调用函数时,传入的参数为实参,函数形参列表的参数为形参,形参和实参的数量必须一致(有默认实参除外),类型可以不一致,但是实参的类型一定要可以转换为形参类型。
int f(int num){
return num;
}
int main(){
f(3.14);
return 1;
}
上面虽然形参定义的是int类型,但是double类型可以转化为int类型所以代码逻辑是正确的。
4.形参列表中可以有零个或者多个
形参,每个形参都必须要有自己的类型,但是不一定要有变量名字。
练习
6.1
形参是函数形参列表中的参数,实参是调用函数时,传给函数的参数。实参的值用于初始化形参。
6.2
a。函数返回值类型为int,但是返回的是string
b。函数没有写返回值
c。函数的函数体少了一个括号
d。函数的函数体么有打括号
6.4
int fact(int num) {
int sum = 1;
while (num>1) {
sum *= num--;
}
return sum;
}
int main() {
int num;
while (cin>>num) {
cout << fact(num) << endl;
}
//cout<<fact(5)<<endl;
return 1;
}
6.5
int my_abs(int num) {
return num > 0 ? num : -num;
}
局部对象
这里需要弄清楚生命周期和作用域,
变量的作用域,该变量能否访问到
变量的生命周期是,该变量是否还“活着”,而不是被销毁了。
函数的形参和定义在函数内部的变量都统称为局部变量。他们仅在函数内部可见,且局部变量会隐藏外层作用域的同名变量。
局部变量分为自动对象和局部静态对象。形参和定义在函数体中的普通局部变量都是自动对象
对于自动对象,生命周期在函数访问结束后也结束了。
而静态局部变量其生命周期是从第一次经过该变量的定义语句开始,到整个程序运行结束。内置类型的静态局部变量的初始化默认初始化为0;
练习
局部变量分为自动对象和静态局部变量,其中自动对象包含了形参和普通的局部变量。
形参和静态局部对象的作用域都是从定义开始到函数结束。但是形参的生命周期在函数调用结束之后就跟着结束了,而静态局部变量的声明周期是从第一次经过该变量的定义语句开始到整个程序结束。
6.6
int f_1(int num) {
static bool is_first = false;
if (!is_first) {
is_first = true;
}
int sum = num + 1;
return sum;
}
6.7
int f_1() {
static bool is_first = false;
int status= is_first == false ? 0 : 1;
if (!is_first) {
is_first = true;
}
return status;
}
6.1.2 函数声明
函数也可以分为声明和定义,函数声明又叫函数原型,函数声明不包含函数体,定义则包含了函数体,同时函数的声明可以省略形参的变量名字。
一般在头文件中写函数的声明,在源文件中函数的定义。
6.1.3 分离式编译
C++支持分离式编译,即可以将逻辑写在多个文件里面,每个单独编译。
然后再将相关联的文件链接起来,目前链接的工作都是IDE帮我们做的。
如果我们修改了代码,只需要重新编译改动了的文件。这对于大型软件项目是非常的有用的。
6.2 参数传递
调用函数需要传递参数,如果形参是引用类型,那么就是在进行引用传递,如果形参是普通类型或者指针类型则实参进行值传递。
形参是引用类型,等于形参是实参的别名,修改形参可以改变实参的值。
如果形参是指针,虽然它可以改变它所指向的对象,但是指针本身改变并不影响它对饮的实参,所以它依旧是值传递。
在c++推荐使用引用传递来代替在函数中的形参中使用指针。
我们能使用引用传递就尽量使用引用传递,如果函数无需改变传递进来的值,就将形参声明为常量应用。引用配合const使用非常方便,如果一个函数的形参中含有const引用类型,那个我们调用函数的方式,和调用普通的函数没有区别,但是如果函数中有指针,那么调用的话就要传递地址。
但是如果函数是非常量引用的类型,那么我们也就不知道,传入的实参是否会被更改了。个人觉得引用方便一些。
我们还可以利用引用传递的特性,返回多个值,多余的返回值通过引用来修改。
总结起来就是:
在定义函数时,尽量使用引用类型,如果函数中无需改变该值,则声明为常量引用,可以利用引用传递的特性,返回多个值
练习
6.11
略
6.12
void swap_value(int *a,int *b) {
int temp = *b;
*b = *a;
*a = temp;
}
void swap_value_2(int &a,int &b) {
int temp = b;
b = a;
a = temp;
}
我觉得第二种更加容易使用,因为第二种在编写代码时和一般的值传递没有太大的差别,而使用指针的话,在函数种还需要对其解引用。第二种使得代码风格更加统一。
但是如果使用指针的话,我们就可以确切的知道,这个函数会改变实参的类型,这是使用指针形参的好处。
6.13
第一种声明属于在调用时,实参被值传递
第二种声明在调用时,实参被引用传递
6.14
如果实参是一个占较大空间的值,最好使用引用类型。
如果需要在代码中改变形参的值,但是又不希望改变其对应实参的值,则该形参不能声明为引用类型
6.15
因为在代码中无需改变s的值,所以s可以声明为常量引用
char也可以是引用类型,但是在代码中实参是字面值,需要使用常量引用。
令s为普通引用就可以在代码中修改s的值,造成不可估量的后果
occurs为常量引用则不能修改其值。
6.2.3 const形参和实参
这里如果是值传递,则实参顶层的const在传递的过程中是被忽略的。
C++的函数可以重载,但是const并不作为重载函数的标志。
void f1(int num);
void f1(const int num);//并不算两个函数
在实参被值传递时,初始化形参的限制和变量初始化时的性质时一样的。
比如引用类型的形参,不能使用字面值的实参进行初始化。等等,不再赘述
在编写函数时,我们应当尽量的使用,常量引用的形参。
因为如果只使用引用形参,那么传递的const类型的实参和字面值实参都会报错。
练习
6.16
s是引用类型,不可以接收字符串字面值和string类型的常量
6.17
bool contain_upppercase(const string& str) {
bool contain_upppercase = false;
for (auto item :str) {
if (isupper(item)) {
contain_upppercase = true;
break;
}
}
return contain_upppercase;
}
void convert_lowercase(string& str) {
for (auto &item:str) {
item = tolower(item);
}
}
形参类型不一样,因为一个在函数体中不需要改变其内容,而另一个则需要改变其类型。
6.18
a.bool compare(const matrix& a,const matrix &b);
b.vector<int> chang_val(int,vector<int>::iterator);
6.19
a.实参和形参的数量不一致
b。合法
c。合法
d。合法
6.20
如果我们在函数体的具体逻辑中不会修改这个形参,那么就可以使用常量引用,如果将其设置为普通引用,字面值常量以及const类型的实参将不可以传递该形参。
6.2.4 数组形参
C++中规定的数组是无法进行拷贝的,所以在传递数组的时候,实际上传递的是这个数组首元素的指针。
虽然我们可以这么写
void func(int f[10]){}
但是其实上面的写法和下面的写法是一样的
void func(int f[]){
}
void func(int *f){}
对于多维数组,要时刻记住,数组其实是指向其第一个元素的指针,所以多为数组可以这么写
void func(int (*p)[10]){}
其中p是一个指针,其指向一个大小为10的整型数组,注意这里的
int (*p)[10],其中的数字10,是这个数组类型的一部分,所以不可以省略。
但是对于第一维我们可以省略
void func(int p[][10])
练习
6.21
int compare(const int value,const int* value1) {
return value > *value1 ? value : *value1;
}
6.22
void swap_point(int *p1,int *p2) {
int * temp = p2;
p2 = p1;
p1 = p2;
}
不知道是不是理解有问题,反正写出来的就是这样,交换两个指针对于实参是没有影响的
6.23
void print(int *value,size_t element_num) {
for (size_t i = 0; i < element_num;++i) {
cout<<value[i]<<endl;
}
}
我选择传入元素的个数,这样只要输入的element_num是正确的,就不会出错。
我晕了。。CSDN的编辑器卡了,写的东西全没了
6.2.5 main函数
我们之前写的main函数的形参列表是空的,但是main函数的形参列表是可以有参数的
int main(){
}
int main(int argc,char *argv[]){
}
这里的argv是一个二维的字符数组,里面存储的是C风格分字符串。argc表示argv中的字符串个数。
其中下标为0的字符串,现实的是程序的名字,其余的都是传入的参数。
可变形参的函数
有的时候,我们不能确定传入的实参的数量
C++11提供了两种解决方案,其中第一种initializer_list,适用于传入的参数都是同一种类型,另一种适用于传入的参数属于不同的类型,叫做,可变参数模板。
除此之外,C++还有一种特殊的形参类型,省略符 …,省略符一般用在和C语言交互的代码中。
可变形参列表中的元素类型必须是一样的。它可以使用beigin和end迭代器来遍历列表中的元素
void f1(std::initializer_list<string> list) {
for (auto &item : list) {
cout << item << endl;
}
}
void f2(std::initializer_list<int> list) {
for (auto beg = list.begin(); beg != list.end();++beg) {
cout << *beg << endl;
}
}
使用范围for和beign()和end()的形式都是可以访问initializer_list中的元素的。
当然传统for也可以,因为initializer_list有size()函数。
在调用时,我们需要用一对花括号将要传入initializer_list中的元素都括进去。
f1({"123","312","1234"});
f2({123,123,123,21});
initializer_list中的元素都是常量,不能改变其值。
省略符形参
省略符形参的设置是为了便于C++程序访问某些特殊的C代码而设立的。
书中也没有详细的讲这部分内容。,
练习
6.27
void f2(std::initializer_list<int> list) {
int sum = 0;
for (auto beg = list.begin(); beg != list.end();++beg) {
cout << *beg << endl;
sum += *beg;
}
cout << sum << endl;
}
6.28
不知道这个ErrCode是什么类型的,百度没有查到。。
6.29
应该,声明为引用传递可以避免值拷贝,而initializer_list中的元素本身就是const类型的,使用auto得到的自动类型为const类型,我们无需担心使用引用会改变其值。