端午继续~
C++对C的扩展
引用
本质:给变量名取别名,可以通过别名间接操作变量
&:引用说明符(不是取地址符)
给哪个变量取别名,就定义该变量
普通变量名引用
void Test()
{
int a = 0;
//给变量a取一个别名叫b
//&修饰变量为别名,b就是a的别名 --- 引用
int& b = a;//必须初始化,这里&不是取地址,是引用
}
注意:系统并不会为引用开辟空间,a,b代表同一个空间。
我们可以看一下地址
void Test()
{
int a = 0;
//给变量a取一个别名叫b
//&修饰变量为别名,b就是a的别名 --- 引用
int& b = a;//必须初始化
cout << "&a = " << &a << endl;
cout << "&b = " << &b << endl;
}
运行结果:
操作b等价于操作a
b = 10; 等价于操作了 a = 10;
对数组的引用
void Test_2()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
//给数组arr取别名my_arr
//注意这里要将&my_arr用括号括起来,否则my_arr会先于[10]结合
int (&my_arr)[10] = arr;
//访问的方式是一样
int i = 0;
for (i = 0; i < sz; i++)
{
cout << my_arr[i] << " ";
}
}
对指针的引用
void Test_3()
{
int num = 6;
int* p = #
//给num取别名为 my_num
int*& my_num = p;//这里&的优先级高于*,所以可以不用带括号
//访问时,与指针的访问方法是一样的
cout << "num = " << *my_num << endl;
}
对函数的引用
int main()
{
//对函数Test_3取别名为My_Test_3
void(&My_Test_3)() = Test_3;//注意括号不可省略
//访问
My_Test_3();
return 0;
}
这么多例子,我们为什么要用引用,引用能解决什么问题?
咱往下走~
引用作为函数的参数
这是引用解决的主要问题,函数可以通过引用来操作外部变量
这里学过C语言的小伙伴可能注意到了,引用和指针十分相似
确实如此~
指针有一些操作比较麻烦地方:
形参是带指针*,并且实参要取地址,相对于来说比较麻烦。
所以在之后的学习中需要用到指针的地方尽量换成用引用
我们来对比一下,指针操作和引用操作,交换两个变量:
void My_exchange_1(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
void My_exchange_2(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 6;
int b = 8;
cout << "交换前:a = " << a << " b = " << b << endl;
My_exchange_1(&a, &b);//指针
My_exchange_2(a, b);//引用
cout << "交换后:a = " << a << " b = " << b << endl;
return 0;
}
引用作为函数的返回类型
int& Ret()
{
int num = 5;
return num;
}
int main()
{
int& b = Ret();//由于函数返回类型为int&,所以应该用别名类型来接收
//这里相当于间接给num取了一个别名b
return 0;
}
但是,我们都知道,局部变量一旦出了函数,就销毁了,所以这里的相当于你给一个已经释放了的空间取别名,这是不因该的,(会出现段错误)所以函数返回时不应该返回局部变量 !
常引用
简而言之就是给常量取别名
注:不能通过常引用修改内容
可以看到这里编译器会报错,为什么呢?
给10这个常量取别名为num,10是一个常量,但num是一个变量, C++格式要求十分严格,所以这里会报错。
如何解决呢?
const修饰以下即可
有什么用呢?
防止函数内部修改外部的值
观察以下代码:
void Print(const int& x)
{
//x = 100 //err
cout << "num = " << x << endl;
}
int main()
{
int num = 10;
Print(num);
return 0;
}
我们知道,用引用作为形参可以节省空间,x 和 num 表示一个空间,这里写一个函数,功能就是遍历,但同时我希望他的功能只是遍历,如果一不小心修改了形参 x,也意味着 num 被修改,很危险,所以这里可以用常引用修饰 x,使 x 不可被修改,既提高了安全性,有节省了空间。
内联函数
必须在定义函数的时候使用inline关键字修饰,声明时不需要
string Print();
int main()
{
cout << Print() << endl;
return 0;
}
inline string Print()
{
string s1 = "端午节";
string s2 = "快乐";
return s1+s2;
}
直观感受是不是和普通函数差不多?
其实有很大滴作用!
内联函数:在编译阶段,将内联函数中的函数体 替换 函数调用处 ,避免函数调用时的开销(类似于宏函数,内联函数只是编译阶段直接替换,宏函数在预处理阶段替换)。
注意事项:
1.不能存在任何形式的循环语句
2.不能存在过多的判断语句
3.函数体不能过于庞大
4.不能对函数取地址
所以,加上inline并不是使函数成为内联函数的充要条件,他只是必要条件
加上inline只是告诉编译器,希望使其成为内联函数,最终是不是,还是有编译器决定。
函数重载
表示同一个函数在不同的场景下可以有不同的含义
同一个作用域,函数的参数类型,个数,顺序不同都可以重载(返回类型不能作为重载的条件)。
void print_1(int a)
{
cout << "int" << endl;
}
void print_1(char a)
{
cout << "char" << endl;
}
void print_1(int a, char b)
{
cout << "int char" << endl;
}
void print_1(char b, int a)
{
cout << "char int" << endl;
}
int main()
{
print_1(50);
print_1('a');
print_1(60,'b');
print_1('c',70);//根据参数自动匹配
return 0;
}
可以看到这里,函数名都一样,只是参数的类型,个数,顺序不一样,只要一种或多种,都可以重载。
重载函数的底层原理:实际上这些函数在linux编译之后会产生不同的函数名,然后根据参数自动匹配不同函数的。
函数的默认参数(缺省参数)
#include<iostream>
using namespace std;
void test_1(int a = 10, int b = 20)//这里的int a = 10,被赋值10,说明只是一个默认参数
{
cout << "a + b = " << a + b << endl;
}
//注意1
//如果b设为默认参数,则从b往右的参数都必须设为默认参数
//也就是说,此时a不受影响,c是必须设为默认参数
//因为形参位置与实参位置保持一致,假设c没有默认参数
//调用test_2(100,200),这时,a,b都被赋值,c没有赋值,编译器就会报错(缺少参数)
void test_2(int a, int b = 20, int c = 30)
{
cout << "a + b = " << a + b << endl;
}
//还有值得注意的一点:如果函数声明与函数定义分开,想要设置默认参数,只需在声明里设置即可,函数定义无需再设。
int main()
{
test_1();//可以什么参数都不传,这里就会使用默认参数
test_1(30);//将30这个实参传给a,这时,函数就不会使用默认参数,而是a = 30;
test_1(40, 50);//同理,此时a,b的默认参数失效, a,b的值因为 40 , 50
return 0;
}
默认参数和函数重载的二义性
void fun(int a)
{
cout << "a = " << a << endl;
}
void fun(int a, int b = 20)
{
cout << "a = " << a << endl << "b = " << b << endl;
}
int main()
{
fun(10);
return 0;
}
此时编译器会报错:
什么意思呢?
就是说,此时调用fun(10)传入一个参数,既可以传给第一个fun,也可以传给第二个fun(第二个fun的第二个参数有默认参数,所以传入一个参数也正确),编译器不知到传给哪个fun,所以就报错 ,但如果这样调用fun(10,20)就是正确的,他会自动匹配到第二个fun。
占位参数
占位参数只有类型声明,没有参数名声明,一般情况下,函数体内部无法使用占位参数。
//形式一
void test_1(int a , int)
{
;//这里的int就是占位参数
}
//形式二
void test_2(int a , int = 20)
{
;//int = 20是占位参数,其实这里只是有一个隐形的参数名,但也只是表现形式,无法使用
}
int main()
{
test_1(10, 20);//传参依然要传两个,并且类型对应,否则报错(缺少参数)
test_2(10);//占位参数有缺省值,所以可以只传一个参数
test_2(10, 20);//当然传两个也okk
return 0;
}
也就是说,占位参数位置必须要传入参数,除非有缺省值。
至于怎么用,以后章节会细讲滴。
extern "C" 浅析
C++编译函数和C编译器编译函数的方式是不同的,假设定义一个Fun函数,经过C编译后会产生一个新的函数名叫Fun,而经过C++编译器编译后产生的新函数名可能(不同的C++编译器效果不一样)叫Funv,所以如果调用函数可能就会存在连接错误,那么为了能在C++的环境下调用C语言函数,就引入了extern "C",这部分代码也将按照C语言的编译方式取运行。
使用如下:
#if __cplusplus //检测当前是否是C++工程
extern "C"//满足则执行这里
{
#endif//不满足则执行这里,因为不满足说明就是C语言工程,直接引用以下就可以
extern//引入你想要实现的C语言的函数声明
//...
;
#if __cplusplus
}
#endif