C++:入门学习C++,它在C的基础上做了哪些修改?

这篇具有很好参考价值的文章主要介绍了C++:入门学习C++,它在C的基础上做了哪些修改?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

命名空间

首先看这样的代码:

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

int rand = 0;

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

上述代码可以编译通过吗?很明显是不可以的,原因在于头文件stdlib中包含了rand函数,因此在定义变量时就不可以再继续使用rand作为你的变量了

而在未来写工程项目中,这样的情况会遇见很多,在包含某个头文件后,代码中的许多变量就不可以再使用了,这就体现出了C语言的一部分局限性

那么C++在C语言的基础上很好的改善了这个问题,C++引入了命名空间的概念,把变量命名在某个空间内,这样就能很好的解决这个问题

命名空间的定义很自由,可以定义变量,定义函数,定义结构体,甚至可以嵌套定义

namespace zbh
{
	//定义变量
	int test = 0;

	//定义函数
	int Add(int x, int y)
	{
		return x + y;
	}

	//定义结构体
	struct MyStruct
	{
		int a;
		int b;
	};

	//命名空间可以嵌套
	namespace free
	{
		int print1()
		{
			return 1;
		}
	}
}

命名空间是如何使用的?C++如何保证命名空间的独立性?

  1. 使用变量时单独说明
  2. 前面定义使用命名空间中的某个函数或变量等
  3. 直接展开
#include <stdio.h>

namespace zbh
{
	//定义变量
	int test = 0;

	//定义函数
	int Add(int x, int y)
	{
		return x + y;
	}

	//定义结构体
	struct MyStruct
	{
		int a;
		int b;
	};

	//命名空间可以嵌套
	namespace free
	{
		int print1()
		{
			return 1;
		}
	}
}

using zbh::Add;

int main()
{
	printf("%d\n", zbh::test);
	printf("%d\n", Add(1,2));
	printf("%d", zbh::free::print1());
}

上面对命名空间的定义也可以省略,直接在main函数前加上

using namespace zbh;

即可直接在后面的函数中使用,通过这样的方式即实现了函数命名空间的独立化

缺省参数

C++中对于函数参数定义了缺省参数,可以理解为,如果我对函数参数中的成员赋给了它一个初值,那么在后续调用的过程中,如果我并未给函数传参,那么函数就会使用默认的参数

具体样例如下所示

#include <iostream>
using namespace std;

void f(int a = 10, int b = 20, int c = 30)
{
	cout << a << " " << b << " " << c << endl;
}

int main()
{
	f();
	f(1);
	f(1, 2);
	f(1, 2, 3);
	return 0;
}

运行结果如下:
C++:入门学习C++,它在C的基础上做了哪些修改?,C++,知识总结,c++,笔记
在实际中这样的操作有什么作用??

在定义顺序表中,我们使用的是动态开辟的顺序表,那么在初始化阶段我们是不是可以利用缺省参数优化一些步骤?

首先看C语言实现过程中的方法

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

typedef int SLDataType;
typedef struct Seqlist
{
	SLDataType* a;
	int size;
	int capacity;
};

void SeqlistInit(Seqlist* s)
{
	s->a = (SLDataType*)malloc(sizeof(SLDataType) * 4);
	s->size = 0;
	s->capacity = 4;
}

这样的实现实际上把容量写死了,不管要开辟多大的顺序表我们都是先开辟容量为4的顺序表再后续进行扩容,而扩容用的realloc是有消耗的

但假设如果我们使用缺省参数进行实现这个函数,可以优化很多

#include <iostream>
using namespace std;

typedef int SLDataType;
typedef struct Seqlist
{
	SLDataType* a;
	int size;
	int capacity;
}Seqlist;

void SeqlistInit(Seqlist* s,int capacity=4)
{
	s->a = (SLDataType*)malloc(sizeof(SLDataType) * capacity);
	s->size = 0;
	s->capacity = capacity;
}

int main()
{
	Seqlist sq,sl;
	SeqlistInit(&sl);
	SeqlistInit(&sq, 100);
	return 0;
}

调用监视观察可以看到

C++:入门学习C++,它在C的基础上做了哪些修改?,C++,知识总结,c++,笔记
利用缺省参数,我们确实实实在在实现了自由确定自己想要的容量

函数重载

C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型
不同的问题。

下面展示了什么是函数重载

#include <iostream>
using namespace std;

void func(int a, double b)
{
	cout << "void func(int a, double b)" << endl;
}

void func(double b, int a)
{
	cout << "void func(double b, int a)" << endl;
}

int main()
{
	func(1, 1.1);
	func(2.2, 2);
	return 0;
}

当同名函数中参数个数或参数类型不同时,构成了函数重载,编译器会自动识别传参的个数和类型对应不同的函数重载类型

那为什么C++支持,但是C语言不支持呢?

这里需要讲到一个程序运行起来需要经历的过程

预处理,编译,汇编,链接

假设现在定义了一个Add函数,这个Add函数是在a.cpp文件中定义的,而我要在b.cpp中使用这个函数,当使用后运行程序时,编译器要在b.cpp中使用该函数,但是却找不到这个函数的地址,因此编译器会去b.cpp的符号表中去寻找
Add的地址,然后把这两个链接在一起

在链接的过程中,C和C++就产生了不一样的地方,每个编译器都有自己专属的函数名修饰的规则

下面展示的是C语言编译器的展示结果:
C++:入门学习C++,它在C的基础上做了哪些修改?,C++,知识总结,c++,笔记
从中可以看出,在编译完成后,函数名字的修饰依旧是函数的名字,没有实质性的变化

下面再看C++编译器中的样例

C++:入门学习C++,它在C的基础上做了哪些修改?,C++,知识总结,c++,笔记
对比C语言的编译器可以很清楚的看到,C++的编译器在编译的过程中把函数名进行了一定程度的修饰,在Linux的编译器下是把函数的参数类型和参数个数也加到了函数名字当中

因此,从中就知道了,为什么C语言不支持函数重载,而C++支持了,就是因为C语言编译器对函数名就是函数名,而C++的编译器对函数名做了参数的引入进行修饰,因此重载后的函数有不同的函数名

从中也就能很轻松的理解,为什么函数参数的类型和个数一样,只有返回值不一样的时候,无法构成函数重载,就是因为编译器对于两个同名的函数不知道该调用哪一个

引用

C++在C的基础上做出的另一大调整就是引用

简单来说,引用就是给变量起了一个别名,变量本身和引用一起控制变量所在的区域

有下面的代码:

void test2()
{
	int a = 10;
	int& b = a;
	cout << &a << endl;
	cout << &b << endl;
}

那么这个程序的运行结果是多少呢

C++:入门学习C++,它在C的基础上做了哪些修改?,C++,知识总结,c++,笔记
从中可以看出,引用并不是单独再开辟一块空间用来管理所指向的对象,而是直接和原来的变量一起控制某块区域内的内容

引用的一些特性

  1. 引用在定义的时候必须初始化
  2. 一个变量可以有多个引用
  3. 引用一旦确认了一个实体,就不能引用其他内容了

引用作为函数返回值

引用作为函数返回值是一件危险的事,但如果使用正确是有高回报的

我们以下面的操作为例

int& Count()
{
	int n = 1;
	n++;
	return n;
}

void test2()
{
	int a = 10;
}

int main()
{
	int& ret = Count();
	cout << ret << endl;
	test2();
	cout << ret << endl;
	return 0;
}

输出结果为2和10,这是为什么?

原因上升到了函数栈帧的问题,首先画出函数栈帧

下面画出的是main函数执行前两行时候的操作,后续还未画出

C++:入门学习C++,它在C的基础上做了哪些修改?,C++,知识总结,c++,笔记
这里定义了&ret,它接收了来自Count函数中的返回值int& n,实际上,这里的ret已经具有了管理n那块区域的能力,而我们又知道,函数在结束后栈帧会被销毁,这里的销毁只是失去了对内存中这片区域的管理权,内存中这块区域本身还是存在的

因此,当下面执行test2函数时,又会开辟一块新的栈帧,而这块栈帧所在的位置和Count函数的栈帧是有很大重合区域的

C++:入门学习C++,它在C的基础上做了哪些修改?,C++,知识总结,c++,笔记
因此,这里的ret已经具备了管理不属于它的空间的能力,它可以随时访问一块已经不属于它的区域,因此这里原本是n的空间,现在已经变成了a,但是它依旧可以访问,因此访问出的结果就是10了

因此,这里有需要注意的地方:

==如果函数返回时,出了函数的作用域,如果返回对象还在(没有还给系统),那么这里就可以使用引用返回,如果是像这样的临时变量或者局部变量,就必须使用传值返回

那么说回来,既然引用作为返回值或者参数如此危险,那它有什么使用的必要?

引用的收益

以函数传参为例,如果不用传引用会在函数传参的时候构建一个形参,如果传递的是对象,那么会执行很多默认的成员函数,这也是性能上的损耗,而如果传递的是引用,直接把已经创建好的对象引用到这里,略去了形参创建再销毁这个过程

传引用传参的优势

  1. 提高效率
  2. 输出型参数(形参的改变可以影响实参,类似于指针,但比指针简单一些)

继续谈作为引用返回的优势,作为引用返回也是诸多好处,当然是使用正确的前提;有这样的原则,什么时候可以使用传引用返回?结论是当返回的内容出了函数作用域不被销毁就可以,这是有相当高收益的,在类内的很多成员函数中,返回的通常是*this,而this并不会被销毁,这时用传引用的返回值就很值得

传引用返回的优势

  1. 提高效率
  2. 可以修改返回对象

综上所述, 传值传参还是传值返回值都会创建对象,而使用引用就避免了无效的,多余的创建带来的性能损耗

引用和指针的区别?

语法概念来讲引用是别名,没有自己的空间,它存在的本身就是和实体公用一块空间

但是
底层实现上引用是有自己的空间的,引用本身就是用指针的方式实现的

引用和指针的对比

引用和指针的不同点:

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

内联函数

首先,什么是内联函数?

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调
用建立栈帧的开销,内联函数提升程序运行的效率。

我们都知道,当main函数执行到某个函数时,会在内存中建立该函数的栈帧,再call(调用),而建立函数栈帧是有成本的,如果频繁的建立一些可能只有很少语句的函数会浪费程序运行的效率,因此内联函数的产生就解决了这个问题

内联函数工作的原理,就是在编译阶段,直接把函数体替换为函数调用,从汇编中可以看出,正常函数是有函数调用和函数栈帧的创建的,而内联函数直接把函数体全部都放到汇编

现在我们有这样的代码:

int add(int a, int b)
{
	return a + b;
}

int main()
{
	int a = 1;
	int b = 2;
	int c = add(a, b);
}

通过汇编来观察

C++:入门学习C++,它在C的基础上做了哪些修改?,C++,知识总结,c++,笔记
当执行到add函数时,确确实实是调用了add函数,那么假如把add函数设为内联函数

inline int add(int a, int b)
{
	return a + b;
}

int main()
{
	int a = 1;
	int b = 2;
	int c = add(a, b);
}

再来看它的汇编代码,就会发现不再是函数调用,而是直接把函数体展开

C++:入门学习C++,它在C的基础上做了哪些修改?,C++,知识总结,c++,笔记

内联函数的特性

这么来看,内联函数确实是很有用,但内联函数有它不可忽视的弊端

内联函数是一种用空间换时间的方法,不可否认,在这里略去了函数建立栈帧所需要的时间消耗,但是在编译阶段,内联函数会把函数调用全部替换为函数体,这会使得目标文件变大

因此,内联函数只是一个建议,只是建议编译器可以把这个函数当成内联函数来处理,但是具体到底有没有把它当成内联函数来处理还要看编译器本身,一般来说,会把函数规模比较小,不是递归函数,调用很频繁的函数设置为内联函数

同时,内联函数不应该声明和定义分离,内联函数被展开后,函数地址也就不复存在了,链接过程中就无法找到链接

内联函数和宏的关系

在C语言中,引入了宏,宏看似是一个很好的功能,但其中也有很多弊端

宏的优缺点?

优点:

  1. 增强代码的复用性。
  2. 提高性能。

缺点:

  1. 不方便调试宏。(因为预编译阶段进行了替换)
  2. 导致代码可读性差,可维护性差,容易误用。
  3. 没有类型安全的检查 。

因此,内联函数的出现也算是弥补了宏定义函数带来的诸多不便

指针空值问题

在良好的编程习惯中,定义一个变量要给它一定的初始值,因此在C语言中,我们定义一个指针时,对它的初始化常常是NULL

NULL实际上是一个宏

#define NULL  0

可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如:

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

程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。

在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void*)0。

因此,在C++的新标准中就引入了关于空指针:文章来源地址https://www.toymoban.com/news/detail-593689.html

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

到了这里,关于C++:入门学习C++,它在C的基础上做了哪些修改?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • OKCC在系统安全方面做了哪些措施?

    语音通信行业,运营者普遍比较关心的问题是,运营风险如何控制?运营安全如何保证?OKCC呼叫中心又有那些风控措施来保证运营安全。 当前阶段,语音通信运营,最主要的风险主要包括以下几个方面: 一、系统安全:     一个通信系统,往往既包括硬件系统,又包括软件

    2024年02月12日
    浏览(34)
  • Vue2 ➔ Vue3 都做了哪些改变?

    不是吧,兄弟,Vue3 都出来多久了,你还对这个感兴趣,说!是不是没好好卷?😏 俺也一样 😂,Vue3 出来之后只是简单了解了一下,然后还是转头一直在写 Vue2。当然,这也和大家搬砖 🧱 的处境有关。一般情况下,用 Vue2 起的项目没有什么大的问题,谁又会耗费经历去迁

    2024年02月16日
    浏览(37)
  • Swagger 3 对比于 Swagger 2 更新了哪些内容,做了哪些优化 ?Swagger 3 解读

    如果觉得本文对你有帮助,可以一键三连支持,

    2023年04月08日
    浏览(42)
  • Spring Boot 在启动之前还做了哪些准备工作?

    目录 一:初始化资源加载器 二:校验主要源 三:设置主要源 四:推断 Web 应用类型

    2024年02月05日
    浏览(47)
  • Java 17官方编程手册都针对哪些方面做了更新?

    Java 17,官方编程手册, 《International Developer》杂志称为“全世界醉著名的编程书籍创作者之一”的Herbert Schildt倾情解读 《Java官方编程手册》从1996年首次出版以来,已经经历了数次改版,每次改版都反映 了Java不断演化的进程。本书是第12版,针对JavaSE 17(JDK 17)进行了更新。因

    2024年02月11日
    浏览(44)
  • 数据仓库内容分享(十七):Doris实践分享:它做了哪些架构优化和场景优化?

    Apache Doris是一款开源的实时数据仓库,由百度旗下的技术团队开发。它具有高性能、高可靠性、易扩展等特点,能够满足大规模数据实时查询和分析的需求。目前,Apache Doris已经成为国内外众多企业的首选数据仓库解决方案,包括阿里巴巴、美团、京东、滴滴等知名企业。

    2024年02月21日
    浏览(55)
  • A Beginner‘s Guide to Apache Kafka: 什么是Kafka、它为什么如此受欢迎、它在哪些场景下可以应用、以及一些基本概念和术语

    作者:禅与计算机程序设计艺术 Apache Kafka(以下简称Kafka)是一个开源分布式流处理平台,它被设计用来实时传输大量的数据,从而能够实时的对数据进行处理并提取价值。本文通过梳理,引导读者了解什么是Kafka、它为什么如此受欢迎、它在哪些场景下可以应用、以

    2024年02月09日
    浏览(63)
  • 【C#基础】chatGpt带你学C#接口,它在游戏中有什么应用?

    In computer programming, an interface is a set of rules or guidelines that define how different software components or systems should interact with each other. It serves as a contract between two or more components, specifying how they should communicate with each other without revealing the underlying implementation details. An interface defines a standardi

    2023年04月21日
    浏览(35)
  • 嵌入式入门基础知识有哪些?

    嵌入式系统 是指在特定应用领域内为满足特定要求而设计的计算机系统,通常被嵌入到设备中,具有实时性、可靠性、低功耗等特点。嵌入式系统应用广泛,例如:智能家居、智能手表、汽车控制系统、医疗设备等。 在本篇博客中,我们将讨论嵌入式入门基础知识,包括嵌

    2024年02月15日
    浏览(53)
  • C++ 98/03 应该学习哪些知识18

    STL标准模板库 STL是C++标准库中的一部分,是一个强大的模板库,提供了大量常用的数据结构和算法,例如向量(vector)、链表(list)、映射(map)、哈希表(unordered_map)和排序等等。STL使得开发者能够高效地完成许多常见的任务,而且它是经过充分测试和优化的,因此是一

    2023年04月08日
    浏览(49)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包