前言:在前面我们讲解了C++入门基础的一些学习例如命名空间、缺省参数、函数重载等。今天我们将进一步的学习,跟着博主的脚步再次往前迈一步吧。
💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:高质量C++学习 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
-
目录标题
- C++入门
- 引用
- 引用的特性与引用的使用
- 引用的使用
- 变量与引用的变量共用同一块内存空间
- 引用的特性
- 常引用
- 引用的使用场景
- 做参数
- 做返回值
- 内联函数
- 内联函数的特性
- 内联函数的限制和注意事项
- 基于范围的for循环(C++11)
- 指针空值nullptr(C++11)
C++入门
引用
C++中引用是一种特殊的变量类型,它类似于一个别名,可以用来引用另一个变量。引用用&符号来声明,对于一个已经存在的变量,可以使用引用来访问该变量的值。如果我们还是无法理解什么是引用,我们可以通俗的理解成,你的名字叫张三,但是你的小名叫三三、阿三等等,无论小名是什么,这个名字的本质都是指代着你这个人。
语法说明:类型& 引用变量名(对象名) = 引用实体
引用可以有以下特点:
-
引用必须在声明时初始化,并且不能改变引用的目标。一旦指定了引用的目标,它将始终引用该目标。
-
引用可以用来修改引用的目标。通过引用可以修改引用所指向的变量的值。
-
引用不能引用空值或不存在的对象。
-
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
-
不能建立数组的引用。因为数组是一个由若干个元素所组成的集合,所以无法建立一个数组的别名。
引用的特性与引用的使用
引用的使用
int main() {
int x = 10;
int& ref = x;//对x进行引用
cout << "x = " << x << endl; // 输出 x = 10
cout << "ref = " << ref << endl; // 输出 ref = 10
ref = 20; // 修改引用的目标
cout << "x = " << x << endl; // 输出 x = 20
cout << "ref = " << ref << endl; // 输出 ref = 20
return 0;
}
变量与引用的变量共用同一块内存空间
void TestRef()
{
int a = 10;
int& ra = a;//<====定义引用类型
printf("%p\n", &a);
printf("%p\n", &ra);
}
int main()
{
TestRef();
return 0;
}
此时我们也不难发现,a和ra所在的空间上的地址是相同的,因此可以知道,a和ra共用同一块内存空间。
引用的特性
如果要博主解释引用的话,博主目前短暂的理解是,引用如指针是十分相向的,但是引用一旦指向一个实体就不能改变其指向了。
-
引用在定义时必须初始化
-
一个变量可以有多个引用
-
引用一旦引用一个实体,再不能引用其他实体
int main()
{
int a = 10;
int& ra = a;
//int& ra;此时会报错,因为引用在定义的时候必须初始化
int& rra = ra;//一个变量可以拥有多个引用
cout << "原本的值" << endl;
cout << "a = " << a << " ra = " << ra << " rra = " << rra << endl;//引用的变量与变量本身共用同一块空间
cout <<"地址: " << "a = " << &a << " ra = " << &ra << " rra = " << &rra << endl;
cout << "修改后的值" << endl;
a++;//对引用的修改是会影响变量本身的
ra++;
rra++;
cout << "a = " << a << " ra = " << ra << " rra = " << rra << endl;
cout << "地址: " << "a = " << &a << " ra = " << &ra << " rra = " << &rra << endl;
return 0;
}
常引用
用const这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到了引用的安全性。和const修饰变量一样,被const修饰的引用只能够读,而不能够写,即不能修改引用变量的值。接下来我们将通过几个例题来帮助我们理解,如下:
例1:请问如下两个语句,哪一个会报错,哪一个不会。
const int a = 10;//语句1
int& ra = a; //语句2
代码解读:
在如上的两个代码中,语句1将10这个常量传给了整型a,并且a无法被修改,因此语句1正确,而语句二,我们将一个无法被修改的a值传给引用ra,此时我们不难发现,如果ra可以这样引用的话不就代表a值是可以修改的了嘛?那这个const修饰的a不就没有任何意义了嘛?因此不难判断出语句2是错误的。因此正确的写法是:
const int& ra = a;
此时我们用const修饰的引用变量ra就可以达到该引用值无法被修改。
例题2:请问如下两个语句,哪一个会报错,哪一个不会。
int& b = 10; //语句1
const int& b = 10;//语句2
代码解读:
我们可以知道10是一个常量对吧,10能被修改嘛?当然不可以,因此我们怎么可能可以通过引用变量直接来引用10呢?这不开玩笑嘛,如果b能够直接修改这个10,那10是什么表示呢?如果不懂的可以看下图。那么语句2呢?我们用const修饰引用变量,那么此时被引用的变量就无法修改,因此语句2正确。
例题3:请问如下两个语句,哪一个会报错,哪一个不会。
double d = 12.34;
int& rd = d; //语句1
const int& rd = d;//语句2
代码解读:
语句1直接对d进行引用的同时,在编译器中会对d进行强转成一个整型12,而12具有常性因此如果我们直接对其进行引用的过程中会导致我们墙面讲到的情况,如果对一个常量直接引用会导致数据发生修改,因此我们需要加上const对其进行修饰,如果不能理解可以看下图。
整体代码测试:
void TestConstRef()
{
const int a = 10;
//int& ra = a; // 该语句编译时会出错,a为常量
const int& ra = a;
// int& b = 10; // 该语句编译时会出错,b为常量
const int& b = 10;
double d = 12.34;
//int& rd = d; // 该语句编译时会出错,类型不同
const int& rd = d;
cout << "a = " << a << " ra = " << ra << " b = " << b << " d = " << d << " rd = " << rd << endl;
}
int main()
{
TestConstRef();
return 0;
}
运行结果:
引用的使用场景
做参数
在学习C语言的过程中我们知道,如果将一个数传递给函数,形参是实参的一份临时拷贝,此时空间占用是十分大的,而引用本质就是直接把该数本身传递过去,直接对这个数可以进行修改省区了对该数进行拷贝的过程减少了空间的消耗.
实例演示:
void Swap(int& a, int& b)//此时我们直接用引用结接收
{
int tmp = 0;
tmp = a;
a = b;
b = tmp;
}
int main()
{
int n = 3;
int m = 4;
cout <<"修改前: " << n << ' ' << m << endl;
Swap(n, m);
cout <<"修改后: " << n << ' ' << m << endl;
return 0;
}
具体等作为参数的情况,我们在后面的学习会持续体会到的,到时候感受肯定是会与现在不太一样,因此我们现在就对其做一个初步的使用即可.
做返回值
我们接下来依然通过几个例子来帮助我们理解作为返回值的情况.
例1:
int& Func()
{
static int n = 0;//static修饰的局部变量出了作用域不会结束
n++;
printf("&n:%p\n", &n);
return n;
}
int main()
{
int ret = Func();
cout <<"n = "<< ret << endl;//查看此时n的地址与值
int& rret = Func();
printf("&rret:%p\n", &rret);
return 0;
}
此时我们不难发现将引用作为返回值返回给了函数
例题2:
int& Add(int a, int b)
{
static int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
cout << "Add(1, 2) is :" << ret << endl;
int& m = Add(3, 4);
cout << "Add(3, 4) is :" << m << endl;
return 0;
}
代码分析:
这里我们将1,2先传过去因此一开始返回的是3没有任何疑问,在第二次传递给3和4的时候我们需要注意达到是static修饰的局部变量出了作用域并不会结束,static修饰局部变量会进行一次初始化,并且只会进行一次,因此我们传过去的3和4并没有相加,返回的依然是上一次的结果3.
例题3:
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
cout << "Add(1, 2) is :" << ret << endl;
Add(3, 4);
cout << "Add(1, 2) is :" << ret << endl;
return 0;
}
代码分析:
在函数 Add 中,局部变量 c 的生命周期仅限于函数内部。
当函数执行完毕后,c 被销毁,而返回的引用 ret 将指向一个不存在的对象。当再次使用这个引用时,就会出现未定义的行为。因为此时我们又传递了3和4此时我们并没有返回值接收,然后再次调用ret的时候所引用的就是3和4那块函数所指向空间的值.
总结一下:出了函数作用域返回变量不存在了,不能用引用返回因为此时指向的空间已经被销毁了可以理解成为一个野引用,因此出了函数作用域返向变量存在,才能用引用返回.
- 引用概念上定义一个变量的别名,指针存储一个变量地址。
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
一个同类型实体 - 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
位平台下占4个字节) - 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
内联函数
C++内联函数是一种特殊的函数,其函数体在调用点处直接展开,而不是按照函数调用的方式进行执行。这样可以避免函数调用的开销,提高程序的执行效率。
在C++中,可以通过将函数定义放在函数声明之前,并在函数声明处加上关键字“inline”来定义内联函数
内联函数的特性
- inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会
用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运
行效率。 - inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)
- inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到
内联函数的限制和注意事项
- 内联函数的定义必须在每个使用该函数的文件中可见。
- 内联函数一般放在头文件中,以便在多个源文件中使用。
- 内联函数不应包含复杂的逻辑或大量的代码,以避免代码膨胀。
- 对于递归函数、带有循环或开销较大的函数,不建议使用内联函数。
实例演示:
inline int add(int a, int b) //内联函数的定义
{
return a + b;
}
int main()
{
cout << add(19, 21) << endl;
return 0;
}
auto 关键字在C语言中就已经存在了,只不过在C语言中它的作用是声明自动变量:auto int a = 0;
在C++中,关键字auto用于自动推导变量的类型。它可以让编译器根据变量初始化表达式的类型自动推导出该变量的类型,从而简化变量类型的声明和初始化过程
使用auto关键字有以下几点注意事项:
- auto关键字只能在变量的声明和初始化过程中使用,不能用于函数的返回类型、函数参数类型、类成员的类型等。
- auto关键字通常用于推导变量的类型,不适用于推导引用或指针的类型。如果需要推导引用或指针的类型,可以使用auto&或auto*。
- 推导出的变量类型与表达式的类型是一致的,包括const、引用、指针等限定符。
- auto关键字在编译时自动推导变量类型,而不是在运行时进行类型推导。
实例演示:
void func(int a, int b)
{
//.....
//.....
}
int main()
{
int i = 0;
int j = i;
auto k = i;//auto可以通过右边的类型自动推导左边的类型
auto p1 = &i;
void(*pf1)(int, int) = func;//函数指针
auto pf1 = func;//用auto直接直接识别
}
基于范围的for循环(C++11)
在C++98中如果要遍历一个数组,可以按照以下方式进行:
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
array[i] *= 2;
for (int* p = array; p < array + sizeof(array)/ sizeof(array[0]); ++p)
cout << *p << endl;
}
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。ps:目前我们只讲怎么用即可,以后会给大家详细讲解
实例演示:
int main()
{
for (auto e : array)//依次取数组中的值赋值给e,自动迭代,自动判断结束
{
cout << e << " ";
}
}
int main()
{
for (auto& e : array)//e是对数组里面的值的拷贝,所以无法直接赋值,得引用
{
e *= 2;
}
for (auto e : array)
{
cout << e << " ";
}
}
指针空值nullptr(C++11)
在这里我们只需要知道nullptr是替代原本的NULL即可
在C++中,nullptr是一种特殊的空指针常量。它被用来表示一个空指针,即指向没有任何对象的指针。
在早期的C++版本中,常常使用NULL来表示空指针。然而,NULL实际上是一个宏定义,一般被定义为整数0。这样的定义使得在一些情况下,NULL可能会被误用。
为了解决这个问题,C++11引入了nullptr。nullptr是一个关键字,用来表示空指针,不会被隐式地转换成其他类型。使用nullptr可以提高代码的可读性和安全性.总而言之,nullptr是C++11中用来表示空指针的关键字,相对于早期的NULL常量,nullptr更加安全和可读性更好.
好啦,今天的内容就到这里啦,下期内容预告类和对象
结语:今天的内容就到这里吧,谢谢各位的观看,如果有讲的不好的地方也请各位多多指出,作者每一条评论都会读的,谢谢各位。