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

相关文章

VSCode中C/C++编码乱码问题的两种解决方法

《VSCode中C/C++编码乱码问题的两种解决方法》在中国地区,Windows系统中的cmd和PowerShell默认编码是GBK,但VSCode默认使用UTF-8编码,这种编码不一致会导致在VSC... 目录问题方法一:通过 Code Runner 插件调整编码配置步骤方法二:在 PowerShell

C/C++随机数生成的五种方法

《C/C++随机数生成的五种方法》C++作为一种古老的编程语言,其随机数生成的方法已经经历了多次的变革,早期的C++版本使用的是rand()函数和RAND_MAX常量,这种方法虽然简单,但并不总是提供... 目录C/C++ 随机数生成方法1. 使用 rand() 和 srand()2. 使用 <random

Win32下C++实现快速获取硬盘分区信息

《Win32下C++实现快速获取硬盘分区信息》这篇文章主要为大家详细介绍了Win32下C++如何实现快速获取硬盘分区信息,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 实现代码CDiskDriveUtils.h#pragma once #include <wtypesbase

C++ Primer 标准库vector示例详解

《C++Primer标准库vector示例详解》该文章主要介绍了C++标准库中的vector类型,包括其定义、初始化、成员函数以及常见操作,文章详细解释了如何使用vector来存储和操作对象集合,... 目录3.3标准库Vector定义和初始化vector对象通列表初始化vector对象创建指定数量的元素值

C++实现回文串判断的两种高效方法

《C++实现回文串判断的两种高效方法》文章介绍了两种判断回文串的方法:解法一通过创建新字符串来处理,解法二在原字符串上直接筛选判断,两种方法都使用了双指针法,文中通过代码示例讲解的非常详细,需要的朋友... 目录一、问题描述示例二、解法一:将字母数字连接到新的 string思路代码实现代码解释复杂度分析三、

C++一个数组赋值给另一个数组方式

《C++一个数组赋值给另一个数组方式》文章介绍了三种在C++中将一个数组赋值给另一个数组的方法:使用循环逐个元素赋值、使用标准库函数std::copy或std::memcpy以及使用标准库容器,每种方... 目录C++一个数组赋值给另一个数组循环遍历赋值使用标准库中的函数 std::copy 或 std::

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没