从零开始C++精讲:第一篇——C++入门

发布时间:2024年01月06日


前言

本篇C++入门主要是讲C++对C语言不足的一些弥补,把这部分学好,为我们以后C++进阶打好坚实的基础。

一、C++关键字

C++总计有63个关键字,其中C语言的就占了32个。

所谓关键字就是我们用来控制语法的关键标识符,比如类型、循环、判断等等
在这里插入图片描述

二、命名空间

2.1引子

我们先来看一个C里面的常见问题:
现在我想打印rand这个变量的值

#include<stdio.h>
int rand = 0;
int main()
{
	printf("%d",rand);
	return 0;
}

在这里插入图片描述
可以看出,这里的代码是没有问题的,也正常打印了0这个值

但如果我们再加一个头文件#include<stdlib.h>

#include<stdio.h>
#include<stdlib.h>
int rand = 0;
int main()
{
	printf("%d",rand);
	return 0;
}

发现这里居然报错了
在这里插入图片描述
这里的报错原因是因为我们的stdlib这个头文件里面也有rand命名的函数,与我们这里命名的全局变量rand产生了冲突。

这就是C语言的一大缺陷,在我们的项目中,难免会有相同名字的变量、函数等,但是一旦命名相同,C语言就无法编译通过

所以,C++旨在改进这一缺陷,引入了“命名空间”这一概念
具体做法就是在指定的名称前加一个namespace,有点类似我们C语言学习的结构体

2.2命名空间定义

namespace test{//这里的命名空间叫test,你也可以取其他名字
	int rand = 0;
}//与结构体不一样的是,C++的命名空间不用加分号

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

在这里插入图片描述

那命名空间到底是什么东西?
我们大白话说就是,现在我家住在山上,我家里养了几头猪,然后山里也有一些野猪在这里插入图片描述
那我们为了区别到底是野猪还是家猪,那我们就装一个围栏,把我们的家猪给围起来。

这个围栏就是namespace

但是细心的同学会发现,这里还是有一个警告,这个警告其实就是告诉我们%d需要的是int型,但我们给的是指针类型,如果需要打印指针类型就是%p,

如果就是要打印rand的值,那我们就需要用到“围栏的钥匙”,也就是域作用限定符::

int main()
{
	
	printf("%p\n", rand);//%p打印的是地址
	printf("%d", test::rand);//如果要找到家猪rand,需要找到对应家猪的围栏test
	//这里的::就是域作用限定符,相当于围栏的钥匙
	return 0;
}

所以,有了命名空间,我们就可以解决命名冲突的问题,如果不同程序员定义了多个同名变量,那就设多个命名空间,通过域作用限定符来使用不同的同名变量

int main()
{
	
	printf("%d\n", test::rand);//如果要找到家猪,需要找到对应家猪的围栏
	//这里的::就是域作用限定符

	printf("%d\n", test1::rand);

	
	printf("%d\n", test2::rand);

	return 0;
}

在这里插入图片描述

2.3命名空间的使用

命名空间除了定义变量,也可以定义函数、结构体等

namespace test{//命名空间test
	int rand = 0;//可以定义变量

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

	struct LNode {//可以定义结构体
		struct LNode* next;
		int val;
	};
}

命名空间就是建了一个围栏,你要找猪,默认不会去围栏里去找。如果想要找围栏里的猪,请给出“围栏名称和围栏钥匙”(也就是命名空间的名字和作用域限定符::)

比如这里我想调用add函数,但是没有给命名空间和作用域限定符,编译器就会报错
在这里插入图片描述

我们把命名空间加上,代码就可以正确运行了

namespace test{//命名空间test
	int rand = 0;//可以定义变量

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

	struct LNode {//可以定义结构体
		struct LNode* next;
		int val;
	};
}



int main()
{
	int x=test::add(1, 2);
	printf("%d", x);
	return 0;
}

在这里插入图片描述

命名空间的结构体也是类似的,比如
struct test::LNode node;
相当于是struct LNode node,
只不过这里中间的LNode是定义在test里,所以我们把LNode换成test::LNode

namespace test{//命名空间test
	int rand = 0;//可以定义变量

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

	struct LNode {//可以定义结构体
		struct LNode* next;
		int val;
	};
}



int main()
{
	struct  test::LNode node;
	//相当于是struct LNode node,
	//只不过这里中间的LNode是定义在test里,所以我们把LNode换成test::LNode
	return 0;
}

可能会有老铁问:“那如果我命名空间里的变量同名产生冲突怎么办?”

答:那你就在命名空间里再定义一个命名空间呗,每有一层命名空间,就多用一个作用域限定符::

namespace test{//命名空间test
	int rand = 0;//可以定义变量

	namespace test1 {
		int rand = 1;
	}
}

int main()
{
	int x = test::rand;
	int y = test::test1::rand;
	printf("x=%d\n", x);
	printf("y=%d\n", y);
	return 0;
}

在这里插入图片描述

但是这样做的话还有一个问题,你每次想要用自己定义的,在命名空间那个变量/函数/结构体,都要加上命名空间和作用域限定符::,这你不觉得烦吗?

所以我们这里可以用

using namespace 命名空间名称;

这样就默认了到某个命名空间里去用它的变量/函数/结构体

使用了这种,你可以直接访问命名空间的,也可以用::来访问

#include<stdio.h>

namespace test{//命名空间test
	int rand = 0;//可以定义变量
}

using namespace test;
int main()
{
	int x = test::rand;
	int y = rand;
	printf("x=%d\n", x);
	printf("y=%d\n", y);
	return 0;
}

在这里插入图片描述

这就有了我们经常用的

using namespace std;

std是我们c++官方定义的命名空间

ps:在工程项目中不要轻易展开std库,因为很容易你自己写的和库中的冲突。日常自己练习一般无所谓。

三、C++输入和输出

3.1输出

到这里,大家可能会发现,我们前面好像打印输出函数还是我们c语言的那一套,那我们c++有没有自己的呢?有!

#include<iostream>
using namespace std;
int main()
{
	cout << "hello world" ;//“<<”是流插入运算符,cout是std库里的一个
	//c是console控制台的意思,out是出来的意思
	//这里涉及到c++的对象知识,我们先学怎么用,具体的原理后面学习面向对象我会详细介绍
	//你可以理解为"hello world"流向了cout控制台

	//cout很牛逼在于,它可以自动识别类型
	int a = 1;
	float b = 3.14;
	char c = 'x';

	
	cout << a<<"\n";//如果要换行,就在后面跟一个<<"\n",表示\n也流向了cout控制台

	cout << b<<endl;//换行法二:在后面加一个<<endl表示换行,法二是最常见的,法一有点麻烦了
	//endl表示end 这个line,就是结束这一行的意思
	cout << c<<endl;


	//你也可以一次性全部流入,比如
	cout << a << b << c;
	return 0;
}

在这里插入图片描述
ps:我们直接using namespace std其实是风险很大的,因为你一个人直接展开库里面的全部,就会导致别人很可能定义的变量和命名空间里的产生冲突,所以我们这里建议只展开std里面的cout和endl

using  std::cout;
using  std::endl;

另外还有一点,如果你想控制打印出来的精度,比如我想打印一个浮点数,只到小数点后1位,c++是不太方便的,这里你还是用c语言的printf来控制(c语言能用的c++也可以用)

int main()
{
	float x = 3.1415926;
	printf("%.2f", x);
}

在这里插入图片描述

3.2输入

这里的输入也就对应了C语言的scanf
我们c++里面就是cin
c是console控制台的意思,in是进去的意思

需要注意的是,因为我们这里是输入嘛,所以用cin>>,这里的符号和输出也是反过来的

using std::cout;
using std::endl;
using std::cin;
int main()
{
	int a = 0;
	cout <<"请输入a值:";
	cin >> a;
	cout << "a值为"<<a;
	//cout叫流插入
	//cin叫流提取,它同样可以自动识别类型
}

在这里插入图片描述

四、缺省参数

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

4.1全缺省

所谓全缺省,就是你定义的函数,这个函数的参数你都预先赋了值

using namespace std;
void func(int a = 1,int b=2, int c=3) {
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
}
int main() {
	func();//缺省a和b和c,默认a=1,b=2,c=3
	func(10);//缺省b和c,默认b=2,c=3
	func(10,20);//缺省c,默认c=3
	func(10,20,30);
}

在这里插入图片描述

4.2半缺省

这种缺省方式就是有一部分参数预先赋了值

注意:

  1. 半缺省参数必须从右往左依次来给出,不能间隔着给
  2. 缺省参数不能在函数声明和定义中同时出现
  3. 缺省值必须是常量或者全局变量
  4. C语言不支持(编译器不支持)

在这里插入图片描述
如果采用了半缺省,你就不能一个参不传了。
因为半缺省肯定是有参数没有提前定义的,如果你一个参数不传,那那个没有提前定义的参数就没有值给它了,就会报错。

using namespace std;
void func(int a ,int b=2, int c=3) {
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
}
int main() {
	func(10);//10传参给a;缺省b和c,默认b=2,c=3
	func(10,20);//10和20传参给a和b;缺省c,默认c=3
	func(10,20,30);//10,20,30传参给a,b,c
}

在这里插入图片描述

五、函数重载

函数重载有点像我们中文的“一词多义”

5.1重载概念

说白了就是函数名相同,参数类型不一样、个数不一样、参数顺序不一样返回值可以不一样,可以一样

ps:C语言是不支持同名函数的,但是我们c++可以支持,只是有一些限制

using namespace std;
int add(int a, int b) {
	cout << "int add(int a,int b)" << endl;
	return a + b;
}

double add(double a, double b) {
	cout << "double add(double a, double b)" << endl;
	return a + b;;
}

int main()
{
	int x=add(1, 2);
	cout << x << endl;
	double y=add(1.1, 2.2);
	cout << y << endl;
	
	return 0;
}

在这里插入图片描述

小细节:如果你有两个不同的重载,这时你传的参数和这两个重载都不一样,比如
在这里插入图片描述
这里就会有歧义了,因为有两个重载,你到底是把int类型的1转成double还是把double类型的2.2转成int类型呢?所以这里会报错

但是如果你只有一个函数,那你这样传就没有毛病了,编译器会自动给你进行类型转换
(因为现在只有一个函数了,只要把传参过去类型不对的转换一下就行,没有重载函数的歧义)
在这里插入图片描述

六、引用

6.1定义

这是对C语言改进最大的地方,C++里面就没有指针的概念了(当年C++的祖师爷也觉得指针过于复杂)

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

using namespace std;
int main()
{
	int a = 1;
	int& b = a;
	int& c = b;

	cout << a << endl;
	cout << b << endl;
	cout << c << endl;

	cout << &a << endl;
	cout << &b << endl;
	cout << &c << endl;
	return 0;
}

可以看到abc共享一个空间,值也是一样的
在这里插入图片描述

比如现在有a、b、c三个变量
在这里插入图片描述
b是把a的值赋过去,b和a不是一个变量,它们独占不同空间

但是我们这里int& c=a,这里就不一样了,c其实就是a的别名,
c和a其实是一个东西,它们占用同一份空间

如果你这里b++,那么a不会受到任何影响
在这里插入图片描述
但如果你对c–,不好意思,a就是c,c就是a,即a–
在这里插入图片描述
ps:引用必须初始化,如果你int& b,不给b初始化,谁知道这个b是哪个变量的别名,所以c++的引用必须初始化。

c++的别名一旦确定,中途是不能改变对象的(比如a起了别名b,那b就一直是a的别名)

int main()
{
	int a = 0;
	int& b = a;
	int c = 1;
	b = c;//这里是把c值赋给b,还是让b称为c的别名?
	cout << b << endl;//打印1
	cout << &a << endl;//a和b同地址,a和c不同地址
	cout << &b << endl;
	cout << &c << endl;
	//说明b=c是赋值,是把c的值赋给b(b是a的别名,也就是把c的值赋给a)
	//但是b还是a的引用
}

6.2引用的使用示例

6.2.1引用作参数

我们c语言经常写一个交换函数swap

int swap(int* x,int* y){
	int tmp=*x;
	*x=*y;
	*y=tmp;
}

那我们c++就不需要这么麻烦了,我们之前说过,引用就是取别名,引用就是它自己,它自己就是引用。

所以我们c++这里swap函数就很容易写了

void swap(int& x, int& y) {
	int tmp = x;
	x = y;
	y = tmp;
}
int main()
{
	int x = 1;
	int y = 2;
	swap(x, y);
	cout<<"x=" << x << endl;
	cout<<"y=" << y << endl;
	return 0;
}

在这里插入图片描述

6.2.1引用作返回值

如果不是引用作为返回值,比如下面代码
我们很容易知道x的是1

int count()
{
	int n = 0;
	n++;
	return n;
}
int main()
{
	int x = count();
	cout << x << endl;
	return 0;
}

这段代码,我们先是在main函数里创立了ret变量,然后调用Count函数
在这里插入图片描述
创立Count函数栈帧,Count里又会创建变量n
在这里插入图片描述
这里Count函数调用完,栈帧就销毁了,所以我们返回的其实是n的复制,返回值为1。
(因为你栈帧已经销毁了,n也肯定销毁了,你再返回n的位置不就是野指针吗?)
在这里插入图片描述
但如果我们用的是引用返回
我们知道引用是我们某个变量的别名,引用就是那个变量
那你返回引用就是返回那个变量

在这里插入图片描述

可以看到,如果我们用引用做返回值,这里是报了一个警告的
因为你count函数结束,其实栈帧已经销毁了,
如果你还要访问原先函数里的变量,其实是非法访问。

至于这里返回值和前面的一样都是1,只是因为vs这个编译器是1,如果换别的编译器结果就不一定
如果用引用做返回值,返回值是不确定的
因为你用引用做返回值,返回值到底是多少是取决于函数栈帧销毁后,到底有没有把原先的空间清理掉,如果清理掉,那就不一定是原来的值了。我们这里vs编译器函数栈帧销毁后是没有清理原先空间,至于其他编译器就未可知了。

比如下面这个示例,大家就会对我上面说的话有更深的理解

int& count()
{
	int n = 0;
	n++;
	return n;
}
int main()
{
	int& x = count();
	cout << x << endl;
	cout << x << endl;

	count();
	cout << x << endl;

	return 0;
}

在这里插入图片描述
我们这里用引用x来接收,count函数返回的引用,这就意味着x和n是一个位置的
第一次我们打印了1,那是因为count函数栈帧里的东西还没销毁
第二次打印了一个1462811616,这个数就是count栈帧里东西销毁了,这个数可能是其他程序当时正在用那个位置生成的数。

第三次,由于我们又调用了count,导致n那个位置又生成了1,所以我们第三次打印出来是1

再举一个例子

int& add(int a,int b)
{
	int c = a + b;
	return c;
}
int main()
{
	int& x = add(1,2);
	cout << x << endl;
	
	add(3,4);
	cout << x << endl;

	return 0;
}

在这里插入图片描述
这里我们用x来接收add函数的引用
第一次我们x=add(1,2),并且由于编译器原因,函数结束栈帧销毁,原先变量位置并没有清除,所以打印了3

但第二次我们没有对x做任何改变,只是中途调用了add(3,4)
为什么打印x是7?
因为x接收的是add函数返回的c的引用,x和c其实是一个位置的同一变量
x其实就是c,所以第二次c改变了,x也是跟着改变,x=7

上面的3和7都是建立在vs这个编译器在函数栈帧销毁后没有清除原先变量空间的前提下,如果是不同编译器,打印情况应该如下:
在这里插入图片描述

这里为什么第二次调用add,x和c还是同一个位置的同一变量?
因为你再一次创建函数栈帧,函数栈帧还是那个函数栈帧,函数里的变量位置也还是不变的,
如下代码所示:

void func()
{
	int c = 0;
	cout << &c << endl;
}
int main()
{
	func();
	func();
	return 0;
}

在这里插入图片描述
再给大家看一个神奇的东西:

void func1()
{
	int c = 0;
	cout << &c << endl;
}

void func2()
{
	int a = 0;
	cout << &a << endl;
}
int main()
{
	func1();
	func2();
	return 0;
}

在这里插入图片描述
并不是说同一个函数才能用同一个空间,不同函数也是用的一个同一块空间
只不过说不同函数它可能里面变量个数不同,它占用同一块空间的大小不同。

可以发现,我们虽然上面这样写,但是其实这个代码是有逻辑上的危险的
那什么时候可以用引用返回是安全的呢?你要么用静态的变量,或者栈上的,你malloc的。简而言之就是出了作用域,那个变量不会销毁,那你用引用就是安全的。

对于静态变量,我们来看一个例子:

int& add(int a, int b) {
	static int c = a + b;
	return c;
}
int main()
{
	int& x=add(1,2);
	cout << x << endl;
	
	add(3, 4);
	cout << x << endl;
	return 0;
}

在这里插入图片描述
这里x是c的别名,c又是一个局部静态变量局部静态变量只会被初始化一次
第一次调用add(1,2),我们初始化了局部静态变量c为3

第二次调用add(3,4),由于局部静态变量c已经被初始化过了,
所以第二次是不执行static int c = a + b;的

所以我们的x也就是c的别名,还是3

再来看另外一种情况:

int& add(int a, int b) {
	static int c = 0;
	c = a + b;
	return c;
}
int main()
{
	int& x=add(1,2);
	cout << x << endl;
	
	add(3, 4);
	cout << x << endl;
	return 0;
}

在这里插入图片描述
如果这样写,我们局部静态变量只会被初始化一次,但是c=a+b;是每次都要调用的
所以我们第一次打印了3,第二次打印了7。
看似两个情况是相同的代码,其实内在是完全不同的。

6.3传值、传引用效率比较

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直
接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效
率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
在这里插入图片描述
为什么能提高效率,大白话就是,如果你传值返回,如果那个值是个特别大的结构体,那返回消耗的代价肯定也很大。但如果你传引用返回,其实就和返回一个指针差不多,占用空间会小很多的。

6.4常引用

在这里插入图片描述
如果一个变量已经被const修饰了,也就说它已经不能被修改了
如果这时你还对常变量取别名是有问题的。
因为我常变量a本身已经不能修改了,但是你给我取个别名b还能修改,这就产生矛盾了。

所以如果想给常变量取别名,别名也必须是const修饰的,如下图,这样就不会报错了
在这里插入图片描述

另外,虽然说const修饰的变量必须用const修饰的别名

但是,没有const修饰的变量也可以用const修饰的别名
(有点类似于权限不能放大,但是权限可以缩小)
在这里插入图片描述
还有一个需要提醒的点,这个大家应该不说也清楚。
就是不同类型的引用也是不可以互相用的
在这里插入图片描述
比如这里j是doible类型的,你可以把i强转,赋值给j
但是rj是double类型的引用,它只能是double类型变量的别名,你int类型的i就不可以用rj这个别名

进一步的,如果我们在double&前加const,这里又不报错了。
在这里插入图片描述
在这里插入图片描述
这是因为我们在强转的时候,会默认生成一个临时变量,通过把int类型的先变成double类型的临时变量,再把临时变量赋给double类型的变量。

而生成的临时变量又具有常变量的性质,所以我们const double& rj=i是可以的。相当于先产生一个const double类型的临时变量,再把这个临时变量赋给rj。

6.5引用和指针的区别

语法上,我们c++的引用就是原来的变量,它们共享一块空间
但是底层实现其实引用和指针是一样,其实是开辟了额外空间的

int main()
{
	int a = 10;
	int& b = a;//语法上b没有开额外空间,b就是a,a就是b
	//但是底层实现其实开了空间
	return 0;
}

下面是反汇编的部分截图
在这里插入图片描述

所以,引用底层实现就是指针,但是我们语法上默认c++的引用就是和原先变量共享一块空间。

举个例子

int main()
{
	char ch = 'x';
	char& c = ch;
	cout << sizeof(c) << endl;
	return 0;
}

按道理这里c其实底层是个指针,但是我们语法上还是默认引用就是原先变量
所以这里sizeof(c)其实就是sizeof(ch),也就是1
在这里插入图片描述
引用和指针的不同点:

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址。

  2. 引用在定义时必须初始化,指针没有要求

  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
    一个同类型实体

  4. 没有NULL引用,但有NULL指针

  5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
    位平台下占4个字节)

  6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

  7. 有多级指针,但是没有多级引用

  8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理

  9. 引用比指针使用起来相对更安全
    ps:引用不是百分百安全的,比如前面讲的,你对出了某个作用域就销毁的变量进行引用,就会产生问题。

七、内联函数

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

你就简单记住,写函数的时候,在返回值类型前加一个inline,就可以提升效率。其他的和正常写函数没啥区别

inline int add(int x, int y) {
	int z = x + y;
	return z;
}
int main()
{
	int x = add(1, 2);
	cout << x << endl;
	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
ps:内联更适合小函数,如果是那种代码比较多的大函数或者递归的函数,用内联就不合适。就算你大函数加内联,编译器也会默认大函数没有加内联。

八、auto关键字

auto可以自动推倒类型

int main()
{
	int a = 0;
	int b = a;
	auto c = a;//auto可以自动推倒类型,这里把int型的a赋给c,那么就默认c是int型
	
	auto d = &a;//指针可以显示的写,也可以不显示的写
	auto* e = &a;
	
	auto& f = a;//引用必须显示的写
	f++;


	cout << typeid(c).name() << endl;//typeid(变量名).name()可以用于打印一个变量的类型
	cout << typeid(d).name() << endl;//d是int型a的指针,所以打印int *
	cout << typeid(e).name() << endl;
	//指针可以显示的写,也可以不显示的写,所以这里auto d=&a和auto*e=&a,d和e的类型是一样的
	cout << typeid(f).name() << endl;
	//f是a的引用,f和a的类型一样都是int

	return 0;
}

在这里插入图片描述
但是上面的代码,说实话没有体现出auto真正的意义,它真正的作业需要到vector顺序表那块知识才能体现出来。比如下面这段代码:定义对象时,类型较长,用auto比较方便,但是auto不能作参数,也不能做返回值(有些比较新版本支持做返回值,但是强烈不建议做返回值,你返回一个值,都不知道是什么类型,我怎么知道用什么接收?)
auto可以让一个很长的定义变得很短哈哈哈哈,这段代码如果暂时看不懂没关系,后面会详细讲
在这里插入图片描述

另外,auto是不能用来声明数组的,别问为什么,当时写c++这么语言的创始人是这么规定的。

九、基于范围的for循环

9.1范围for的语法

在C++98中如果要遍历一个数组,可以按照以下方式进行:

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

	//对数组的三种遍历方法
	//法一
	for (int i = 0;i < sizeof(arr) / sizeof(arr[0]);i++) {
		arr[i] *= 2;
	}
	
	//法二
	for (int* p = arr;p < arr + sizeof(arr) / sizeof(arr[0]);p++) {
		cout << *p<<" ";
	}

	cout << endl;
	
	//法三
	for (auto e : arr) {//会依次取数组arr中的数据赋给e对象,自动判断结束,自动++循环往后走
		cout << e <<" ";
	}
}

在这里插入图片描述
需要注意的是,法三这个是对赋值后的e进行打印,并没有对原先的数进行更改,比如:

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


	for (auto e : arr) {
		e++;
		cout << e <<" ";
	}
	cout << endl;

	for (auto e : arr) {
		cout << e << " ";
	}
}

在这里插入图片描述
当然了,如果你是用引用来接收数组数据,那么数组中的元素是会跟者变化的

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


	for (auto& e : arr) {
		e++;
		cout << e <<" ";
	}
	cout << endl;

	for (auto e : arr) {
		cout << e << " ";
	}
}

在这里插入图片描述

9.2 范围for的使用条件

  1. for循环迭代的范围必须是确定的
    对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供
    begin和end的方法,begin和end就是for循环迭代的范围。
    注意:以下代码就有问题,因为for的范围不确定
void TestFor(int array[])
{
	for(auto& e : array)
	cout<< e <<endl;
}
  1. 迭代的对象要实现++和==的操作。(关于迭代器这个问题,以后会讲,现在提一下,没办法
    讲清楚,现在大家了解一下就可以了)

十、指针空值(nullptr)


void func(int) {
	cout << "f(int)" << endl;
}

void func(int*) {
	cout << "f(int*)" << endl;
}
int main()
{
	int* ptr = NULL;
	func(0);//调用int那个函数
	func(NULL);//这里依旧是调用了int那个函数
	//NULL实际是个宏,在传统c语言头文件stddef.h中,NULL本质就是0
	//这个是当时写这个库的时候,那个创作者定义的

	func(ptr);//调用int*那个函数
	func(nullptr);//调用int*那个函数
	//我们一般初始化空指针也是用nullptr


	return 0;
}

在这里插入图片描述


文章来源:https://blog.csdn.net/m0_57180439/article/details/135357801
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。