C++:与C语言相比的特点

这篇具有很好参考价值的文章主要介绍了C++:与C语言相比的特点。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

命名空间

先看到一段C语言的代码:

#include <stdio.h>
#include <stdlib.h>

int rand = 1;

int main()
{
    printf("%d", rand);
    return 0;
}

这段代码看似没有问题,但是运行后,编译器会报出“rand重定义”的问题。
这是因为我们引入了头文件stdlib.h,而其内部有rand函数,用户的变量名与头文件冲突了。
这该这么解决?
在C语言中,好像没有什么很好的办法,让不同头文件中的同名变量共存,只能让其中一者改变自己的变量名。

C++设计者认为这个特性不利于项目合作,当我们对多个员工编写的C语言代码进行合并时,就有可能出现这个问题,此时只能让其中一者修改代码。
C++为此设计了一种新的域:命名空间域

命名空间域

在不同的域中,是可以存在同名变量的,而C语言只存在局部域与全局域两种域。C++的命名空间域则是一种可以根据用户需要自己定义的域。

  1. 现在当前作用域查找
  2. 如果当前作用域查找不到,就向上级作用域查找
  3. 直到查找到全局作用域,如果此时还没有,编译器报错

 命名空间域有以下特性:

  1. 当两个命名空间域重名,两个域内部的代码会合并
  2. 作用域可以嵌套
  3. 变量,结构体,函数等等都可以写入这个域中
域作用限定符

::是C++中的域作用限定符,将其放在变量前,可以改变此变量的查找规则,使之直接到指定域中查找。

此外:域作用限定符左侧没有值时,默认到全局变量查找。
这一点很重要,因为在基本的查找规则中,是先查找局部作用域,再查找全局作用域的。而当::左侧没有值时,会直接跳过局部变量,在全局中查找。 

比如以下代码:

int a = 3;

int main()
{
	int a = 4;
	printf("%d", ::a);
	return 0;
}

上述代码的输出结果是3。
虽然在局部中有一个a = 4,但是::a会直接跳过局部,直接去全局查找,所以最后输出了3.

访问嵌套的命名空间域:
想要访问嵌套的命名空间域,只需要依据从外层->内层的顺序,利用::将每个名称分隔开,就可以访问了,如下:

namespace A
{
	namespace B
	{
		namespace C
		{
			int a = 2;
		}
	}
}

int main()
{
	printf("%d", A::B::C::a);
	return 0;
}

我们可以按照如下方式解决不同文件变量可能存在冲突的问题:每个.cpp文件最外层,用一个命名空间域包含起来,后续引入文件时,每个人编写的文件独自享有一个域,就不会发生冲突问题了。

user1.cpp

namespace user1
{
	int a = 0;
	int b = 1;

	int Add(int x, int y)
	{
		return x + y;
	}
}

user2.cpp: 

namespace user2
{
	int a = 1;
	int b = 0;

	float Add(float x, float y)
	{
		return x + y;
	}
}

 每一份.cpp文件都用一个命名空间域包在最外层,需要使用谁的代码时,就到哪一个空间域中查找。

展开命名空间域

所谓展开命名空间域,就是对某个空间域进行展开,将其内部的变量放到全局中。也就是说,一个空间域的内容,经过展开后就会变成全局变量,而变量查找规则中,最后一层就是在全局中查找,所以可以不使用::就访问到想要的变量。

using namespace (名称);

但是有时候我们并不是需要一个命名空间域中的所有内容,如果将整个空间域展开有些没必要。
此时我们可以使用部分展开

using (名称)::(变量名) 

缺省参数

全缺省参数

缺省参数是值可以为函数的参数设置初始值,如果调用时没有传入参数,则此参数以初始值调用函数。

int Add(int x = 5, int y = 10)
{
	return x + y;
}

int main()
{
	Add(1, 2);
	Add(1);
	Add();

	return 0;
}

上述代码中,我们定义了一个函数Add,其带有两个参数x和y,其中为x设置初始值x = 5,给y设置初始值y = 10。

第一次调用Add(1, 2);为xy都传了参数,此时完成的是1 + 2。
第二次调用Add(1);只为x传入了参数,此时y以初始值调用此函数,完成的是1 + 10。
第三次调用Add();没有传入参数,此时x和y都以初始值调用此函数,完成的是5 +10。

这种参数缺省叫做全缺省参数,即所有的参数都赋予了初始值,哪怕一个参数都不传,也可以调用函数。
注意:传入参数必须从左往右传入,不能有空缺

半缺省参数

半缺省参数是指,缺省参数时,有一些值不赋予初始值,必须传入值

要注意:半缺省参数中不赋予初始值的参数,必须从左往右连续,不可以间断地缺省。

int Add(int x = 5, int y, int z = 20)
{
	return x + y + z;
}

这种代码就是错误的!!!

最后还有一个注意点:不能在声明和定义时同时缺省参数

当函数的声明和定义中都出现了缺省参数,如果声明和定义缺省不一样,此时编译器不知道是听谁的,因此,缺省参数不能同时存在与声明和定义中。

看到一个示例:
test.h文件中:

void func(int a = 10);

  test.cpp文件中:

void func(int a = 10)
{
	cout << a * 5 << endl;
}

那么应该在什么地方进行缺省呢?

答案是在声明

假设只存在于定义中:函数的声明和定义是在链接阶段才会合在一起,在编译阶段,.h 和 .cpp是在各走各的。在编译阶段,在头文件.h 中,func函数的参数是(int a),编译器并不知道我们在定义中设置的参数的缺省值,因此会发生编译报错。

假设只存在于声明中:函数编译调用函数时主要寻找函数声明,找到函数声明即找到函数地址,这样可以在链接阶段通过函数地址找到函数定义运行,所以在声明时缺省可以让编译器识别传参个数正确与否,从而编译不会报错。

函数重载

函数重载是指C++允许在同一作用域中声明的同名函数,但是其必须遵守一项规则:保证同名函数的形参列表不同。

形参列表不同就是要求满足以下三者之一:

  1. 函数的参数个数不同
  2. 函数的参数类型不同
  3. 函数的参数类型的顺序不同

引用

基本语法

C++的引用是一种特殊的变量类型,用于给已经存在的变量起一个别名。通过引用,我们可以通过一个已存在的变量名来访问和操作另一个变量的值。

引用可以被看作是一个已存在变量的别名,引用和被引用的变量始终指向同一块内存空间,对引用的操作实际上就是对被引用变量的操作。

引用的语法如下:

type& 别名 = 变量名;

 其中,type是被引用变量的类型。

需要注意的是,引用不同于指针,它不能指向空值或者没有初始化的变量。因此,在定义引用时必须保证所引用的变量已经存在,并且在定义引用时必须进行初始化

也就是说下面的语句是非法的:

int& a;

引用其实不单单只是代替指针这么简单,其还可以作为返回值,参数等。

按引用传递

C++中的按引用传递是一种参数传递方式,它允许函数通过引用来操作调用者提供的实参。

按引用传递是将实参的引用传递给形参。

按引用传递的语法是在函数的参数前加上&符号。例如,以下的函数原型中使用了按引用传递:

void Function(int& x);

        

按引用传递有以下几个作用:

  1. 通过引用传递参数可以避免对大型对象的复制。当传递一个大型对象时,按值传递会进行一次复制操作,而按引用传递只需要传递对象的引用而不需进行复制,从而提高了程序的效率。
  2. 通过引用传递参数可以实现函数对实参的修改。在函数内部,通过引用可以直接操作实参,对实参的修改会在函数外部产生影响。而按值传递只能修改函数内部的形参副本,对实参没有影响。

按引用传递是一种高效且灵活的参数传递方式,可以减少内存的复制操作,实现对实参的修改。在C++中,通过引用传递可以提高程序的效率和可读性。

返回引用

在C++中,返回引用是指从函数中返回一个引用类型的值。返回引用的主要目的是允许函数返回一个对于某个变量的引用,从而允许在函数外部对该变量进行修改。

返回引用的主要用途有以下几个:

  1. 允许函数直接修改函数外部的变量。
  2. 允许在函数调用中连续进行操作,类似于链式操作。
  3. 优化性能,避免创建临时对象。

下面通过案例来分别说明这几个功能: 

  1. 允许函数直接修改函数外部的变量:
    int& increment(int& num) {
      num++;
      return num;
    }
    
    int main() {
      int num = 5;
      increment(num) = 10;
      cout << num << endl;  // 输出为 10
      return 0;
    }
    

    在上面的例子中,increment函数返回了对num的引用。在main函数中,我们可以直接对increment(num)进行赋值操作,相当于对num进行了修改。

  2. 允许在函数调用中连续进行操作:
    int& add(int& num, int value) {
      num += value;
      return num;
    }
    
    int main() {
      int num = 5;
      add(add(num, 3), 2);
      cout << num << endl;  // 输出为 10
      return 0;
    }
    

    在上面的例子中,add函数返回了对num的引用。我们可以连续调用add函数,每次都对num进行修改。

优化性能,避免创建临时对象:

string& concatenate(string& str1, const string& str2) {
  str1 += str2;
  return str1;
}

int main() {
  string str1 = "Hello";
  string str2 = " World";
  concatenate(str1, str2) += "!";
  cout << str1 << endl;  // 输出为 "Hello World!"
  return 0;
}

在上面的例子中,concatenate函数返回了对str1的引用。通过返回引用,我们可以直接对str1进行修改,避免了创建临时对象。在调用concatenate函数的时候,我们可以将返回的引用与另一个字符串连接操作进行连续调用。

需要注意的是,返回引用时,被返回的变量应该仍然存在(即出了作用域变量的生命周期还没结束),否则返回的引用就会变成悬空引用,可能导致不可预期的行为。此外,如果返回引用指向了一个局部变量,函数返回后该变量将被销毁,返回的引用将变得无效。因此,返回引用时需要确保引用的有效性。

引用与指针的区别        
  1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
  2. 引用在定义时必须初始化,指针没有要求
  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
  4. 没有NULL引用,但有NULL指针
  5. 在sizeof中含义不同: 引用结果为引用类型的大小,但指针始终是地址空间所占字节个数
  6. 引用自加即用的实体增加1,指针自加即指针向后偏移一个类型的大小
  7. 有多级指针,但是没有多级引用
  8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  9. 引用比指针使用起来相对更安全

内联函数

在讲解内联函数前,我们前看看C语言中的宏的缺点。
C语言宏的缺点有以下几个:

1.没有类型检查: 宏是在预处理阶段进行替换,没有类型检查的机制。因此,使用宏时要特别小心,否则可能会出现类型不匹配的错误。
2.可读性差: 宏通常会展开为较长的代码,可能会使代码变得难以阅读和理解。特别是在宏内部使用复杂的表达式或多行代码时,会使代码的可读性大大降低。
3.可能引起副作用: 宏通常会直接对参数进行替换,可能会导致意外的副作用。例如,一个宏可能会多次计算参数的值,如果参数是一个函数调用或者是一个带有副作用的表达式,那么可能会引发错误。
4.可能导致重复的代码: 使用宏可能导致代码中出现大量的重复代码。当多个地方使用相同的宏时,如果需要修改宏的实现方式,就需要修改所有使用该宏的地方,增加了代码维护的复杂性。
5.调试困难: 宏在展开后的代码中看不到宏本身的定义,因此在调试时很难跟踪和查找问题。由于宏在编译阶段被替换,调试器无法直接定位到宏的定义位置,这给调试带来了一定的困难。

C++认为宏是一个不太好的特性,于是在C++中推荐使用enum枚举和const替换掉宏常量。用内联函inline替换掉宏函数。 

被inline修饰的函数叫做内联函数,在编译时C++编译器会在调用内联函数的地方将内联函数展开,不额外创建栈帧来执行函数,提高程序的效率。

本质上是函数指令在执行处直接展开。所以如果宏函数非常长,那么对其展开时会导致代码重复性非常高,这已经违背了函数设计的初衷:代码复用。
所以内联函数有另外一个特性:当函数体内部代码长度超过一定值时,其会转化为普通函数,不会直接展开,而是创建栈帧,防止代码冗余。

auto

在C++中,auto关键字可以用来自动推断变量的类型,它在编译时会根据初始化表达式的类型来确定变量的类型。

auto与指针和引用结合

auto也可以自动推断指针的类型,比如这样:

int x = 10;
auto y = &x;

此时y的类型自动判别为int*
那么我们可不可以为auto加上*来识别指针?

int x = 10;

auto* a1 = x;
auto* a2 = &x;
auto a3 = &x;

在auto* a1 = x;中,x的类型是int,那么auto本应将其值判别为int,但是由于auto*被*限制了,此时auto必须得到一个指针,所以编译器会报错;而auto* a2 = &x;得到的就是指针,此时代码不会报错,可以正常识别为int*。

在本质上auto* a2 = &x;和auto a3 = &x;的结果是没有区别的,只是auto*要求得到的必须是一个指针类型,而auto不限制其类型。
同理的auto&会要求必须是一个引用类型,否则会报错。

auto也有许多限制,要注意以下问题:

  1. auto不能作为函数的参数

  2. auto不能用于声明数组

  3. 在同一行定义多个变量时,如果将auto作为其类型,必须一整行都是同一个类型的变量。

范围for循环

范围for循环是C++11引入的一种新的循环结构,它可以方便地遍历数组或者其他具有迭代器的对象。

范围for循环的语法如下:

for (auto element : collection) {
   // 执行语句
}

其中,element 是一个临时变量,用来存储集合中的每个元素的副本,collection 是一个可迭代的对象,可以是数组或者其他具有迭代器的对象。
其中auto也可以换为intfloat等类型,只是结合auto会更好用。

int main() {
   int numbers[] = {1, 2, 3, 4, 5};

   for (auto element : numbers) {
      cout << element << " ";
   }

   return 0;
}

输出结果为:1 2 3 4 5

在上面的例子中,我们定义了一个整型数组 numbers,范围for循环遍历了整个数组,每次迭代将数组中的一个元素赋值给临时变量 element,然后我们将该元素输出到控制台。

如果你希望修改这个数组内部的值,可以在auto后加上&,将其变为一个引用。

   for (auto& element : numbers) {
      element *= 2;
   }

nullptr

在C++11标准中,引入了nullptr关键字来表示空指针。C++推荐使用nullptr而不是使用传统的NULL宏定义。

NULL在传统的C++中只是一个宏定义为0,会被隐式转换为整型,这可能导致一些类型安全性问题。nullptr不会被隐式转换为其他类型,只能赋值给指针类型,从而避免了潜在的类型错误。

其次是代码清晰度,nullptr相比于NULL更加直观明了,能够更好地表示空指针的含义即null + ptr,null表示空ptr表示指针。这样可以提高代码的可读性。

所以在C++中,定义一个空指针最好用nullptr。文章来源地址https://www.toymoban.com/news/detail-828747.html

到了这里,关于C++:与C语言相比的特点的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C语言的发展及特点

    C语言作为计算机编程领域的重要里程碑,其发展历程承载着无数开发者的智慧和创新。C语言诞生于20世纪70年代初,由计算机科学家Dennis Ritchie在贝尔实验室首次推出。当时,Ritchie的目标是为Unix操作系统开发一门能够更方便地进行系统编程的语言。事实证明,C语言不仅在U

    2024年02月11日
    浏览(40)
  • Ruby软件外包开发语言特点

    Ruby 是一种动态、开放源代码的编程语言,它注重简洁性和开发人员的幸福感。在许多方面都具有优点,但由于其动态类型和解释执行的特性,它可能不适合某些对性能和类型安全性要求较高的场景。下面和大家分享 Ruby 语言的一些主要特点以及适用的场景,希望对大家有所

    2024年02月12日
    浏览(52)
  • C# 编程语言有什么特点?

    在开始前我有一些资料,是我根据网友给的问题精心整理了一份「C#的资料从专业入门到高级教程」, 点个关注在评论区回复“888”之后私信回复“888”,全部无偿共享给大家!!!C#(C Sharp)是一种由Microsoft开发的多范式编程语言,最初发布于2000年。以下是C#编程语言的一

    2024年01月22日
    浏览(57)
  • 什么是脚本语言,解释脚本语言的特点和应用领域

    脚本语言是一种编程语言,通常用于自动化任务或脚本。它们通常比传统的编程语言更容易学习和使用,因为它们通常具有更少的语法和更简单的命令。 脚本语言的特点包括: 简单易学:脚本语言通常具有简单的语法和命令,使得它们易于学习和使用。 快速执行:脚本语言

    2024年02月09日
    浏览(38)
  • Rust软件外包开发语言的特点

    Rust 是一种系统级编程语言,强调性能、安全性和并发性的编程语言,适用于广泛的应用领域,特别是那些需要高度可靠性和高性能的场景。下面和大家分享 Rust 语言的一些主要特点以及适用的场合,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发公

    2024年02月12日
    浏览(51)
  • 【算法】动态规划 ⑧ ( 动态规划特点 )

    求解类型 : 动态规划 必须是求 最值 , 可行性 , 方案数 , 三者之一 , 如果求其它内容 , 则不能使用动态规划算法 ; 求最值 : 最大值 , 最小值 等 ; 大规模问题的结果 由 小规模问题 的计算结果 取最大值 大规模问题的结果 由 小规模问题 的计算结果 取最小值 可行性 : 是否可行

    2023年04月08日
    浏览(48)
  • 探究贪心算法:特点与实际应用

    博主 默语带您 Go to New World. ✍ 个人主页—— 默语 的博客👦🏻 《java 面试题大全》 《java 专栏》 🍩惟余辈才疏学浅,临摹之作或有不妥之处,还请读者海涵指正。☕🍭 《MYSQL从入门到精通》数据库是开发者必会基础之一~ 🪁 吾期望此文有资助于尔,即使粗浅难及深广,亦

    2024年04月13日
    浏览(54)
  • Python编程语言的特点(优点和缺点)

    目录 Python的优点 1) 语法简单 2) Python 是开源的 3) Python 是免费的 4) Python 是高级语言 5) Python 是解释型语言,能跨平台 6) Python 是面向对象的编程语言 7) Python 功能强大(模块众多) 8) Python 可扩展性强 Python 的缺点 1) 运行速度慢 2) 代码加密困难 Python 是一种开源的解释型脚本编

    2024年02月07日
    浏览(84)
  • 机器人寻路算法双向A*(Bidirectional A*)算法的实现C++、Python、Matlab语言

    最近好久没更新,在搞华为的软件挑战赛(软挑),好卷只能说。去年还能混进32强,今年就比较迷糊了,这东西对我来说主要还是看运气,毕竟没有实力哈哈哈。 但是,好歹自己吭哧吭哧搞了两周,也和大家分享一下自己的收获吧,希望能为后来有需要的同学提供一些帮助

    2024年04月13日
    浏览(44)
  • Java入门指南:Java语言优势及其特点

    目录 1. Java语言简介及发展概述 2. Java语言的优势  2.1 可移植性 2.2 面向对象 2.3 安全性 2.4 大量类库 3. Java语言与C/C++的区别 4. 初识Java程序入口之main方法  5. 注释、标识符、 5.1 注释 5.2 标识符 5.3 Java是一种面向对象的编程语言,由Sun Microsystems(现为Oracle)于

    2024年02月14日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包