- 参数传递
当实参的值被拷贝给形参时,形参与实参是两个相互独立的对象。我们说这样的实参被值传递。本质上说,都为值传递。所以若对于一个大的自定义对象,如此传递的方式是可取的,代价有些大,所以传入参数,特别是传入个人的一些大的类型对象时,应该使用指针或引用。若在函数中不牵扯到值的改变。则应尽可能使用const。另,若仅是指针或引用,则不能传入一个字面值(一方面不能给字面值取地址,二是把一个字面值赋给非常量引用肯定会出错)。从这个角度也是可以理解指针和引用在期间的作用的。其本质是在参数列表处做了一次同值运算而已。所以,我们不可以把一个const类型赋给一个非const类型。
对于函数顶层const,在函数的形参会被忽略掉,所以,对于一个具有同名,而参数列表若只有const 差别时,视为重定义,会报错的!如:
void reset(int&);//方法1 void reset(int*);//方法2 int main() { int i=0; const int ci =i; unsigned ctr =0; reset(&i); //reset(&ci); &ci为const int*,而形参为int*,故不可赋值 reset(i); //调用 方法1 //reset(ci); //错误,不能把普通引用绑定到一个const 对象上 //reset(34); 不能把普通应用绑定到一个字面值上,这个需注意, //reset(ctr); 类型不匹配 return 0; }
//对于函数顶层const,在函数的形参会被忽略掉,所以,对于一个具有同名,而参数列表若只有const 差别时,编译器不会识别区分,会以相同认为重定会报错的!如<pre name="code" class="cpp">string fcn(const int i) { return "cosnt int"; } string fcn(int i) { return "int"; } // 上面fcn属于重定义 void lookup(int*) { cout<<"int*"; } void lookup(int* const) { cout<<"int* const"; } //上面lookup属于重定义
但其对于底层const并无此作用
#include <iostream> using namespace std; void fcn(const int& i) { cout<< "cosnt int&"<<endl; } void fcn(int& i) { cout<<"int&"<<endl; } void lookup(int*) { cout<<"int*"<<endl; } void lookup(const int* ) { cout<<"int* const"<<endl; } int main() { int s =3; const int cs =s; lookup(&s); lookup(&cs); int& e =s; const int& ce =e; fcn(e); fcn(ce); return 0; }
2.数组形参
1.数组形参,其表示的含义是相同的
2.数组引用形参,数组指针形参void print(const int*); void print(const int[]); void print(const int[10]);
void print(int (&arr)[10]);//arr是具有10个整数数组的引用 void print( int &arr[10]);//arr声明成引用的数组 void print(int (*arr)[10]);//指向10个整数的数组的指针 void print(int *arr[10]) ;//10个指针构成的数组
3.含有可变形参的函数老式的c可用...,但c11中可以使用initializer_list<T>如:
注,省略符形应该仅仅用于c和c++通用的类型。但需要注意的是,大多数类类型的对象在传递给省略符形参时都无法正确拷贝。#include <iostream> using namespace std; void ini_example(initializer_list<string> list){ for(auto i : list){ cout<< i <<endl; } } int main() { ini_example({"nihao","zaijian","women","huajd"});//其传入应使用{}包括。 return 0; }
3.返回值
- 不要返回局部对象的引用或指针。
- 函数可以返回花括号包围的值的列表、
- 返回类型可以使用decltype,也可以使用auto结合尾置类型
string a; auto return_func(void) ->string { return "return_func"; } decltype(a) return_func_dec(void) { return "retrun_func_dec"; } int main() { cout<<return_func()<<endl<<return_func_dec(); return 0; }
4.constexpr函数
constexpr函数是指用于常量表达式,即可以在编译期就已经计算出的值。所以可将其用于数组大小的声明等地方。(注,返回const的 函数不具有此功能,虽const 变量可以)。其要求是仅有一条语句,且只能是return语句,其返回值是和所有形参的类型都是字面值类型。
#include <iostream> using namespace std; constexpr int new_sz(int s){ return 43+s; } int main() { int a=4; int array[new_sz(2)]={0}; //int array1[new_sz(a)]={0}; 此编译不过,由于传入的参数只能是字面值 for(auto i : array){ cout << i <<" "; } return 0; }
5.有关调试的问题
- 断言aseert。断言是 一种编程手段,可以通过NDEBUG来开闭。
- 静态断言static_assert,相对断言assert只有在程序运行时才起作用,静态断言,可以在编译期便排除,问题在于,调试信息可能难以判断但我们可以通过参数业实现。但static_assert可以追加第二参数,表明断言信息。
#include <iostream> #include <cassert> using namespace std; static_assert(sizeof(int)!=8,"int is not 8"); // 这是可以,尽量将其独立于函数。好定位。 int new_sz(const int s){ const int a =s>0; // static_assert(a,"value must > 0"); 由于静态断言,使用了变量 会报错的!assert 是可以使用变量的。当然其断言也将推到运行期 return a; } int main() { cout << new_sz(2); return 0; }
- __func__ __FILE__ __TIME__ __DATE__ __LINE__ 这些,在c11,其甚至可以用在类的列表初始化值
#include <iostream> #include <cassert> using namespace std; struct s{ string name; int line; string time; string file; string date; s():name(__func__),line(__LINE__),time(__TIME__),file(__FILE__),date(__DATE__){ } }; int main() { s a; cout << a.name<<endl<<a.line<<endl<<a.time<<endl<<a.file<<endl<<a.date; return 0; }
6.函数匹配
- 精确匹配
- 实参类型与形参类型相同
- 实参从数组类型或函数类型转换成对应的指针类型
- 顶层const的删除或添加
- 通过const 转换实现的匹配(底层const,主要集中在引用,指针的底层const)
- 通过类型提升实现匹配(数值类型)
- 通过算术类型转换(有符号数转为无符号数等,可能产生)或指针转换(void*,bool,)
- 通过类类型转换实现的匹配
7.函数指针
- 重载函数的指针,上下文必须清晰给出选用的函数。
- 在参数中,函数类型的形参与数组相同与其对应的指针参数等价
void func( bool pf(const string& a)); void func(bool (*pf)(const string& a));
- 函数指针和函数类型是两个不同的概念。函数不能返回函数类型,只能返回一个函数指针。
using f = int(int*,int); //f 是一个函数类型 using fp= int(*)(int*,int); //fp 是一个函数指针 //f f1(int); //错误的,函数f1不能返回一个函数指针。 fp f2(int);//正确的 返回一个函数指针 f* f3(int);//正确的
- 返回函数指针一般最后使用decltyp或尾置的方式来解决。