C++对C语法的扩充
C++对C的扩充
(一)引用-引用变量(reference)
(1)引用是一个新的变量类型,作用:为一个变量起一个新的别名;相当于给这块空间取了一个名字叫做 a ,后来又给他取了个小名叫做 b。变量b就叫做变量a的引用。
(2)这里的 &
不是取地址操作符,而是类型标识符的一部分。eg:char * p
中的 *
也是类型标识符的一部分,表示一个指向 char 类型的指针变量,同理 int &:表示一个指向 int 类型的引用变量;
变量a和引用变量b 指向相同的地址和内存空间。
(3)引用变量b的地址是根据它要引用的变量的地址分配的。因此引用变量 b 定义时,必须同时进行初始化,而不能先声明,再赋值。也就是引用变量必须定义时就已经指向另一个已经定义了的变量,否则引用变量无法得知自己在内存空间中存放的位置。
注意:引用变量只能引用一个变量,并且在它的生命周期里,就不能成为其他变量的引用。
int a = 1;
int& b = a;正确
改为:
int a ;
int& b = a;正确:只不过此时a = b = 因为a没有初始化,所以会产生一个随机的垃圾值,但是语法是正确的
改为:
int a = 1;
int& b;
b = a;错误:引用要初始化时就立刻定义,不能先声明再定义
再改为:
int a = 1;
float& b = a;错误:引用本身的类型要和要引用的变量类型相同
再改为:
int a = 1;
int& b = 1;错误:因为常量本身是实际的量,没有别名,也不需要。
再改为:
int a = 1;
int c = 2;
int& b = a;
int& b = c;错误,重复引用
(4)函数的引用形参
引用在一些场合可以代替指针使用,因为引用比指针有更好的可读性和使用性。
引用作为函数形参时不能初始化,只能在函数调用时,由外部变量对引用进行初始化。
C语言中的函数传参两种方式:复制传递和地址传递;
C++扩展增加了函数的引用形参的方式**
- 引用变量的使用:
(1)作为函数的参数传递
(2)作为函数的返回值
通过引用实现两个参数的数据交换:
1 #include <iostream>
2 using namespace std;
3
4 void exchange(int &a, int &b)
5 {
6 int t;
7 t = a + b;
8 a = t - a;
9 b = t - a;
10 }
11
12 int& retval(int &a) 从引用内部实现的角度来看,其实这个函数本质是想返回一个指针int * const
13 {
14 a++;
15
16 return a; 本质是想返回一个局部变量的地址&a
17 }
18 返回局部变量的引用是没有意义的,因为在}作用域后结束了,&a传不到外面去。
19 int main(int argc, const char *argv[])
20 {
21 int x = 100, y = 200;
22
23 cout << "x = " << x << ", y = "<< y << endl;
24
25 exchange(x, y);
26 cout << "x = " << x << ", y = "<< y << endl;
27
28 cout << "retval(x) = " << retval(x) << endl;
29
30 retval(x) = 1;
31 cout << "x= " << x << endl;
32
33
34 return 0;
35 }
~
执行结果:
linux@ubuntu:~/test$ g++ demo.cpp -Wall
linux@ubuntu:~/test$ ./a.out
x = 100, y = 200
x = 200, y = 100
retval(x) = 201
x= 1
代码解析:
-
调用
exchange(x, y);
时,实际就是:int &a = x; int &b = y;
给变量x,y起别名分别对应a,b;
操作a,b实际就是操作x,y对应的同一块内存空间。
通过变量引用可以省去C语法函数传参时变量的复制拷贝和指针的地址跳转。 -
retval(x)打印到终端时,实际就是引用变量a操作变量x对应的内存空间200,返回值也是一个引用变量别名,其实还是x对应的内存空间。
-
retval(x) = 1;
:调用retval函数操作变量x,内部别名a操作实际就是对x操作,返回值是x,
因此retval(x) = 1,也就是x的赋值操作:x = 1; -
返回值:int&型必须为已经起过别名的引用变量,不能为(&a+&b)。
(5)const 引用变量 = 变量/数值常量
1)变量:const int& b = a;
int a = 1;
const int& b = a;
int * p = (int*)&b;
b = 5;错误:a/b为只读变量,不可赋值
*p = 8;正确:兼容C语法可以使用指针修改变量a/b的值。
2)数值常量:const int& b = 1;
const int& b = 1;编译器为常量1分配4个字节的空间,b为其别名
int* p = (int*)&b;p指向b这段空间的指针
b = 3;错误:b为只读变量,不可赋值更改
*p = 5;正确:为了兼容C语法,可修改引用变量b的值
注意:使用数值常量对引用进行初始化赋值,c++编译器会为常量分配空间,并且把引用名作为这一段空间的引用别名。此时引用变量是一个只读变量。
(6)引用是一个变量的别名,那么引用本身是否拥有自己的存储空间呢?
引用的本质:C++定义一个引用:type& name=》等价于 C中定义一个指针常量:type * const name。
指针常量特性:指向的内容可以改变,但是指针指向的地址不可改变。此属性和引用属性不谋而合。
因此说:引用在C++的内部实现是一个指针常量。是对指针更为直观有效的不容易使用出错的改良。
注意:
1)C++中,编译器 在编译器编译过程中,用指针常量作为引用变量的内部实现,因此引用所占的内存空间和指针相同(4字节)。
2)引用只是一个别名,c++为了实用性而隐藏了引用的空间存储这一细节。
#include <stdio.h>
int& demo()
{
int d = 0;
printf("demo: d = %d\n", d);
return d;
}
int& func()
{
static int s = 0; 局部变量s作用域还是此函数中,但是s为静态局部,其生命周期延长。
每次调用此函数都会在原有值的基础上再做改变
printf("func: s = %d\n", s);
return s;
}
int main(int argc, char* argv[])
{
int& rd = demo();rd为demo局部变量的引用,调用函数过程中,就会打印d/s的值
int& rs = func();rs为func静态局部变量的引用
printf("\n");
printf("main: rd = %d\n", rd);
printf("main: rs = %d\n", rs);
printf("\n");
rd = 10;
rs = 11;
demo();
func();
printf("\n");
printf("main: rd = %d\n", rd);
printf("main: rs = %d\n", rs);
printf("\n");
return 0;
}
结果:
linux@ubuntu:~/test$ g++ demo.cpp
demo.cpp: In function ‘int& demo()’:
demo.cpp:5:9: warning: reference to local variable ‘d’ returned [enabled by default]
linux@ubuntu:~/test$ ./a.out
demo: d = 0
func: s = 0
main: rd = -1216630796 因为demo中变量d在函数结束后就被销毁了,其引用rd就变成了一个没有意义的变量。
main: rs = 0 rs为静态局部变量的引用,因为其生命周期延长,并且存储在静态区,因此可以对其进行赋值操作
demo: d = 0
func: s = 11
main: rd = -1216630796 再次对rd赋值时,因为其已经没有意义或者说已经是一个野指针了,无论怎么赋值,
其内存中值始终为垃圾值,容易造成内存泄漏
main: rs = 11
(二)inline内联关键字
用inline内联函数代替宏代码段,inline是对编译器的请求,可以被编译器忽略。
内联函数声明时,inline关键字必须和函数定义结合在一起,否则编译器会直接忽略。
语法格式:
inline int demo(int a, int b)
{
int max;
max = (a > b)? a : b;
return max;
}
(1)inline是C++关键字,在函数声明或定义中,函数返回类型前加上关键字inline,即可以把函数指定为内联函数。函数跳转时需要记录上下文,频繁调用的同一函数会大量消耗栈内存空间:函数的参数入栈,返回值返回和栈销毁。因此内联函数可以解决这个问题实现代码的高效率运行。
(2)内联函数声明一般放头文件中,内联函数是在编译阶段进行处理的,在函数的调用处将函数的代码全部展开,进行替换,没有函数调用跳转的开销。所以 内联函数也被称为一种更安全的宏。但是内联函数不产生符号,只是在本文件可见,所以内联函数一般都写在头文件中,防止在连接阶段所有的引用都找不到该符号的定义,导致符号解析错误。所以说内联函数是一个用于实现的关键字而不是一个用于声明的关键字。
(3)宏代码块的副作用
#include <stdio.h>
#define FUNC(a, b) ((a) < (b) ? (a) : (b))
int main(int argc, char *argv[])
{
int a = 1;
int b = 3;
int c = FUNC(++a, b);
printf("a = %d\n", a); 2
printf("b = %d\n", b); 3
printf("c = %d\n", c); 2
return 0;
}
执行结果:
linux@ubuntu:~/test$ ./a.out
a = 3
b = 3
c = 3
分析结果不是
a = 2
b = 3
c = 2
的原因:FUNC(++a, b)
宏替换FUNC(a, b) ((a) < (b) ? (a) : (b))为:
FUNC(++a, b) ((++a) < (b) ? (++a) : (b))
此时:a = 3
内联函数编译时,因为会对函数做语法检查可以避免这种错误:
#include <stdio.h>
//#define FUNC(a, b) ((a) < (b) ? (a) : (b))
inline int func(int a, int b)
{
return a < b ? a : b;
}
int main(int argc, char *argv[])
{
int a = 1;
int b = 3;
int c = FUNC(++a, b);
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
return 0;
}
结果:
linux@ubuntu:~/test$ ./a.out
a = 2
b = 3
c = 2
(4)强制内联请求
g++编译器:__attribute__((always_inline))//不是c++标准语法支持的东西,而是g++编译器支持的关键字
microsoft VS:__forceinline//VC++:force inline
(三)函数参数的默认值
在函数声明的时候,为参数提供一个默认值。当调用函数时,如果没有提供参数值,则默认使用函数声明时的默认值。
使用规则:
(1)参数默认值必须在函数声明时提供,函数分开声明和定义时不能在函数定义时提供参数默认值。
(2)函数声明时,参数默认值的提供必须从右到左。
(3)函数调用时,当默认使用参数的默认值时,从左到右匹配参数默认值,一旦使用参数默认值则其后续参数也必须使用默认值。且参数中间不能出现参数无默认值的行为。
(4)函数声明时,形参若为引用变量type&,则不能将实际数值作为引用参数的默认值,只能是已经定义好的全局变量作为引用参数的默认值。
1 #include <stdio.h>
2
3
4 int add(int x, int y = 1, int z = 2)
5 {
6 return x + y + z;
7 }
8
9 int main(int argc, char *argv[])
10 {
11 printf("%d\n", add(0)); //x = 0; y = 1; z = 2;
12 printf("%d\n", add(2, 3));//x = 2; y = 3; z = 2;
13 printf("%d\n", add(1, 2, 3));//x = 1; y = 2; z = 3;
14
15 return 0;
16 }
17
正确一:
int add(int x, int y = 2, int z = 3);//在声明中提供默认值
int add(int x, int y, int z)//分开定义时不能提供
{
return x + y + z;
}
正确二:
int add(int x, int y = 2, int z = 3)//在函数声明和定义同时定义时,可以提供默认值
{
return x + y + z;
}
正确三:
int add(int x, int y = 2, int z = 3);//在声明中提供默认值
int add(int x, int z, int y)// 交换形参int z, int y的顺序,也是可以编译通过的。
{
return x + y + z;
}
错误一:函数分开定义时使用默认值
int add(int x, int y, int z);
int add(int x, int y, int z = 0)
{
return x + y + z;
}
错误二:提供参数默认值时没有从右到左
int add(int x = 1, int y = 2, int z);
int add(int x, int y, int z)
{
return x + y + z;
}
错误三:参数中间不能出现参数无默认值的行为
int add(int x, int y, int z = 0); 函数调用add(0);时,没有提供足够的默认参数。即y没有默认参数
int add(int x, int y, int z)
{
return x + y + z;
}
- 函数占用参数
(四)函数重载
C语言中,不允许函数名重名,除了函数不定参数形式:open()。
c++引入函数重载:使用函数重载,则函数的行为必须相似,允许函数同名,参数个数和类型不同。
- 重载(Overload)
同一个标识符在不同的上下文中有不同的含义。
eg:日常生活中,“洗”这个动作搭配不同的名词可以有很多种含义:洗衣服、洗车、洗脑等等。
为了设计出更贴合生活中的自然语言,程序设计便引入函数重载的概念;
函数也就相当于日常生活中的每一个动作,搭配不同的参数,实现完成不同的功能。
(1)函数重载(Function Overload)
用同一个函数名定义不同的函数:当同一函数名和不同的参数搭配时,函数也就有不同含义。
函数重载的条件:想要实现函数重载必须至少满足下面的一个条件
- 参数个数不同
- 参数类型不同
- 参数顺序不同
(2)编译器调用重载函数的准则
(1)将所有同名函数作为候选者;
(2)尝试寻找可行的候选函数:
- 精确匹配实参
- 通过函数的默认参数值匹配实参
- 通过默认类型转换匹配实参
(3)匹配编译失败的原因:
- 找到的候选函数不唯一:二义性
- 函数未定义或函数定义失败
举例:函数重载和函数参数的默认值相结合:出现二义性/未定义函数
#include <stdio.h>
int func(int a, int b, int c = 0)
{
return a * b * c;
}
int func(int a, int b)
{
return a + b;
}
int main(int argc, char *argv[])
{
int c = func(1, 2);失败原因一:此时重载函数调用时,会出现二义性
int d = func(1);失败原因二:函数未定义
return 0;
}
(3)函数重载的本质
重载函数在本质上是相互独立的类型不同的函数,函数重载是由"函数名"和"函数参数列表"决定的,与函数的返回值无关。
#include <stdio.h>
int add(int a, int b) // 函数类型:int(int, int)
{
return a + b;
}
int add(int a, int b, int c) // 函数类型:int(int, int, int)
{
return a + b + c;
}
int main()
{
printf("%p\n", (int(*)(int, int))add);
printf("%p\n", (int(*)(int, int, int))add);
return 0;
}
结果:
linux@ubuntu:~/test$ g++ demo.cpp -Wall
linux@ubuntu:~/test$ ./a.out
0x80483e4
0x80483f1
两个函数函数名相同但是函数的入口地址不同,因此重载函数是不同类型的函数。
(4)重载函数和函数指针的结合
- 根据
重载规则
挑选必须跟函数指针指向的函数类型的参数列表
一致的候选函数; - 严格匹配
候选函数的函数类型
和函数指针的函数类型
,因为c++是强类型匹配的语言,此时就需要考虑函数返回值的类型
。
(5)函数重载的注意事项
(1)函数重载必须发生在同一个作用域中:全局/局部作用域/类作用域。
(2)编译器编译在函数调用的时候,通过函数类型和函数列表
严格匹配进行选择对应的函数。
(3)无法通过函数名来直接得到重载函数的入口地址:必须根据函数指针的数据类型。
printf("%p\n", (int(*)(int, int))add);//将add强制类型转换成对应的函数指针的数据类型一致
printf("%p\n", (int(*)(int, int, int))add);
C语言中,没有函数重载的概念,但是有函数不定参数的概念,eg:open函数的参数个数不同。
C++函数重载是对C语法中的不定参数函数的扩展补充。
函数行为相似,才可以函数重载:函数可以同名,参数个数和类型可以不同。
但是行为一致,函数不能重载
#include <stdio.h>
//规则:函数行为形式才能使用重载
int add(int x)
{
printf("-------int add(int x)-------\n");
return x+x;
}
int add(int x, int y)
{
printf("-------int add(int x, int y)-------\n");
return x+y;
}
int add(int x, int y, int z)
{
printf("-------int add(int x, int y, int z)-------\n");
return x+y+z;
}
int main()
{
//函数同名,参数不同:就是函数重载
add(1);//int add(int x)
add(2,3);//int add(int x,int y)
add(1,2,3);//int add(int x,int y, int z)
//add(1,2,3,4);错误:因为没有这样的版本
}