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

文章目录


前言

本篇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_832">八、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;
}

在这里插入图片描述



http://www.niftyadmin.cn/n/5308059.html

相关文章

AI赋能游戏开发,如何更好地处理随之而来的海量数据,更好地利用开发游戏?

人工智能&#xff08;AI&#xff09;正在改变我们所知的游戏行业。它为3A工作室、独立开发者和业余爱好者提供了工具&#xff0c;让他们能够更轻松地创建以前需要大量时间和资源的项目。尤其是&#xff0c;虚幻引擎的AI工具已经取得了显著的进步。 虚幻引擎AI拥有专门用于游戏…

【hive】报错累积

6.1 创建新表 错误1&#xff1a;FAILED: SemanticException [Error 10006]: Line 1:63 Partition not found "20210919" 场景&#xff1a;在创建例行表时&#xff0c;报错。这种情况是先创建了多级分区表&#xff08;date&#xff0c;product&#xff09;&#xff0c…

Spring Cloud Config相关问题及答案(2024)

1、什么是 Spring Cloud Config&#xff0c;它解决了哪些问题&#xff1f; Spring Cloud Config 是一个为微服务架构提供集中化外部配置支持的项目。它是构建在 Spring Cloud 生态系统之上&#xff0c;利用 Spring Boot 的开发便利性&#xff0c;简化了分布式系统中的配置管理…

小红书 X WSDM 2024「对话式多文档问答挑战赛」火热开赛!

基于大语言模型&#xff08;LLM&#xff09;的对话问答机器人&#xff0c;已经成为当前人工智能领域学术界和工业界共同关注的的热门研究方向之一。在对话过程中&#xff0c;为大模型引入搜索结果&#xff0c;进行检索增强的生成&#xff08;Retrieval Augmented Generation&am…

Java HashMap 面试题(一)

HashMap 面试题&#xff08;一&#xff09; 文章目录 HashMap 面试题&#xff08;一&#xff09;3.3 面试题-说一下HashMap的实现原理&#xff1f;面试题-HashMap的put方法的具体流程hashMap常见属性源码分析 3.3 面试题-说一下HashMap的实现原理&#xff1f; HashMap的数据结…

使用Go语言的HTTP客户端进行并发请求

Go语言是一种高性能、简洁的编程语言&#xff0c;它非常适合用于构建并发密集型的网络应用。在Go中&#xff0c;标准库提供了强大的HTTP客户端和服务器功能&#xff0c;使得并发HTTP请求变得简单而高效。 首先&#xff0c;让我们了解为什么需要并发HTTP请求。在许多应用场景中…

Object.defineProperty(js的问题)

定义 Object.defineProperty(obj, prop, descriptor)/* obj&#xff1a;需要定义属性的对象 prop&#xff1a;需要定义的属性 descriptor&#xff1a;属性的描述描述符 返回值&#xff1a;返回此对象 */ var obj {}// 数据描述符 var descriptor {// 能否delete删除&#x…

【100条linux常用命令】

ls - 列出当前目录的文件和子目录cd - 切换工作目录pwd - 显示当前工作目录路径touch - 创建新文件mkdir - 创建新目录rm - 删除文件或目录rmdir - 删除空目录cp - 复制文件或目录mv - 移动文件或目录cat - 查看文件内容less - 分页查看文件内容head - 显示文件头部内容tail - …