C到C++的敲门砖-2

2024-03-18 08:28
文章标签 c++ 敲门砖

本文主要是介绍C到C++的敲门砖-2,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 引用
  • 内联函数
  • auto关键字
  • 基于范围的for循环
  • 指针空值nullptr
  • 后记

引用

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空
间,它和它引用的变量共用同一块内存空间。

所谓引用就是给变量起别名,在语法上来说是不会开辟变量的。
例如:

int main()
{int a=0;int&ra=a;a=5;cout<<a<<endl;cout<<ra<<endl<<endl;ra=10;cout<<a<<endl;cout<<ra<<endl;	
}

在这里插入图片描述
如上,即为代码的运行结果。可以看到我们改变了a的值,ra也跟着改变。改变了ra的值,a也跟着改变。这个ra就类似于c语言的指针,但此处不同在于不需要解引用。
此外,引用还有以下特性:

  • 引用在定义时必须初始化
    这是很好理解的,如果不初始化就很容易有歧义:
int a=0;
int b=0;
int&ra;
ra=a;
ra=b;

如上,我们便没有对ra进行初始化,那么下面ra=a和ra=b,这到底是说ra是a的别名还是b的别名呢?所以,为了避免这样的歧义存在,引用在定义时必须初始化。

  • 引用一旦引用一个实体,再不能引用其他实体。原因还是为了避免歧义。这也是和指针不同的一点,这也导致有些场景只能用指针而不能用引用来解决
  • 一个变量可以有多个引用。
    一个人可以有多个别名,那么变量当然也可以有多个别名。

除了上面的特性外,使用引用还有以下需要注意的点:

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;}

上面我们了解了引用的定义,那么c++多出这么个概念又有什么作用呢?
别急,下面我们就来谈谈引用的使用场景:

  1. 做参数
void Swap(int& left, int& right){int temp = left;left = right;right = temp;}int main(){int a=10,b=5;Swap(a,b);}

如上代码,我们以往使用c语言时传参就必须是穿址调用才能交换a,b的值。现在可以引用调用,只传a,b的值就能实现Swap。
这样做的一大好处就是增加了代码的可读性,不用时不时传一个指针、二级指针,不仅不好理解,传参的时候也麻烦。此外,由于引用实际上是给变量取别名,所以left和right实际上就是a和b,换句话说这两个变量不需要压栈,这就减少了空间开销。
不过话又说回来,引用的底层实际上还是用指针来实现的

  1. 做返回值
int& Count(){static int n = 0;n++;// ...return n;}

这里的返回值是n的一个引用,我们就可以通过改变返回值来改变n的值。但是正如指针会有野指针,引用也有野引用。引用做返回值的时候,需要注意返回的对象没被销毁。因此只有堆、静态区、全局变量才能返回引用。

学习了引用,细心的读者也可以发现这简直和指针太像了,一股子指针味。这下谁还分得清什么是指针,什么是引用啊!无妨,接下来我们谈谈引用和指针的区别:

  1. 语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
  2. 引用在定义时必须初始化,指针没有要求
  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
  4. 没有NULL引用,但有NULL指针
  5. sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
  6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  7. 有多级指针,但是没有多级引用(给引用再引用,实际上也只是给原变量取多一个别名而已。)
  8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  9. 引用比指针使用起来相对更安全

阅读上面的指针和引用的区别,我们也了解到了引用的一些不足之处,但是总体来说,相较于指针,引用还是更方便的。

内联函数

学习内联函数前,我们要知道编译器调用函数时要进行申请栈帧、压栈、弹栈等操作的
如果一段较为简短的代码在程序里反复调用,那么上述过程产生的开销就很大。因此在c++中专门用一个关键字inline修饰内敛函数,来达到类似宏定义函数的效果,从而解决上述问题。
如下:

inline int Add(int a=0,int b=0)
{return a+b;
}
int main()
{Add();Add();Add();Add();Add();Add();Add();Add();Add();return 0;
}

如上函数,如果不加上inline修饰,那么Add调用消耗栈帧就比较大。现在用inline修饰后,Add这个函数会进行代码块展开,这用就不用进行开辟栈帧、压栈、弹栈等操作。
inline的特性:

  • inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
  • inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性
  • inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址
    了,链接就会找不到。

auto关键字

随着程序逐渐复杂,变量类型也逐渐复杂。就算是一个简单的函数int Add(int ,int ),其函数指针的类型为int (*)(int ,int ),这也是比较复杂的。虽然可以通过typedef来进行一定程度的改善,但是就算是使用typedef也是比较繁琐的。
为了解决上述的问题,c++使用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都用了auto来自动识别类型,其中b为int,c为char,d为int。是不是感觉方便了不少呢?
需要注意的是,使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
auto的使用场景:

  • auto与指针和引用结合起来使用,用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。(当然auto*声明的变量必须是指针类型,而auto声明的变量类型既可以是指针类型,也可以不是指针类型)
  • 在同一行定义多个变量
    当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译
    器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
void TestAuto(){auto a = 1, b = 2; auto c = 3, d = 4.0;  // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}

auto不能使用的场景:

  • auto不能作为函数的参数
  • auto不能直接用来声明数组
void TestAuto(){int a[] = {1,2,3};auto b[] = {456};//不可行}

基于范围的for循环

范围for循环:
一个已知范围的数组可以用如下的遍历方式:

int main()
{int arr[]={1,2,3,4,5,6,7,8,9,10};for(int i=0;i<sizeof(arr)/sizeof(int);i++)arr[i]++;return 0;
}

对于一个已知范围的数组,需要遍历其元素时用上述方式显得繁琐,而且还是错误的风险。
在c++中,我们就可以使用下面的方式来遍历数组元素:

void TestFor(){int array[] = { 1, 2, 3, 4, 5 };for(auto& e : array)e *= 2;for(auto e : array)cout << e << " ";return;}

在这里插入图片描述
就这样,我们使用了一种较为简洁的方式来遍历有范围的数组。
范围for的使用条件:

  • for循环迭代的范围必须是确定的。对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
void TestFor(int array[]){for(auto& e : array)cout<< e <<endl;}

上述代码就是一个典型的使用错误,因为函数压栈时是绝对不会传数组的,这样的空间开销太大。所以这里的array是一个指针,因此array的范围并不明确。

  • 迭代的对象要实现++和==的操作。

指针空值nullptr

在C语言中我们常用NULL来赋值空指针,但实际上NULL是一个宏定义的值,本质是int 0.
前面我们学习了函数重载,由此就引出了一个问题。

void Test(int x)
{cout << "int" << endl;
}
void Test(int* x)
{cout << "int*" << endl;
}
int main()
{Test(0);Test(NULL);return 0;
}

上述代码的理想的输出结果为:
int
int*
但实际的输出结果为:
在这里插入图片描述
也就是我们的NULL被识别成了int类型,这和我们的本意显然是冲突的。
由此,c++引入了指针空值nullptr。
注意:

  • 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
  • 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
  • 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

后记

以上就是大致的C和C++的一些用法上的不同,显然C++的各种语法改良了C语言很多不足的地方,这也为我们叩响了C++的敲门砖。就借着此处的优势继续向着C++的更深处进发吧。

这篇关于C到C++的敲门砖-2的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/821756

相关文章

Windows下C++使用SQLitede的操作过程

《Windows下C++使用SQLitede的操作过程》本文介绍了Windows下C++使用SQLite的安装配置、CppSQLite库封装优势、核心功能(如数据库连接、事务管理)、跨平台支持及性能优... 目录Windows下C++使用SQLite1、安装2、代码示例CppSQLite:C++轻松操作SQ

C++中RAII资源获取即初始化

《C++中RAII资源获取即初始化》RAII通过构造/析构自动管理资源生命周期,确保安全释放,本文就来介绍一下C++中的RAII技术及其应用,具有一定的参考价值,感兴趣的可以了解一下... 目录一、核心原理与机制二、标准库中的RAII实现三、自定义RAII类设计原则四、常见应用场景1. 内存管理2. 文件操

C++中零拷贝的多种实现方式

《C++中零拷贝的多种实现方式》本文主要介绍了C++中零拷贝的实现示例,旨在在减少数据在内存中的不必要复制,从而提高程序性能、降低内存使用并减少CPU消耗,零拷贝技术通过多种方式实现,下面就来了解一下... 目录一、C++中零拷贝技术的核心概念二、std::string_view 简介三、std::stri

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

C++ 函数 strftime 和时间格式示例详解

《C++函数strftime和时间格式示例详解》strftime是C/C++标准库中用于格式化日期和时间的函数,定义在ctime头文件中,它将tm结构体中的时间信息转换为指定格式的字符串,是处理... 目录C++ 函数 strftipythonme 详解一、函数原型二、功能描述三、格式字符串说明四、返回值五

C++作用域和标识符查找规则详解

《C++作用域和标识符查找规则详解》在C++中,作用域(Scope)和标识符查找(IdentifierLookup)是理解代码行为的重要概念,本文将详细介绍这些规则,并通过实例来说明它们的工作原理,需... 目录作用域标识符查找规则1. 普通查找(Ordinary Lookup)2. 限定查找(Qualif

C/C++ chrono简单使用场景示例详解

《C/C++chrono简单使用场景示例详解》:本文主要介绍C/C++chrono简单使用场景示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友... 目录chrono使用场景举例1 输出格式化字符串chrono使用场景China编程举例1 输出格式化字符串示

C++/类与对象/默认成员函数@构造函数的用法

《C++/类与对象/默认成员函数@构造函数的用法》:本文主要介绍C++/类与对象/默认成员函数@构造函数的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录名词概念默认成员函数构造函数概念函数特征显示构造函数隐式构造函数总结名词概念默认构造函数:不用传参就可以

C++类和对象之默认成员函数的使用解读

《C++类和对象之默认成员函数的使用解读》:本文主要介绍C++类和对象之默认成员函数的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、默认成员函数有哪些二、各默认成员函数详解默认构造函数析构函数拷贝构造函数拷贝赋值运算符三、默认成员函数的注意事项总结一

C/C++中OpenCV 矩阵运算的实现

《C/C++中OpenCV矩阵运算的实现》本文主要介绍了C/C++中OpenCV矩阵运算的实现,包括基本算术运算(标量与矩阵)、矩阵乘法、转置、逆矩阵、行列式、迹、范数等操作,感兴趣的可以了解一下... 目录矩阵的创建与初始化创建矩阵访问矩阵元素基本的算术运算 ➕➖✖️➗矩阵与标量运算矩阵与矩阵运算 (逐元