C++ Primer 5th学习笔记5 函数

函数

1 函数基础

  自动对象:只存在于块执行期间的对象,当块的执行结束后,块中创建的自动对象的值就变成未定义的。
  局部静态对象:在程序的执行路径第一次经过对象定义语句初始化后,直到程序终止才被销毁,对象所在的函数结束执行也不会影响该变量。将局部变量定义成static类型即可变成局部静态对象

2 参数传递

  引用传递:形参是引用类型
  值传递:实参的值被拷贝给形参时

2.1 传引用参数

  示例:

//该函数接受一个int对象的引用,然后将对象置0
void reset(int &i)
{
    i = 0;
}

此时i绑定的是传给函数的int对象,改变i也就是改变i所引对象的值。
tip:使用引用可以避免拷贝;如果函数无须改变引用形参的值,最好将其声明为常量引用

2.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;
}

&arr两端的括号必不可少:

f(int &arr[10])//错误:将arr声明成了引用的数组
f(int (&arr)[10])//正确:arr是具有10个整数的整型数组的引用

  传递多维数组:

//matrix指向数组的首元素,该数组的元素是由10个整数构成的数组
void print(int (*matrix)[10], int rowSize)

强调:matrix两端的括号必不可少:

f(int *matrix[10])//10个指针构成的数组
f(int (*matrix)[10])//指向含有10个整数的数组的指针

  main处理命令行选项

int main(int argc, char *argv[]){...}

第二个形参argv是一个数组,其元素是指向C风格字符串的指针;
第一个形参argc表示数组中字符串的数量
例如输入下面命令: prog -d -o ofile data0

argv[0] = "prog";
argv[1] = "-d";
argv[2] = "-o";
argv[3] = "ofile";
argv[4] = "data0";
argv[5] = 0;

其中argc = 5当使用argv中的实参时,可选的实参是从argv[1]开始,argv[0]保存程序的名字,而非用户输入

  含有可变参数的函数
若所有的实参类型相同,可以传递initializer_list类型的形参,initializer_list可用于表示某种特定类型的数组。

initializer_list提供的操作
操作 描述
initializer_list<T> list; 默认初始化;T类型元素的空列表
initializer_list<T> list{a,b,c...}; lst的元素数量和初始值一样,是对应初始值的副本;列表中的元素是const
lst2(lst) 拷贝lst,原始列表和副本共享元素
lst2 = lst 赋值lst,原始列表和副本共享元素
lst.size() 列表中元素的数量
lst.begin() 返回指向lst中首元素的指针
lst.end() 返回指向lst中尾元素下一位置的指针

示例

void error_msg(initializer_list<string> il)
{
    for(auto beg = il.begin(); beg != il.end(); ++beg)
    {
        cout << *beg <<  " ";
    }
    cout << endl;
}

  与vector不同,initializer_list对象中的元素永远是常量值;如果想向initializer_list形参中传递一个值的系列,则必须把序列放在一对花括号内

error({"abcd", excepted, actual});
error({"abcd", "okey"});

  省略符形参,省略符形参只能出现在形参列表的最后一个位置;其形式如下:

void foo(parm_list,...);
void foo(...);

3 返回类型和return语句

  不要返回局部对象的引用或指针原因在于函数完成后,其所占用的存储空间也随之被释放掉,因此函数终止意味着局部变量的引用将指向不在有效的内存区域

  引用返回左值,函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到左值,其他返回类型得到右值

char &get_val(string &str, string::size_type ix)
{
    return str[ix]; 
}
//调用上述函数如下,将s[0]的值改为A:
get_val(s,0) = 'A';

  返回数组指针,声明一个返回数组指针的函数形式如下所示:
Type (*function(parameter_list)) [dimension]
type表示元素的类型
dimension表示数组的大小
示例:
int (*func(int i))[10];
对上述示例的理解如下:

  • func(int i)表示调用fun函数时需要一个int 类型的实参;
  • (*func(int i))意味着可以对函数调用的结果执行解引用操作;
  • (*func(int i))[10]表示解引用func的调用将得到一个大小是10的数组;
  • int (*func(int i))[10]表示数组中的元素是int类型。

  使用尾置返回类型
示例:

//func接受一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组
auto func(int i) -> int(*)[10];

  使用decltype,若事先知道函数返回的指针将指向哪个数组,就可以使用decltype关键字声明返回类型。

4 函数重载

  函数重载:若同一作用域内的几个函数名字相同但形参列表不同的函数
如下:

void print(const char *cp);
void print(const int *beg, const int *end);
void print(const int ia[], size_t size);

Tips:main函数不能重载;不允许两个函数除了返回类型外其他所有的要素都相同,即返回类型要相同

  重载和const形参
顶层const不影响传入函数的对象,但底层const会影响。如果形参是某种类型的指针或者引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,这里是通过底层const实现的
例如:

int func(int&);              //函数作用于int的引用
int func(const int&);        //新函数,作用于常量的引用

int func(int*);              //新函数,作用于指向int的指针
int func(const int*);        //新函数,作用于指向常量的指针

5 特殊用途语言特性

5.1 默认实参

默认实参:在函数的很多次调用中,形参都被赋予一个相同的值,这个反复出现的值称为默认实参。
示例如下:

typedef string::size_type sz;
string screen(sz ht = 24, sz wid = 80, char backgrnd = '');

这里默认窗口的高度为24,宽度为80。调用的时候,可以省略该实参,但是函数调用时实参是按其位置解析的,默认实参负责填补函数调用缺少的尾部参数(靠右侧的位置),即只能省略尾部的实参

5.2 内联函数和constexpr函数

  将函数指定为内联函数(inline),可避免函数调用的开销。在函数的返回类型前加上关键字inline,即可将函数声明为内联函数。示例如下:

//函数原型
const string & shorterString(const string &s1, const string &s2)
{
    return s1.size() <= s2.size() ? s1 : s2;
}

若声明为以下形式(内联函数):

//函数原型
inline const string & shorterString(const string &s1, const string &s2)
{
    return s1.size() <= s2.size() ? s1 : s2;
}

则在函数调用会变成以下过程:

//函数调用
cout << shorterString(s1, s2) << endl;

编译过程中会展开成如下形式:

//函数调用
cout << (s1.size() <= s2.size() ? s1 : s2) << endl;

  constexpr函数是指能用于常量表达式的函数,示例如下:

constexpr int new_sz() {return 43;}
constexpr int foo = new_sz();
  • 此函数的返回类型及所有形参的类型都必须是字面值类型
  • 函数体中必须有且只有一条return语句

Tip:由于内联函数和constexpr函数可以在程序中多次定义,内联函数和constexpr函数通常定义在头文件中

5.3 调试帮助

两种预处理功能:assertNDEBUG
assert属于预处理宏,定义在cassert头文件中,常用于检查“不能发生”的条件,示例如下:
assert(expr);
首先对expr求值,如果表达式为假(即0),assert输出信息并终止程序的执行。

NDEBUG预处理变量
可以使用NDEBUG编写自己的条件调试代码,如果NDEBUG未定义,将执行#ifndef#endef之间的代码;若有定义,则上述代码将被忽略掉。示例如下:

void print(const int ia[],size_t size)
{
#ifndef NDEBUG
cerr << _ _func_ _ << ": array szie is " << size << endl;
#endif
}

此处使用变量_ _func_ _输出当前调试的函数的名字
类似的变量还有如下:
_ _FILE_ _ 存放文件名的字符串字面量值
_ _LINE_ _ 存放当前行号的整型面量值
_ _TIME_ _ 存放文件编译时间的字符串字面量值
_ _DATE_ _ 存放文件编译日期的字符串字面量值

6 函数指针

函数指针指向的是函数,而非对象,函数的类型由它的返回类型和形参类型共同决定,与函数名无关。示例如下:

//比较两个string对象的长度
bool lengthCompare(const string &,const string &);

声明一个可以指向该函数的指针如下,只需要用指针替换函数名即可,*pf两端的括号必不可少

//pf指向一个函数
bool (*pf)(const string &,const string &);   //未初始化

使用函数指针,示例如下:

pf = lengthCompare;       //pf指向名为lengthCompare的函数
pf = &lengthCompare;        //等价的赋值语句

猜你喜欢

转载自blog.csdn.net/qq_18150255/article/details/88785810