1 函数
1.1 函数定义
函数:函数的名字;参数列表,可以为空();返回类型,可以是void,可前置或者后置(使用auto)。inline,是一种愿望:通过内联函数整体实现函数调用。constexpx,表示当给定常量表达式作为实参时,可以在编译时对函数求值。noexcept,表示该函数不允许抛出异常。链接说明:例如static。[[noreturn]],表示该函数不会常规的调用/返回机制返回结果。此外,成员函数还能被限定为:virtual,该函数可以被派生类覆盖。override,该函数必须覆盖基类中的一个虚函数。final,该函数不能被派生类覆盖。static,该函数不与某一特定对象关联。const,该函数不能修改其对象的内容。
void swap(int* , int* )
void swap(int* p,int* q)
{
int t=*p;
*p=*q;
*q=t;
}
1.2 函数返回值
string to_string(int a); //前置返回类型
auto to_string(int a) -> string ; //后置返回类型
前置的关键词auto表示函数的返回类型放在参数列表之后,由->符号引导。
并不存在void值,但是可以调用void函数令其作为另一个void函数的返回值。
void g(int* p)
void h(int* p)
{
//....
return g(p);
}
inline int fac(int n)
{
return (n<2)? 1: n*fac(n-1)
}
inline函数:inline限定符告诉编译器,尝试为fac()调用内联代码,而非为fac()函数构建代码再通过常规的调用机制调用它。
constexpr出现在函数定义中时,它的含义是“如果给定了常量表达式作为实参,则该函数应该能用在常量表达式中”。而当constexpr出现在对象定义中时,它的含义是在编译时对初始化器求值。函数必须足够简单才能在编译时求值。constexpr函数必须包含一条独立的return语句,不能有循环,也没有局部变量,constexpr函数不能有副作用。
1.3 局部变量
当线程执行到局部变量时,它们将被初始化,除非我们把变量声明成static,否则函数的每次调用都会拷贝一次该变量。
void f(int a)
{
while(a--){
static int n=0;
int x=0;
cout<<"n == "<<n++<<",x == "<<x++<<"\n";
}
}
int main()
{
f(3);
}
static局部变量可以在函数的多次调用间维护一份公共信息,而无须使用全局变量。
2 参数传递
当程序调用一个函数时,我们为该函数的形参申请内存空间,并用实参初始化对应的形参。编译器负责检查实参的类型是否对应形参类型,并在必要时进行标准类型转换或者自定义类型转换。除非形参是引用,其他情况下传入函数的是实参的副本。
2.1 引用参数
两种方式,一种以值的方式传入函数,另一种以传引用的方式传入函数。
void f(const Large&arg)
{
//不允许修改“arg”的值。
}
如果未被指定const,则可以修改该参数的值。类似的指针类型的参数被声明成const意味着该指针所指的对象的值不会被函数改变。
int strlen(cosnt char*);
char* strcpy(char* to,const char* from);
int strcmp(cosnt char* ,cosnt char*);
字面值、常量和需要执行类型转换的参数可以传递给const T&参数,但是不能传给普通的非const T&参数。
float fsqrt(const float&);
void g(double d)
{
float r=fsqrt(2.0f); //传递存放2.0f的临时变量的引用;
r=fsqrt(r); //传递r的引用
r=fsqrt(d); //传递存放static_cast<float>(d)的临时变量的引用
}
2.2 数组参数
当数组作为函数的参数时,实际传入的是指向该数组首元素的指针。数组类型的参数与指针类型的参数等价。
void odd(int *p);
void odd(int a[]);
void odd(int buf[1024]);
数组的尺寸可以表示为:
void compute1(int* vec_ptr,int vec_size);
2.3 列表参数
类型std::initializer_list,其中列表的值能隐式的转换为T。
2.4 默认参数
int f(int,int =0,char* =nullptr);
3 重载函数
不同函数在不同类型的对象上执行相同的任务,则给它们起同一名字是更好的选择,为不同的数据类型的同一种操作起相同的名字称为重载。
3.1 自动重载解析
当调用函数f时,由编译器决定使用名为f的那组函数时,依据是考察实参类型与作用域中名为f的哪个函数的形参类型最为匹配。
void print(double)
void print(long)
void f()
{
print(1L);
print(1.0);
print(1);
}
重载解析过程中不考虑函数的返回类型,这样可以确保对运算符或者函数调用的解析独立于上下文。
3.2 重载和作用域
重载函数应该位于同一作用域内,不同的非名字空间作用域中的函数不会重载。
void f(int)
void g()
{
void f(double)
f(1); //调用f(double)
}
struct Base{
void f(int);
};
struct Derived:Base{
void f(double);
};
void g(Derived & d)
{
d.f(1); //调用Derived::f(double);
}
4 前置条件和后置条件
5 函数指针
通过获取函数地址得到的指针能被用来调用该函数。函数指针的参数类型声明与函数本身类似,进行指针赋值操作时,要求完整的函数类型必须精确匹配。
void (*pf)(string); //指向void(string)的指针
void f1(string); //void(string)
int f2(string); //int(string)
void f3(int*); //void(int*)
void f()
{
pf=&f1; //正确;
pf=&f2; //错误,返回类型错误;
pf=&f3; //错误,参数类型错误;
pf("Hera"); //正确
pf(1); //错误,参数类型错误;
}