接第二章C++编程宝典
2.5. 默认参数(default parameters)
通常情况下,函数在调用时,形参从实参那里取得值。对于多次调用用一函数同一实参时,C++给出了更简单的处理办法。给形参以默认值,这样就不用从实参那里取值了。
2.5.1. 示例
单个参数
#include #include using namespace std; void weatherForcast(char * w="sunny") { time_t t = time(0); char tmp[64]; strftime( tmp, sizeof(tmp), "%Y/%m/%d %X %A ",localtime(&t) ); cout<<tmp<< "today is weahter "<<w<<endl; } int main() { //sunny windy cloudy foggy rainy weatherForcast(); weatherForcast("rainny"); weatherForcast(); return 0; }
多个参数
float volume(float length, float weight = 4,float high = 5) { return length*weight*high; } int main() { float v = volume(10); float v1 = volume(10,20); float v2 = volume(10,20,30); cout<<v<<endl; cout<<v1<<endl; cout<<v2<<endl; return 0; }
2.5.2. 规则
1,默认的顺序,是从右向左,不能跳跃。
2,定义在前,调用在后(此时定义和声明为一体),默认认参数在定义处。声明在前,调用在后,默认参数在声明处。
3,一个函数,不能既作重载,又作默认参数的函数。当你少写一个参数时,系统 无法确认是重载还是默认参数。
void print(int a) { } void print(int a,int b =10) { } int main() { print(10); return 0; }
main.cpp:16: error: call of overloaded 'print(int)' is ambiguous
print(10);
2.6. 引用(Reference)
2.6.1. 引用的概念
变量名,本身是一段内存的引用,即别名(alias)。此处引入的引用,是为己有变量起一个别名。
声明如下
int main() { int a; int &b = a; }
2.6.2. 规则
1,引用没有定义,是一种关系型声明。声明它和原有某一变量(实体)的关系。故而类型与原类型保持一致,且不分配内存。与被引用的变量有相同的地址。
2,声明的时候必须初始化,一经声明,不可变更。
3,可对引用,再次引用。多次引用的结果,是某一变量具有多个别名。
4,&符号前有数据类型时,是引用。其它皆为取地址。
int main() { int a,b; int &r = a; int &r = b; //错误,不可更改原有的引用关系 float &rr = b; //错误,引用类型不匹配 cout<<&a<<&r<<endl; //变量与引用具有相同的地址。 int &ra = r; //可对引用更次引用,表示 a 变量有两个别名,分别是 r 和 ra }
2.6.3. 应用
C++很少使用独立变量的引用,如果使用某一个变量,就直接使用它的原名,没有必要使用他的别名。
作函数参数引用 (call by value)
void swap(int a, int b); //无法实现两数据的交换 void swap(int *p, int *q); //开辟了两个指针空间实现交换
作函数参数引用 (call by reference)
void swap(int &a, int &b){ int tmp; tmp = a; a = b; b = tmp; } int main(){ int a = 3,b = 5; cout<<"a = "<<a<<"b = "<<b<<endl; swap(a,b); cout<<"a = "<<a<<"b = "<<b<<endl; return 0; }
c++中引入引用后,可以用引用解决的问题。避免用指针来解决
2.6.4. 引用提高
引用的本质是指针,C++对裸露的内存地址(指针)作了一次包装。又取得的指针的优良
特性。所以再对引用取地址,建立引用的指针没有意义。
1,可以定义指针的引用,但不能定义引用的引用。
int a; int* p = &a; int*& rp = p; // ok int& r = a; int&& rr =
案例:
#include using namespace std; void swap(char *pa,char *pb) { char *t; t = pa; pa = pb; pb = t; } void swap2(char **pa,char **pb) { char *t; t = *pa; *pa = *pb; *pb = t; } void swap3(char * &pa,char *&pb) { char *t; t = pa; pa = pb; pb = t; } int main() { char *pa = "china"; char *pb = "america"; cout<<"pa "<<pa<<endl; cout<<"pb "<<pb<<endl; // swap(pa,pb); // swap2(&pa,&pb); swap3(pa,pb); cout<<"pa "<<pa<<endl; cout<<"pb "<<pb<<endl; return 0; }
2,可以定义指针的指针(二级指针),但不能定义引用的指针。
int a; int* p = &a; int** pp = &p; // ok int& r = a; int&* pr = &r; // error
3,可以定义指针数组,但不能定义引用数组,可以定义数组引用。
int a, b, c; int* parr[] = {&a, &b, &c}; // ok int& rarr[] = {a, b, c}; // error int arr[] = {1, 2, 3}; int (&rarr)[3] = arr; // ok 的
4,常引用
const 引用有较多使用。它可以防止对象的值被随意修改。因而具有一些特性。
(1)const 对象的引用必须是 const 的,将普通引用绑定到 const 对象是不合法的。这个原因比较简单。既然对象是 const 的,表示不能被修改,引用当然也不能修改,必须使用 const 引用。实际上,const int a=1; int &b=a;这种写法是不合法的,编译不过。
(2)const 引用可使用相关类型的对象(常量,非同类型的变量或表达式)初始化。
这个是 const 引用与普通引用最大的区别。const int &a=2;是合法的。double x=3.14; constint &b=a;也是合法的。
常引用原理:
const 引用的目的是,禁止通过修改引用值来改变被引用的对象。const 引用的初始化特性较为微妙,可通过如下代码说明
double val = 3.14; const int &ref = val; double & ref2 = val; cout<<ref<<" "<<ref2<<endl; val = 4.14; cout<<ref<<" "<<ref2<
上述输出结果为 3 3.14 和 3 4.14。因为 ref 是 const 的,在初始化的过程中已经给定值,不允许修改。而被引用的对象是 val,是非 const 的,所以 val 的修改并未影响ref 的值,而 ref2 的值发生了相应的改变。
那么,为什么非 const 的引用不能使用相关类型初始化呢?实际上,const 引用使用相关类型对象初始化时发生了如下过程:
int temp = val; const int &ref = temp;
如果 ref 不是 const 的,那么改变 ref 值,修改的是 temp,而不是 val。期望对 ref的赋值会修改 val 的程序员会发现 val 实际并未修改。
int i=5; const int & ref = i+5; //此时产生了与表达式等值的无名的临时变量, //此时的引用是对无名的临时变量的引用。故不能更改。 cout<<ref<<endl;
2.6.5. 引用的本质浅析
2.6.5.1. 大小与不可再引用
引用的本质是指针,是个什么样指针呢?可以通过两方面来探究,初始化方式和大小
struct TypeP { char *p; }; struct TypeC { char c; }; struct TypeR { char& r; //把引用单列出来,不与具体的对像发生关系 }; int main() { // int a; // int &ra = &a; // const int rb; //const 类型必须要初始化。 printf("%d %d %dn",sizeof(TypeP),sizeof(TypeC),sizeof(TypeR)); return 0; }
结论:
引用的本质是,是对常指针 type * const p 的再次包装。char &rc == *pc double &rd == *p
2.6.5.2. 反汇编对比指针和引用
原程序
#include using namespace std; void Swap(int *p, int *q) { int t = *p *p = *q; *q = t; } void Swap(int &p, int &q) { int t = p; p = q; q = t; } int main() { int a = 3; int b =5; Swap(a,b); Swap(&a,&b); return 0; }
汇编程序:
[1] { 55 push %ebp <+0x0001> 89 e5 mov %esp,%ebp <+0x0003> 83 e4 f0 and $0xfffffff0,%esp <+0x0006> 83 ec 20 sub $0x20,%esp <+0x0009> e8 ce 0a 00 00 call 0x402130 <__main> [1] int a = 3; int b =5; <+0x000e> c7 44 24 1c 03 00 00 00 movl $0x3,0x1c(%esp) <+0x0016> c7 44 24 18 05 00 00 00 movl $0x5,0x18(%esp) [1] Swap(a,b); <+0x001e> 8d 44 24 18 lea 0x18(%esp),%eax <+0x0022> 89 44 24 04 mov %eax,0x4(%esp) <+0x0026> 8d 44 24 1c lea 0x1c(%esp),%eax <+0x002a> 89 04 24 mov %eax,(%esp) <+0x002d> e8 ac ff ff ff call 0x401632 <Swap(int&, int&)> [1] Swap(&a,&b); <+0x0032> 8d 44 24 18 lea 0x18(%esp),%eax <+0x0036> 89 44 24 04 mov %eax,0x4(%esp) <+0x003a> 8d 44 24 1c lea 0x1c(%esp),%eax <+0x003e> 89 04 24 mov %eax,(%esp) <+0x0041> e8 76 ff ff ff call 0x401610 <Swap(int*, int*)> [1] return 0; <+0x0046> b8 00 00 00 00 mov $0x0,%eax [1] } <+0x004b> c9 leave <+0x004c> c3 ret
0x401632 <Swap(int&,int&)>
12 [1]{ 0x401632 55 push %ebp 0x401633 <+0x0001> 89 e5 mov %esp,%ebp 0x401635 <+0x0003> 83 ec 10 sub $0x10,%esp 13 [1] int t = p; 0x401638 <+0x0006> 8b 45 08 mov 0x8(%ebp),%eax 0x40163b <+0x0009> 8b 00 mov (%eax),%eax 0x40163d <+0x000b> 89 45 fc mov %eax,-0x4(%ebp) 14 [1] p = q; 0x401640 <+0x000e> 8b 45 0c mov 0xc(%ebp),%eax 0x401643 <+0x0011> 8b 10 mov (%eax),%edx 0x401645 <+0x0013> 8b 45 08 mov 0x8(%ebp),%eax 0x401648 <+0x0016> 89 10 mov %edx,(%eax) 15 [1] q = t; 0x40164a <+0x0018> 8b 45 0c mov 0xc(%ebp),%eax 0x40164d <+0x001b> 8b 55 fc mov -0x4(%ebp),%edx 0x401650 <+0x001e> 89 10 mov %edx,(%eax) 16 [1] } 0x401652 <+0x0020> c9 leave 0x401653 <+0x0021> c3 ret
0x401610 <Swap(int*,int*)>
6 [1] { 0x401610 55 push %ebp 0x401611 <+0x0001> 89 e5 mov %esp,%ebp 0x401613 <+0x0003> 83 ec 10 sub $0x10,%esp 7 [1] int t = *p; 0x401616 <+0x0006> 8b 45 08 mov 0x8(%ebp),%eax 0x401619 <+0x0009> 8b 00 mov (%eax),%eax 0x40161b <+0x000b> 89 45 fc mov %eax,-0x4(%ebp) 8 [1] *p = *q; 0x40161e <+0x000e> 8b 45 0c mov 0xc(%ebp),%eax 0x401621 <+0x0011> 8b 10 mov (%eax),%edx 0x401623 <+0x0013> 8b 45 08 mov 0x8(%ebp),%eax 0x401626 <+0x0016> 89 10 mov %edx,(%eax) 9 [1] *q = t; 0x401628 <+0x0018> 8b 45 0c mov 0xc(%ebp),%eax 0x40162b <+0x001b> 8b 55 fc mov -0x4(%ebp),%edx 0x40162e <+0x001e> 89 10 mov %edx,(%eax) 10 [1] } 0x401630 <+0x0020> c9 leave 0x401631 <+0x0021> c3 ret
对比结果
2.7. new/delete
c 语言中提供了 malloc 和 free 两个系统函数,完成对堆内存的申请和释放。而 c++则提供了两关键字 new 和 delete ;
2.7.1. new用法:
1.开辟单变量地址空间
int *p = new int; //开辟大小为 sizeof(int)空间 int *a = new int(5); //开辟大小为 sizeof(int)空间,并初始化为
2.开辟数组空间
一维: int *a = new int[100];开辟一个大小为 100 的整型数组空间 二维: int (*a)[6] = new int[5][6] 三维: int (*a)[5][6] = new int[3][5][6] 四维维及其以上:依此类推
2.7.2. delete用法:
1. int *a = new int;
delete a; //释放单个 int 的空间
2.int *a = new int[5]
delete []a; //释放 int
2.7.3. 综合用法
#include #include #include #include using namespace std; int main() { int *p = new int(5); cout<<*p<<endl; delete p; char *pp = new char[10]; strcpy(pp,"china"); cout<<pp<<endl; delete []pp; string *ps = new string("china"); cout<<*ps<<endl; //cout<<ps<<endl; delete ps; char **pa= new char*[5]; memset(pa,0,sizeof(char*[5])); pa[0] = "china"; pa[1] = "america"; char **pt = pa; while(*pt) { cout<<*pt++<<endl; } delete []pt; int (*q)[3] = new int[2][3]; for(int i=0; i<2; i++) { for(int j=0; j<3; j++) { q[i][j] = i+j; } } for(int i=0; i<2; i++) { for(int j=0; j<3; j++) { cout<<q[i][j]; } cout<<endl; } delete []q; int (*qq)[3][4] = new int [2][3][4]; delete []qq; }
2.7.4. 关于返回值
int main() { //c 语言版本 char *ps = (char*)malloc(100); if(ps == NULL) return -1; //C++ 内存申请失败会抛出异常 try{ int *p = new int[10]; }catch(const std::bad_alloc e) { return -1; } //C++ 内存申请失败不抛出异常版本 int *q = new (std::nothrow)int[10]; if(q == NULL) return -1; return 0; }
2.7.5. 注意事项
1,new/delete 是关键字,效率高于 malloc 和 free.
2,配对使用,避免内存泄漏和多重释放。
2,避免,交叉使用。比如 malloc 申请的空间去 delete,new 出的空间被 free;
2.7.6. 更进一步
如果只是上两步的功能,c 中的 malloc 和 free 完全可以胜任,C++就没有必要更进一
步,引入这两个关键字。
此两关键字,重点用在类对像的申请与释放。申请的时候会调用构造器完成初始化,
释放的时候,会调用析构器完成内存的清理。以后我们会重点讲