由C过渡到C++-入门知识点

Posted Booksort

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了由C过渡到C++-入门知识点相关的知识,希望对你有一定的参考价值。

从C语言过渡到C++,这些知识点应该是比较重要的。

第一个C++程序

#include <iostream>
void print(void)
{
	std::cout << "hello world" << std::endl;
}
int main(void)
{
	using namespace std;
	cout << "hello world" << endl;
	print();
	return 0;
}

对于C语言而言,直接printf("hello world");
但是对于C++而言,需要一个using namespace std;
这个叫使用命名空间(名称空间)。

名称空间特性

名称空间支持是C++的一个特性,方便在大型项目由多个部门共同编写时,解决多个变量名相同的情况。对于不同部门,封装不同的域作为名称空间。可以解决命名冲突的问题

同一项目中,如果在不同文件中有同一个名字的命名空间,在最后会讲这一样的命名空间合并成一个命名空间。

像这种using namespace std;是其std名称空间中的所有组件都能使用,当然仅限域其作用域中。

std::cout是只能使用限定的组件。

还有一种使用方法

#include <iostream>
using std::cout;
using std::endl;
int main(void)
{
	cout << "hello world" << endl;
	return 0;
}

提前在全局域声明。后面函数就能使用了。

::符号为:域作用限定符
在C语言中也存在,但我见的比较少。

#include <stdio.h>
int a = 10;
int main(void)
{
	int a = 20;
	printf("%d\\n", a);
	printf("%d", ::a);//这样可以调用全局变量
	return 0;
}

对于局部变量和全局变量有一模一样的变量,在编译时,会先查找局部域,如果没找到才去查找全局域
::a可以这样调用全局域的变量,因为,空白也就是相当于全局域

对于C++也同样理解,去std这个名称空间的域中查找使用其中的函数或其他组件。

cout

COUT<<"HELLO WORLD";

<<将字符串发送给cout
从C++概念上看,输出是一个流,这个操作就是将字符串插入到流中。

cin

C++的输入形式cin>>num;
理解成,cin从输入流中读取信息放入变量中。

<<>>都是表示信息的流向。

缺省参数

是C++支持的一种特性。从某种角度来看,是备胎,是备用数据

概念:缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,
如果没有指定实参则采用该默认值,
否则使用指定的实参。

就是在调用函数的时候,如果没有传实参,那么函数就会使用在定义或声明的时候提前指定的参数,防止函数出错。

全缺省参数

#include <iostream>
int add(int a , int b = 200,int c=10)
{
	return a + b + c;
}
int main(void)
{
	std::cout << add()<<std::endl;
	std::cout<<add(100,100,500);
	return 0;
}

输出结果:>
在这里插入图片描述

半缺省参数

#include <iostream>
int add(int a , int b = 0,int c=0)
{
	return a + b + c;
}
int main(void)
{
	std::cout << add(1)<<std::endl; 
	std::cout<<add(100,100,500);
	return 0;
}

在这里插入图片描述

但是,如果有参数未指定,那么就一定要传递实参。
同时,对于半缺省参数,

1,未指定的参数一定要从左向右排列,指定的参数从右向左排列,
不能中间既有指定参数又有未指定参数,不能跳跃分布。
2,对于缺省参数,不能在函数声明和定义中同时出现,万一给的参数不一样,会导致编译器出错。
3,缺省参数必须是全局变量或者常量

重载

概念:
是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的
形参列表(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题

也就是在不同的环境下,其函数因为已经提前定义,一样的函数名但可以运行且满足需求。

看例子

#include <iostream>
int add(int a, int b)
{
	return a + b;
}
double add(double a, double b)
{
	return a + b;
}
double add(double a, double b,int c)
{
	return a + b+c;
}
int main(void)
{
	std::cout << add(1, 3)<<std::endl;
	std::cout << add(1.2, 2.5)<<std::endl;
	std::cout<<add(1.2, 1.3, 6);
	return 0;
}

输出结果
在这里插入图片描述

对于函数重载的要求:
形参列表(参数个数 或 类型 或 顺序)必须不同,只有返回类型不同不是重载

提问:为什么C语言不支持重载而C++支持

这一切都与C++的函数命名修饰规则有关

我们都知道无论是C/C++运行起来都需要经历这几个阶段
参考C语言编译过程

  1. 预处理->>头文件展开,宏替换,删除注释,条件编译
  2. 编译->>检查语法问题,语义分析,词法分析,符号汇总(函数,变量)
  3. 汇编->>将汇编翻译成机械码,各个文件都要汇总成符号表
  4. 链接->>所有文件中的符号表合并,链接在一起生成exe文件

大致过程
在这里插入图片描述
C++程序大致也是这个过程。
但是,C++针对函数,与C语言有着明显的不同。
C++要支持重载这个性质,那么对于函数就有自己的函数命名修饰规则

C/C++在编译的时候,将所有的符号汇总,在汇编的时候,要翻译成汇编语言,同时形成符号表。也就是对每个函数都会根据声明形成同一个符号表。

汇编:转成机械码后,
对每行函数调用都会转变成一行机械指令。

	add(1, 3);//call _Z3addii(地址)
	add(1.2, 2.5);//call _Z3adddd(地址)
	add(1.2, 1.3, 6);//call _Z3addddi(地址)

当然,如果是分文件写的,函数声明和定义分开在不同文件。

那就找不到函数的地址,因为,各个文件分别形成符号表,在链接前是不会相互联系。只有在链接时,声明才会去其他文件找定义,才能知道函数的地址。

如果定义声明都在一个文件中,直接就知道函数地址了,不用等到链接时。

再来看看C语言的

add(1, 3);//call <add>

指令直接函数名。
根本不存在重载的情况。

C++的机械码指令还会附带显示参数类型,而C语言只是显示函数名。

C++的函数名修饰规则直接提供了重载的可能。

对于重载的判断

1,在查找符号表的同时,会判断是否有相同函数名的调用。
2,如果有,就会去匹配与传参类型数目匹配的函数
3,如果有匹配就执行,无匹配就报错。
如果出现二义性也会报错

总结
C++会根据参数列表去与函数参数列表匹配,去执行响应的函数。

引用

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

这也是C++的特性之一,与之相对应的是C语言中的指针。
引用:就相当于取了个别名int& b=a;

标识符a代表那个空间的所储存的值,而标识符b也代表a空间存储的值。b就是a,不是形参。

但指针容易出错,而引用不容易出错,且,更好理解。

这是C++实现的

#include <iostream>
void swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}
int main(void)
{
	int a = 10, b = 30;
	std::cout << a << " " << b<<std::endl;
	swap(a, b);
	std::cout << a << " " << b;

}

这是C语言的

#include <stdio.h>
void swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
int main(void)
{
	int a = 10, b = 30;
	printf("%d %d", a, b);
	swap(&a, &b);
	printf("%d %d", a, b);
	return 0;
}

在这里插入图片描述
再来看看这个

#include <iostream>
int main(void)
{
	int a = 10;
	int& b = a;
	int& c = b;
	c = 100;
	return 0;
}

对代码进行调试

1,
在这里插入图片描述

2,
在这里插入图片描述

3,
在这里插入图片描述

4,
在这里插入图片描述

由此可见,当c改变时,a,与,b都一起改变了。
这只能说明一件事
在这里插入图片描述

a/b/c这2个标识符都代表这个空间。
都可以通过这任意一个标识符访问到该空间。只是有不同的名字罢了。

引用:就是为该对象取了另外一个名字,无论是什么名字,真实作用的还是该对象。

注意

  1. 引用在定义时必须初始化(不然无意义)
  2. 一个变量可以有多个引用
  3. 引用一旦引用了一个实体,就不能再引用另一个实体,否则不能运行。
#include <iostream>
int main(void)
{
	int a = 1;
	int m = 10;
	int& b = a;
	int& c = b;
	int& d = m;
	c = d;
	return 0;
}

b/c都是a的别名
d是m的别名

c=d;
并不是让c是d的别名,而是单纯的操作
将d的值赋给c。
也就是将m的值赋给a.

引用一旦定义后,就不会轻易改变,比指针稳定,安全。

常引用

例->>

#include <iostream>
int main(void)
{
	
	int a = 10;//a可读可写
	const int& a1 = a;//a1操作只能都不能写//对权限缩小
	//a1 = 12;//->对与a1的操作权限放大
	a = 12;
	
	const int b = 10;//b的操作只能都不能写
	const int& b2 = b;//b2只能读不能写
	//int& b3 = b2;
	//int& b1 = b;//->对b的权限放大
}

有const,对与引用而言

权限可以缩小,不能放大

	int a=10;
	double d = 1.11;
	//d = a;//隐式类型转换
	//a = d;
	double& tmp = a;
	const double& tmp1 = a;

对于a=d;d=a;
这种叫隐式类型转换
这并不是直接的作用,而是创建了一个要转换类型的临时变量,在赋值。
在这里插入图片描述

引用与函数返回值

#include <iostream>
int add(int a, int b)
{
	return a + b;
}
int main(void)
{
	int ret = add(10, 20);
	return 0;
}

这个int ret = add(10, 20);实际上,其ret接收的是一个临时空间里的值。
在这里插入图片描述

如果,使用引用。

#include <iostream>
int& add(int a, int b)
{
	int c = a + b;
	return c;
}
int main(void)
{
	int ret = add(10, 20);
	return 0;
}

在这里插入图片描述
返回的就是对这个临时空间的引用。
而c变量一旦函数结束,该空间就销毁了,没有了访问权限。
所以只能开辟一个临时空间,用于返回值。
而引用也就是该空间的引用。

但是对于随时会销毁的空间而言,引用并不适合用于返回值。

为什么?
来看看这个

#include <iostream>
int& add(int a, int b)
{
	int c = a + b;
	return c;
}
int main(void)
{
	int& ret = add(10, 20);
	add(20, 50);
	std::cout<<ret;
	return 0;
}

这个ret打印是70
在这里插入图片描述
有点意想不到。
因为,当一个函数结束后,那个空间就会被销毁。而返回的引用却还是引用那块空间。当下一个函数调用时,又会建立栈帧,在main函数下的那块空间。
也就是上一个函数建立栈帧的空间。而返回的临时空间还是原来的地址,那引用的空间不会变,但是其临时空间中的值被改变了。

std::cout<<ret;时,是打印ret所引用的空间的值。虽然越界了,但是编译器却没有检查出来。

对于引用而言,如果所引用的那块空间不稳定,容易被销毁后出现创建。那所引用的值也是不确定的。
对于此返回值并不推荐用引用。

如果是

#include <iostream>
int& add(int a, int b)
{
	static int c = a + b;
	return c;
}
int main(void)
{
	int& ret = add(10, 20);
	std::cout << ret<<std::endl;
	add(20, 50);
	std::cout << ret;

	return 0;
}

在这里插入图片描述
因为静态变量存放在静态区,是不会轻易被销毁的,可以使用引用。

对于指针和引用的区别

引用的底层实现

在汇编实现的时候,引用和指针实现的底层都是一样的,引用是靠指针的底层实现,只不过编译器会对其进行打包。

都是依靠传地址进行操作。

两者在语法上

引用
只是为变量空间取了一个别名,并未开辟新的空间,增加了一个新符号,还是原来的空间。

指针:
指针开辟了4个字节的空间,存储目标空间的首字节的地址。

两者在物理上

两者底层都是一样的,都是使用指针进行操作。

对于两者在效率上,其实都差不多,因为,底层都是靠指针进行操作,只不过引用被打包了一下。

两者的不同之处

  1. 引用在概念(语法)上是定义一个变量的别名,而指针是存储一个变量的地址。
  2. 引用在定义时必须要初始化,而指针没有要求
  3. 引用在初始化引用一个实体后不能再引用其他实体(变量),而指针可以在任何时刻都可以随便改变指向同类型的实体。
  4. 没有NULL引用,但有空指针。
  5. sizeof():对于引用实际上是计算引用类型的字节大小,而对于指针,却是地址所占空间的字节大小。
  6. 引用自增,就是其引用的实体加1,而指针自增则是其指向的地址向后移动一个类型大小。
  7. 有多级指针,但没有多级引用,有多次引用。
  8. 引用不需要解引用,编译器已经处理好了,而指针需要显示解引用。
  9. 引用比指针更安全。

内联函数

在C语言中,为了减少小一点的多次调用的函数的开销,会使用宏函数来减少时间消耗。

例如:

#include <stdio.h>
#define Add(x,y) ((x)+(y))
int main(void)
{
	printf("%d",Add(1, 2)*4);
	return 0;
}

直接替换,还减少了调用函数,为函数开辟栈帧,跳转到函数位置的时间。

但是C语言的宏函数也存在一些不足:

  1. 不支持调试
  2. 没有安全检查
  3. 语法复杂,容易出错。

而C++提供了另一种方法来解决C语言的一些不足,也就是内联函数
效果与宏差不多。
在这里插入图片描述
这张图很清晰的描述了内联函数的过程。
要使用内联函数的特性,必须要

在函数声明前加上关键字inline
在函数定义前加上关键字inline

但是,当使用内联函数时,编译器并不一定会执行。当编译器认为函数过大或者函数使用了递归,就不会处理为内联函数。

注意,分文件函数的声明和定义时,是不可行的,因为内联函数就是直接插入main文件中,
是不会建立栈帧的,即函数是不会有地址,不会形成符号表。所以不要将内联函数的声明和定义分文件。

对下面代码进行汇编查看

#include <iostream>
inline int Add(int a, int b)
{
	return a + b;
}
int main(void)
{
	int c = Add(1, 2);
}

在这里插入图片描述

如图

这就是形成汇编代码时,内联函数和常规函数的区别。

内联函数在汇编时,没有call指令是不会调用Add函数的。
而常规函数是由call指令,会去一个地方调用函数。

这就是内联函数对于常规函数的优势。时间更快

使用内联函数可以减少程序的在调用函数上的时间,但是却会增加可执行文件的大小。可以说是以空间换时间的操作了。

对于C++而言,并不建议使用宏,而是推荐使用const,因为宏不会进行检查。

auto

#include <iostream>
int main(void)
{
	int a = 10;
	auto b = a;
	return 0;
}

声明auto类型的变量会根据赋值的变量的类型自动推导相应的类型。

b 会根据 a 的 int 类型自动推出 b 的类型也是 int 。

我觉得挺方便的。
还有

#include <iostream>
int main(void)
{
	int a = 10;
	auto b = a;
	int& c = a;
	auto d = c;//b是int类型//相当于int d=c;
	auto& e = c;//e是int& //相当于int& e=c;
}

引用并不是一个类型,c 虽然是引用 a ,但其还是 int 类型

范围循环

#include <iostream>
int main(void)
{
	int arr[] = { 1,2,3,4,5 };
	for (auto a : arr)
		std::cout << a << " ";
	std::cout << std::endl;
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
		std::cout << arr[i]<<" ";
	return 0;
}

在这里插入图片描述
可以达到同样的效果。

再来看看这个

#include <iostream>
int main(void)
{
	int arr[] = { 1,2,3,4,5 };
	for (auto a : arr)
		a *= 2;
	for (auto a : arr)
		std::cout << a << " ";
	std::cout << std::endl;
	for (auto& a : arr)
		a *= 2;
	for (auto a : arr)
		std::cout << a << " ";
	std::cout << std::endl;
	
	return 0;
}

打印效果
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
这就是,将数组中每个元素赋值给变量 a,int a=arr[i],再对 a自乘一个2,却不会影响数组中的元素


在这里插入图片描述
这个用的是引用,相当于int& a=arr[i],直接可以作用于数组元素。会改变数组元素

这些都是从C过度到C++的基础知识点,比较零碎,但都需要了解。

谢谢观看
如有问题,烦请大佬指点一二。

以上是关于由C过渡到C++-入门知识点的主要内容,如果未能解决你的问题,请参考以下文章

如何正确地将多个片段添加到片段过渡?

[Go] 通过 17 个简短代码片段,切底弄懂 channel 基础

Unity零基础到入门 ☀️| 基础知识入门篇章,看完就可以做游戏啦! | 寻找C站宝藏

怎么从C语言过渡到C++

怎么从C语言过渡到C++

配置更改后片段丢失过渡动画