局部变量:
形参和函数体内定义的变量
局部变量又分为自动变量和局部静态变量
注意,局部静态变量在程序的执行路径第一次经过对象定义语句时初始化,直到程序终止才被销毁。所以一个简单的应用是,用局部静态变量统计函数被调用 的次数。
函数声明
最好在函数声明中写上形参的名字。把函数声明放在头文件中
指针形参
指针的行为与其他非引用类型一样。对执行指针拷贝操作时,拷贝的是指针的指,所以不能修改传入的实参指针。但是可以修改实参指针所指向的对象的值
所以对于函数f(int *p){*p=3;p=0;}
如果int a=5;
int *q=&a;
f(&a);
f(q);
cout<<a; //输出3
cout<<q; //q不变,仍是a的地址
引用传递
引用有几种用途:
1.想改变传入的实参的值
2.避免拷贝大的类或容器对象
3.可传递某些根本不支持拷贝的类对象作为实参
4.使函数能够返回多个值
尽量使用常量引用
1.明确给调用者传递信息:该函数不能修改实参的值
2.能接受更多类型的实参:非常量对象,字面值,或者通过类型转换能转换成形参类型的对象(参数传递规则与初始化规则是一样的)(普通的引用形参不支持传递这几种类型的实参)
数组形参
形参是数组的函数,以下三种形式是等价的
f(const int*);
f(const int[]);
f(const int[10]);
上面每个函数的唯一形参都是const int*,因此实参可以是任意大小的整型数组,还可以是整型变量
但是只是像上面那样定义形参是数组的函数,函数内部就没有足够的信息确定数组的大小,因此,
定义形参是数组的函数有如下三种形式:
1.如果知道指针指向的数组何时结束,可将函数定义成 f(const char *p){}(传入C风格的字符串,则*p=='\0'表示数组的结束)
2.使用两个指针,分别表示数组的开始和结束 f(const in *beg,const int *end){}
3.常用的一种方法,用一个参数指出数组的大小 f(const int *p,size_t n){}
如果不需要改变数组的内容,那么应该将数组形参定义成const变量
数组引用形参
f(const int (&arr)[10]){}的形参是具有10个整数的数组的引用,注意此时传入的实参必须是含有10个整数的数组
多维数组形参
首先,要明白,多维数组其实是数组的数组
其次,指向多维数组的指针实际上是指向数组首元素的指针
定义形参是多维数组的函数
f(int (*matrix)[10],int rowSize){} 注意,数组的第二维(及后面的所有维数)的大小不能省略,参数rowSize指定数组的行数
f(int matrix[][10],int rowSize){} 注意,数组的第二维(及后面的所有维数)的大小不能省略,参数rowSize指定数组的行数
含有可变形参的函数
1.使用类模板initializer_list 。
函数的定义;f(initializer_list<int> il){}
函数的调用:f({a,b,c});其中a,b,c是该类型的常量或者变量。注意,大括号不能少
2.使用省略符
函数的定义 f(int a,...){}
省略符形参应该只用于与C函数交互的接口程序。特别应该注意的是,大多数类对象作为实参传入时,都不能正确拷贝
函数的返回值
1.对于返回值是void类型的函数,如果要提前终止程序,用return;
2.函数返回一个值,那么返回的值初始化了调用点的一个临时量
3.函数返回引用,那么不会发生拷贝。返回的引用只是所引对象的别名
4.不要返回局部对象的引用或指针(函数结束后,引用或指针指向了不可用的内存空间)
5.若函数返回引用(非常量引用),则函数调用可用作左值
6.C++11新增,函数可以返回用花括号包围起来的列表。该列表对函数返回的临时量进行初始化。初始化规则取决于函数返回值类型的初始化规则
7.函数返回数组指针(即指向数组的指针)。该类函数的定义形式有如下几种:
- int(*f(int i))[10]{} ,这种方法与定义数组指针非常类似
- 使用类型别名 typedef int arrT[10];(using arrT=int[10];)arrT *f(int i){} //这种方式很方便
- 使用尾置返回类型 auto f(int i)->int(*)[10]{}
- 使用decltype int a[10];decltype(a) *f(int i){}
若两个函数的形参只是顶层const和顶层非const的区别,那个这两个函数是等价的,不构成重载
若形参的const属性不同,且是底层const,则这两个函数是重载函数。例 f(const int &a)与f(int &a) f(const int *a)和f(int *a)
const_cast可用来增加或者去除对象的const属性,在重载函数中的情景中最有用,用来在一个const(或者非const)函数中调用另一个非const(或者const)函数
如果在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体(变量或函数)
size_t和size_type
size_t是数组下标的输入参数的类型,在cstddef头文件中定义
string::size_type是string对象的size()函数的返回值的类型,也是string对象 的下标的输入参数的类型
vector<T>::size_type是vector对象的size()函数的返回值的类型,也是vector对象 的下标的输入参数的类型
size_t和size_type都是无符号类型,长度与机器相关
默认实参
1.局部变量不能作为默认实参
2.只要表达式的类型能转换成开通所需的类型,该表达式就能作为 默认实参
3.用作默认实参的名字在函数声明所在的作用域内解析
内联函数
通常将规模较小的操作定义成内联函数。这样整个代码清晰,函数便于重复使用,日后修改起来也方便
constexpr函数
1.函数的返回类型和形参的类型都必须是字面值类型(算术类型,引用,指针,不能是类类型)
2.函数体有且仅有一条return语句.若函数体内包含其它语句,这些语句在运行时不能执行任何操作(比如,可以有类型别名和using声明)
3.constexpr函数不一定返回常量表达式
4.constexpr函数可以作为constexpr变量的初始值,而普通函数不能。constexpr函数作为constexpr变量的初始值时,函数的返回值必须是常量表达式,否则编译器会报错。
5.constexpr函数被隐式地指定为内联函数
6.constexpr函数用于初始化constexpr类型的变量。初始化时,编译器把对constexpr函数的调用替换成其结果值
程序可以包含一些用于调试的代码,这些代码只在开发程序时使用。发布程序时,屏蔽掉这些代码。
使用预处理宏assert
1.调试时,程序中加入assert(expr).expr为假时,assert输出信息并终止程序的运行。(输出的信息内容不用自己指定)。expr为真时,assert什么也不做。
2.使用assert需要在程序中加入头文件cassert。
3.程序发布时,在#include <cassert>前面加上#define NDEBUG,这样就不会执行运行时检查,即assert什么也不做
4.assert应该仅用于验证那些确实不可能发生的事。assert不能代替真正的运行时逻辑检查和其他的错误检查(发布程序时,assert被屏蔽掉,不能起到检查的作用)
使用预处理器变量NDEBUG
1.调试时,可在程序的任意地方加入下面代码
#ifndef NDEBUG
.......
#endif
2.发布程序前,在程序的头部加上#define NDEBUG,就能忽略掉上面的调试代码
重载函数的匹配步骤
1.选候选函数。候选函数要与被调用的函数同名,且其声明可见
2.选可行函数。从候选函数中以以下规则选出可行函数:形参数量与实参数量相等,且形参类型与实参类型相同或形参类型是实参可转换的类型
3.找最佳匹配函数。从可行函数中找形参类型与实参类型最匹配的函数。匹配优先级从高到低:
- 精确匹配
- 通过const转换实现的匹配
- 通过类型提升实现的匹配(简言之,int以下的提升到int,或者unsinged int)
- 通过算术类型转换(int以下的先提升到int,若另一运算对象更宽,则int再转换到另一运算对象的类型;注意,所有算术类型转换的级别一样)或者指针转换(数组名转换为指针;非0的指针转换为true)实现的匹配
- 通过类类型转换实现的匹配
函数指针
定义函数指针:用指针代替函数名 bool (*pfn)(const int ,const int);
给函数指针赋值:
pfn=0;//pfn不指向任何函数
bool f(const int,const int);
pfn=f;//fpn指向f
注意f必须与p的形参类型和返回值类型精确匹配。f是重载函数时,pfn指向精确匹配的那个函数
定义函数指针并初始化
bool (*pfn)(const int ,const int)=f;(函数名自动转换成指针)
函数作为形参
定义函数类型别名
bool f(int ,int);
1.typedef bool f1(int ,int);
2.typedef decltype(f) f2;
3.typedef bool (*pf1)(int ,int);
4.typedef decltype(f) *pf2;
其中f1,f2都是函数,pf1,pf2都是指向函数的指针(decltype(f)的结果是函数类型)
定义函数作为形参的函数:
1.void ff(int ,int ,bool f(int,int));
2.void ff(int ,int ,boo (*pf)(int,int));
上面的两种形式是等价的。1第三个形参看起来是函数,实际上是指向函数的指针。这与数组当作形参类似
3.用类型别名定义函数作为形参的函数
void ff(int ,int,f1)
或者void ff(int ,int,f2)
或者void ff(int ,int,pf1)
或者void ff(int ,int,pf2)
调用函数作为形参的函数
int a=2;
int b=3;
ff(a,b,f);(注意第三个参数不要写成f(a,b)这种函数调用的形式)
返回指向函数的指针
同数组类似,(函数不能返回数组),虽然函数不能返回函数,但是可以返回指向函数类型的指针。
1.显式声明
int (*f1(int))(int ,int);//f1是函数,它返回一个指向函数类型的指针int(*)(int,int);
2.使用类型别名
using F=int (int ,int);//F是函数类型
using PF=int(*)(int,int);//PF是指向函数类型的指针
PF f1(int);
F *f1int);
(注意,若写成F f1(int);则f返回的是函数,而函数是不能返回函数的,所以这种写法不正确)
3.使用auto
auto f1()->int(*)(int,int);
4.使用decltype
int f(int,int);
decltype(f) *f1(int);(注意,decltype(f)的结果是函数)