YaoCheng8667 的个人博客 YaoCheng8667 的个人博客

记录精彩的程序人生

目录
《C++ Primer》 第六章:函数
/    

《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 提供的操作:

  1. initializer_list<T> lst;
  2. initializer_list<T> lst{a,b,c ...};
  3. lst2(lst1); lst2 = lst1; //拷贝或赋值一个 initializer_list 对象,拷贝后共享元素。
  4. lst.size();
  5. lst.begin();
  6. 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