前言:Hello大家好😘,我是心跳sy,这篇文章将介绍下篇知识点内容,本系列文章将会更新关于C++的全部初阶以及进阶的知识点,喜欢的小伙伴点个关注不迷路哦~我们一起来看看吧~
目录
🌈心跳sy的C语言专栏 ⏪C语言知识点汇总到这了,有兴趣的友友可以订阅看看哟~💞💞💞
一、引用 💫
1.1、⭐️概念理解⭐️
🔶引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空 间,它和它引用的变量共用同一块内存空间。
🔶用法:
类型& 引用变量名(对象名) = 引用实体;
void Test()
{
int a = 10;
int& ra = a;//定义引用类型
printf("%p\n", &a);
printf("%p\n", &ra);
}
int main()
{
Test();//两个地址相同
return 0;
}
👉上面例子给变量 a 起了个别名 ra,ra 和 a 共用一块内存空间,所以输出两个地址是一样的。
🔴注意:引用类型必须和引用实体是同种类型的
1.2、⭐️引用要点及特性⭐️
🔸1. 引用在定义时必须初始化
int& ra;//错误!引用必须初始化
int a = 10;
int& ra = a;//正确,编译通过
🔸2. 一个变量可以有多个引用,同时也可以给别名起别名,多个引用别名都指的是原变量
int a = 10;
int& ra = a;
int& rra= a;
🔸3. 引用一旦引用一个实体,再不能引用其他实体
👉在C++中,引用一旦被初始化为指向某个实体(对象),就不能再改变它所引用的对象。这与指针有所不同,指针可以在任何时候改变其指向的目标。
int a = 10;
int b = 20;
int& ref = a; // ref引用a
// 尝试改变ref的引用目标是不允许的
// int& ref = b; // 错误:引用必须在定义时初始化
1.3、⭐️常引用⭐️
🔶C++中的常引用(const&)是一种特殊的引用类型,它为变量提供了 const 语义,即通过常引用修改变量的值是被禁止的;常引用在函数参数传递、返回值以及成员函数中经常被使用,以确保数据的不可变性,同时避免因复制大型对象而带来的性能开销。
const 类型& 常引用名;
//定义一个常引用
int a = 10;
const int& ref = a;
👉在上面的例子中,ref 是 a 的常引用,你不能通过 ref 修改 a 的值。
1.4、⭐️使用场景⭐️
⭕️1、做参数
🔸使用引用作为函数参数,可以让函数修改传入的变量或者避免对大型对象的拷贝,提高效率。
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
🔸例如交换变量的值,增加或减少变量的值都可以用引用,起作用效果和指针一样。
⭕️2、做返回值
int& Count()
{
static int n = 0;
n++;
// ...
return n;
}
🔴需要特别注意,返回变量出了函数作用域就生命周期到了需要销毁(局部变量),不能用引用返回;也就是,如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用 引用返回,如果已经还给系统了,则必须使用传值返回;全局变量/静态变量/堆上变量就可以用引用返回,如上例。
1.5、⭐️指针和引用的区别⭐️
🔶指针和引用的功能是类似的,重叠的
🔶C++的引用,可以对指针使用比较复杂的场最进行一些替换,让代码更简单易懂,但是不能完全替代指针;引用不能完全替代指针原因:引用定义后,不能改变指向(比如链表指向)
🍀区别:
🔸引用概念上定义一个变量的别名,指针存储一个变量地址
🔸引用是别名,不开空间,指针是地址,需要开空间存地址
🔸引用必须初始化,指针可以初始化也可以不初始化
🔸引用不能改变指向,指针可以
🔸引用相对更安全,没有空引用,但是有空指针,容易出现野指针,但是不容易出现野用
🔸sizeof(引用结果为引用类型的大小,但指针始终是地址空间所占字节个数,32 位平台下占4个字节)、++(引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小)、解引用(访问实体方式不同,指针需要显式解引用,引用编译器自己处理)访问等方面的区别。
🔶底层:
汇编层面上,没有引用,都是指针,引用编译后也转换成指针了
二、内联函数 💫
2.1、⭐️什么是内联函数?⭐️
🔶以 inline 修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调 用建立栈帧的开销,内联函数提升程序运行的效率。
👉面对这样一个场景:一个函数需要被不断调用100w次,建立100w个栈帧
c语言如何解决这个问题的?比如Add函数,需要建立宏函数#define ADD(a, b) ((a)+(b))
🔴注意括号控制优先级(当传参有表达式时)
宏的缺点:
1、语法复杂,坑很多,不容易控制
2、不能调试
3、没有类型安全的检查
🔶在C++中就使用了内联函数同样解决了上面场景的问题,在编译时会把函数调用处用函数体来进行替换,这样就避免了上述的函数调用开销,大大提高了执行效率。
2.2、⭐️使用方法⭐️
🔶通过在函数声明或定义前加上 inline 关键字来建议编译器将这个函数作为内联函数处理。
inline int Add(int a, int b)
{
return a + b;
}
2.3、⭐️函数特性⭐️
🔸1. inline 是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率;
🔸2. inline 对于编译器而言只是一个建议,不同编译器关于 inline 实现机制可能不同;
🔸3. inline 不建议声明和定义分离,分离会导致链接错误。因为 inline 被展开,就没有函数地址了,链接就会找不到。
三、auto关键字 💫
🔶C++11引入了 auto 关键字,这个特性被称为“自动类型推导”。
🔶1、auto 关键字的优点是可以让编译器根据变量初始化表达式自动推导变量的类型,从而简化代码。
auto i = 1; // i的类型被推导为int
auto d = 1.1; // d的类型被推导为double
auto s = "hello"; // s的类型被推导为const char*
int i = 0;
int j = i;
auto k = i;
auto p1 = &i;//auto声明指针类型
auto* p2 = &i;//auto声明指针类型
auto& r = i;//auto声明引用类型
int TestAuto()
{
return 10;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = TestAuto();
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
return 0;
}
👉识别b、c、d的类型名称如图:
🔶在C++中,typeid 是一个运算符,它可以用来获取一个变量或表达式的数据类型。它的 name() 方法返回一个 const char*,表示类型的名称(具体字符串依赖于实现,有可能不易读或者加密处理)。
🔶2、auto 关键字还可以自动识别返回值,但注意 auto不能作为函数的参数。
auto add(int a, int b)
{
return a + b;
}
🔴注意:使用 auto 关键字必须初始化,在编译阶段编译器需要根据初始化表达式来推导 auto 的实际类型。因此 auto 并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将 auto替换为变量实际的类型。例如不能这样写:
auto x;//错误
🔶3、auto 关键字也同样具有缺点,就是如果连续使用auto来自动识别变量,容易使其他看不清变量的类型,造成混淆,使用时尽量不要大量使用。
四、基于范围的for循环 💫
4.1、⭐️什么是范围 for 循环⭐️
🔶在C语言中我们学过 for循环,并会使用 for循环进行数组的修改和读取;在C++中有一种更方便快捷的数组修改和读取的方法,就是范围for循环。
🔶在C++11中引入了基于范围的 for循环。for 循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量(常使用 auto 关键字,自动识别变量类型更方便),第二部分则表示被迭代的范围。
👉看一下与正常遍历数组的对比:
int main()
{
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
// 依次取数组中值赋值给e,自动迭代,自动判断结束
for (auto e : array)
{
cout << e << " ";
}
cout << endl;
return 0;
}
🍀使用范围for修改数组数值:
for (auto& e : array)
{
e *= 2;
}
cout << endl;
🔴注意需使用引用来保证值可以被修改完成。
4.2、⭐️范围 for 循环的使用条件⭐️
🔸1. 范围 for 循环迭代的范围必须是确定的,对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供 begin和end的方法,begin 和 end就是for循环迭代的范围。
注意:以下代码就有问题,因为for的范围不确定
void TestFor(int array[])
{
for(auto& e : array)
cout<< e <<endl;
}
🔸2. 迭代的对象要实现++和==的操作。(涉及迭代器这个问题,我们以后会介绍)
五、指针空值(nullptr)💫
❓在之前的C++版本中曾出现了一个bug,就是关于NULL的问题,它究竟是一个字面量?还是一个空指针呢? 当我们在需要使用表示空值的指针时能不能使用NULL呢?来看看下面的验证:
void f(int i)
{
cout << "f(int)" << endl;
}
void f(int* p)
{
cout << "f(int*)" << endl;
}
int main()
{
f(0);
f(NULL);
return 0;
}
👉可以看到两个调用结果相同,NULL的确只是一个字面量,这就导致了在使用空值的指针时,都不可避免的会遇到一些不确定的麻烦,为了解决这个问题,C++定义了nullptr关键字,如下:
void f(int i)
{
cout << "f(int)" << endl;
}
void f(int* p)
{
cout << "f(int*)" << endl;
}
int main()
{
f(0);
f(NULL);
f(nullptr);
return 0;
}
👉可以看到在使用 nullptr 来表示空指针时,不会造成区分不开的问题,我们建议以后都使用它来代表空指针。
本文到此结束💞💞💞感谢大家花费宝贵的时间阅读本文章,制作不易,希望大家多多支持呀😘😘😘,如有任何问题欢迎各位大佬在评论区批评指正!!!