C++ Primer学习—— 第六章:函数
1. 函数基础
- 函数调用:包含两步,一是用实参初始化对应的形参,二是将控制权交给被调函数。
2. 局部对象
-
自动对象:只存在于块执行期间的对象称为自动对象。形参是一种自动对象。
-
局部静态对象:有些时候,有必要令局部变量的生命周期贯穿函数调用及之后的时间。可以见局部变量定义为static类型而获得这种变量。
int count_calls()
{
static size_t ctr = 0; //调用结束后,这个值依然有效
}
int main(){
for(size_t i = 0 ; i < 10 ; i++ ){
cout << count_calls << endl;
}
return 0 ;
}
3. 函数声明与定义
-
一个函数可以被声明多次,只能被定义一次。声明的函数没有函数体,取而代之用一个分号表示。
-
函数声明可以省略形参的名字。
4. 参数传递
-
形参的类型决定了形参和实参的交互方式,若形参为引用类型,它将绑定到对应的实参上,否则,将形参的值拷贝后赋给实参。
-
这两种调用分别称为引用传递(传引用调用)和值传递(传值调用)。
4.1 const形参和实参
- 当形参有顶层const时,传给他常量或非常量都是可以的。
void fcn(const int i){ /* fcn能够读取i,但不能向i写值*/}
- 将函数不会改变的形参定义为普通引用是一种十分危险的行为。一方面会给函数调用者一种误导,另一方面,无法使用字面值,求值结果为int的表达式作为实参。
例如:
string::size_type find_char(string &s ,char c,string::size_type &occurs);
find_char("Hello World",'o',ctr); //错误,普通引用的形参不能传入字面值,应使用const string &s
4.2 数组形参
数组的两个特殊性质:不允许拷贝;在使用时将转化为指针。
- 因为不能拷贝,所以我们不能使用值传递的形式使用数组参数。我们为函数传递一个数组时,实际传递的是指向数组首元素的指针。
- 尽管不能以值的方式传递数组,但可以将形参写成数组的形式。
例如: 下面三个print函数的声明是等价的,形参均为const int*类型。
void print(const int[]);
void print(const int*);
void print(const int[10]); //这里的维度可以表示我们期望数组有多少个元素,实际不一定。
- 数组引用形参:C++语言允许将变量定义为数组的引用,基于同样的道理,形参也可以是数组的引用。
void print(int (&arr)[10]){ //括号不可省略,否则是一个引用的数组。
for(auto elem : arr){
cout << elem << endl;
}
}
- 传递多维数组
实际传递的是指向数组首元素的指针。第二维必须声明。
void print(int (*matrix)[10] , int rowSize){ /* 传入的是一个指向大小为10的int型数组的指针 */}
void print(int *matrix[10]); //传入的是一个10个指针构成的数组
上述声明等价于:
void print(int matrix[][10] , int rowSize){/* 实际形参是一个指向10个整数数组的指针 */}
5. main:处理命令行选项
假定main函数位于可执行文件prog之内,可以向程序传递下面的选项。
prog -d -o ofile data0
这些命令行选项通过两个(可选的)形参传递给main函数。
int main(int argc, char *argv[]){ }
- argv 是一个数组,它的元素是指向C风格字符串的指针。
- argc 表示数组中字符串数量。
注: argv第一个元素指向程序的名字或一个空字符串。最后一个指针之后的位置保证0。
在上面的例子中:
argv[0] = "prog";
argv[1] = "-d";
argv[2] = "-o";
argv[3] = "ofile";
argv[4] = "data0";
argv[5] = "0";
argc = 5;
6.含有可变形参的函数
为了编写能处理不同数量实参的函数,C++11新标准提供了两种主要的方法:
- 所有实参类型相同,可以穿第一个initializer_list的标准库类型。
- 如果实参的类型不同,可以编写一种特殊的参数,称为可变参数模板。(之后介绍)
6.1 initializer_list形参
如果实参数量未知但实参的类型均相同,可以使用initializer_list类型的形参。
initializer_list提供的操作:
-
initializer_list<T> lst;
-
initializer_list<T> lst{a,b,c ...};
-
lst2(lst1); lst2 = lst1; //拷贝或赋值一个initializer_list对象,拷贝后共享元素。
-
lst.size();
-
lst.begin();
-
lst.end();
7. 函数重载
指同一作用域内函数名相同而形参列表不同。
- 不允许两个函数除了返回类型外其他所有要素都相同。
7.1 重载和const形参
- 一个拥有顶层const的形参无法同一个没有顶层const的形参区分开来。因为函数传参时会忽略顶层const。
例如:
Record lookup(Phone);
Record lookup(const Phone); //重复声明
Record lookup(Phone*);
Record lookup(Phone* const); //重复声明
- 如果形参是某种类型的指针或引用,可以通过区分其指向的是常量还是非常量实现函数重载。
Record lookup(Account&);
Record lookup(const Account&); //新函数,作用于常量引用
Record lookup(Account*);
Record lookup(const Accout*); //新函数,作用于常量指针
7.2重载与作用域
不同作用域中无法重载函数名,内层作用域中的名字将隐藏外层作用域中的命名实体。
例如:
void print(const string &);
void test(){
void print(int);
print("Hello\n"); //错误,外层的print被隐藏掉了。
}
8.特殊用途语言特性
8.1 默认实参
调用含有默认实参的函数时,可以包含该实参,也可以省略该实参。
typedef string::size_type sz;
string screen(sz ht = 24 ; sz wid = 80; char bg = ' ');
screen(); //相当于screen(24,80,' ');
screen(66); //相当于screen(66,80,' ');
screen(68,90); //相当于screen(66,90,' ');
screen(68,90,'#');
screen(,,'#'); //错误,只能忽略尾部实参。
- 设计含默认实参的函数时,应尽量把使用默认值的形参放在后面。
- 在给定作用域内函数多次声明是合法的,但一个形参只能被赋予一个默认实参,且该形参右侧的所有形参必须都有默认值。
8.2 内联函数和constexpr函数
- 内敛函数
内联函数可以避免函数调用的开销。可以让程序在编译的时候内联的展开。
inline const string & shorterString(const string &s1,const string &s2){
return s1.size() < s2.size() ? s1 : s2;
}
内联只是向编译器发出一个请求,编译器可以忽略。
- constexpr函数
constexpr函数的参数和返回值必须是字面值类型。且函数体只能有一条return语句。
constexpr int new_sz(){return 42};
允许constexpr函数值并非一个常量。如下例中,若实参为常量表达式,返回值也为常量表达式。
constexpr size_t scale(size_t cnt){ return new_sz() * cnt}
int arr[scale[2]]; //正确
int i = 10;
int arr2[scale[i]]; //错误
9.函数指针
9.1 基本介绍
函数指针指向的是函数而非对象。函数指针的类型由它的返回类型和形参类型共同决定。
例如:
bool lengthCompare(const string &,const string &) //类型为bool(const string &,const string &)
bool (*pf)(const string &,const string &);
声明函数指针时只要把函数名换成指针。括号必不可少,否则返回值为bool*。
9.2 使用
当我们把函数名作为一个值来使用时,该函数自动转化为指针。
pf = lengthCompare;
pf = &lengthCompare; //取地址符为可选项
我们还可以直接使用指向函数的指针来调用该函数,无需进行解引用操作。
bool b1 = pf("hello","goodbye");
bool b2 = *pf("hello","goodbye"); //等价
不同函数类型的函数指针之间不存在相互转化,但可以赋值为空指针。
函数指针可以作为函数形参,当使用时,直接使用函数名即可。
函数类型与函数指针略有不同,但作为形参时,编译器会将函数类型转化为指针。
函数指针作为返回值:
和形参不同,返回类型不会自动由函数类型转化为指针。且函数类型不可作为返回值。
using F = int(int*,int); //F是函数类型
using PF = int (*) (int *,int); //PF是函数指针类型
PF f1(int); //正确,返回的是函数指针
F f2(int); //错误,不可返回函数类型
F* f3(int); //正确,返回的是函数指针
上述语句可以等价于:
int (*f1(int))(int* ,int);
若f1后没有(int) ,f1是一个函数指针,因为有,所以f1是一个函数,参数为int,返回值为一个函数指针。
标题:C++ Primer学习—— 第六章:函数
作者:YaoCheng8667
地址:https://ycisme.xyz/articles/2019/12/05/1575527422721.html