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

文章目录

  • 前言
  • 一、C++关键字
  • 二、命名空间
    • 2.1引子
    • 2.2命名空间定义
    • 2.3命名空间的使用
  • 三、C++输入和输出
    • 3.1输出
    • 3.2输入
  • 四、缺省参数
    • 4.1全缺省
    • 4.2半缺省
  • 五、函数重载
    • 5.1重载概念
  • 六、引用
    • 6.1定义
    • 6.2引用的使用示例
      • 6.2.1引用作参数
      • 6.2.1引用作返回值
    • 6.3传值、传引用效率比较
    • 6.4常引用
    • 6.5引用和指针的区别
  • 七、内联函数
  • 八、auto关键字
  • 九、基于范围的for循环
    • 9.1范围for的语法
    • 9.2 范围for的使用条件
  • 十、指针空值(nullptr)


前言

本篇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;
}

在这里插入图片描述


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/298049.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

真空引水罐 虹吸抽水机 负压虹吸罐 农业灌溉工作原理动画介绍

​ 1&#xff1a;真空引水罐虹吸抽水机虹吸罐介绍 真空引水罐是一种水泵吸水设备&#xff0c;也被称为真空罐、吸水罐或自动引水装置。它是一个密封的罐体&#xff0c;被串联在泵前的吸水管上&#xff0c;能够使水泵的吸水口从负压吸水变为正压吸水。使用真空引水罐可以节省真…

彻底解决vue-video-player视频铺满div

需求 最近需要接入海康视频摄像头&#xff0c;然后把视频的画面接入到自己的网站系统中。以前对接过rtsp固定IP的显示视频&#xff0c;这次的不一样&#xff0c;没有了固定IP。海康的解决办法是&#xff0c;摄像头通过配置服务器到萤石云平台&#xff0c;然后购买企业版账号和…

解决vue3中watch 监听不到旧值的问题,亲测有效!

问题描述 这个问题是我在公司vue3项目的时候发现的一个问题&#xff0c;watch 在监听对象/数组变量的变化时&#xff0c;发现对象的数据变化时 旧数据 获取到的和新数据是一样的 类似于下面这样 const objref({a:我是原来的值,b:6, })obj.a改变值watch(obj,(nel,old)>{ c…

关于曲率、曲率半径和曲率圆,看这几篇文章就够啦

关于曲率、曲率半径和曲率圆的内容&#xff0c;是考研数学数学一和数学二大纲中明确要求掌握的内容&#xff0c;但这部分内容在很多教材教辅以及练习题中较少涉及。在本文中&#xff0c;荒原之梦考研数学网就为大家整理了曲率、曲率半径和曲率圆方程相关的概念、基础知识以及练…

无心剑七绝《译无止境》

七绝译无止境 人生跌宕几春秋 苦辣酸甜永不休 只待通灵成妙译 神思曼舞醉琼楼 2024年1月6日 平水韵十一尤平韵 无心剑的这首《译无止境》以七言绝句的形式&#xff0c;表达了对翻译事业的热爱和追求。 首句“人生跌宕几春秋”&#xff0c;意味着人生的曲折变化&#xff0c…

2024/1/7周报

文章目录 摘要Abstract文献阅读题目引言贡献相关工作Temporal RecommendationSequential Recommendation 方法Problem FormulationInput EmbeddingSelf-Attention StructureModel Training 实验数据集实验过程实验结果 深度学习Self-attention多头机制堆叠多层 总结 摘要 本周…

静态网页设计——宠物狗狗网(HTML+CSS+JavaScript)

前言 声明&#xff1a;该文章只是做技术分享&#xff0c;若侵权请联系我删除。&#xff01;&#xff01; 感谢大佬的视频&#xff1a; https://www.bilibili.com/video/BV1nk4y1X74M/?vd_source5f425e0074a7f92921f53ab87712357b 使用技术&#xff1a;HTMLCSSJS&#xff08;…

深度学习|4.1 深L层神经网络 4.2 深层网络的正向传播

4.1 深L层神经网络 对于某些问题来说&#xff0c;深层神经网络相对于浅层神经网络解决该问题的效果会较好。所以问题就变成了神经网络层数的设置。 其中 n [ i ] n^{[i]} n[i]表示第i层神经节点的个数&#xff0c; w [ l ] w^{[l]} w[l]代表计算第l层所采用的权重系数&#xff…

【响应式编程-05】Lambda方法引用

一、简要描述 Lambda的方法引用也叫引用方法 方法引用初体验方法引用的底层实现方法引用的语法格式方法引用举例 静态方法引用构造方法引用普通方法引用super和this方法引用数组的方法引用 二、方法引用初体验 为什么出现方法引用&#xff1f; 引用已存在方法&#xff0c;避免重…

数据结构第八弹---队列

队列 1、队列的概念和结构2、队列的实现2.1、头文件包含和结构定义2.2、初始化2.3、销毁2.4、判断是否为空2.5、入队2.6、出队2.7、获取队头数据2.8、获取队尾数据2.9、获取有效数据个数 3、代码汇总总结 1、队列的概念和结构 队列&#xff1a;只允许在一端进行插入数据操作&am…

给定0-1数组,找出连续1最长和次最长的2个子数组的起始位置和结束位置。

题目 给定0-1数组&#xff0c;找出连续1最长和次最长的2个子数组的起始位置和结束位置。 要求&#xff1a; 子数组长度大于等于1。 如果有多个子数组满足条件&#xff0c;按照数组下标由小到大只输出满足条件的前2个数组的起始位置和结束位置&#xff0c; 如果只有1个满足&…

Multisim各版本安装指南

Multisim下载链接 https://pan.baidu.com/s/1En9uUKafhGOqo57V5rY9dA?pwd0531 1.鼠标右击【Multisim 14.3(64bit)】压缩包&#xff08;win11及以上统需先点击“显示更多选项”&#xff09;选择【解压到 Multisim 14.3(64bit)】。 2.打开解压后的文件夹&#xff0c;双击打开【…

深度学习中的知识蒸馏

一.概念 知识蒸馏&#xff08;Knowledge Distillation&#xff09;是一种深度学习中的模型压缩技术&#xff0c;旨在通过从一个教师模型&#xff08;teacher model&#xff09;向一个学生模型&#xff08;student model&#xff09;传递知识来减小模型的规模&#xff0c;同时保…

UAV | 多算法在多场景下的无人机路径规划(Matlab)

近年来&#xff0c;无人机(unmanned aerial vehicle&#xff0c;UAV)由于其灵活度高、机动性强、安全风险系数小、成本低等特点&#xff0c;被广泛应用于搜索巡逻、侦察监视、抢险救灾、物流配送、电力巡检、农业灌溉等军用或民用任务。路径规划是无人机执行任务的关键&#xf…

C# Attribute特性实战(1):Swtich判断优化

文章目录 前言简单Switch问题无参Swtich方法声明Swtich Attribute声明带有Swtich特性方法主方法结果 有参Switch修改代码修改运行过程运行结果 总结 前言 在经过前面两章内容的讲解&#xff0c;我们已经简单了解了如何使用特性和反射。我们这里解决一个简单的案例 C#高级语法 …

Spring依赖注入的魔法:深入DI的实现原理【beans 五】

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 Spring依赖注入的魔法&#xff1a;深入DI的实现原理【beans 五】 前言DI的基本概念基本概念&#xff1a;为什么使用依赖注入&#xff1a; 构造器注入构造器注入的基本概念&#xff1a;示例&#xff1a…

RK3568 学习笔记 : 解决 linux_sdk 编译 python 版本报错问题

前言 最近买了 【正点原子】 的 RK3568 开发板&#xff0c;下载了 开发板的资料&#xff0c;包括 Linux SDK&#xff0c;这个 Linux SDK 占用的空间比较大&#xff0c;扩展了一下 VM 虚拟机 ubuntu 20.04 的硬盘空间&#xff0c;编译才正常通过。 编译 RK3568 Linux SDK 时&am…

二维地图组件开发

很多的业务项目中&#xff0c;都会用到二维地图&#xff0c;二维地图功能简洁&#xff0c;并且很多在很多的业务中采用&#xff0c;维护难度也比三维地图小一些。而开发二维地图的开源库有QGis&#xff0c; Mapwingis等。 采用mapwingis开发了一个二维地图组件&#xff0c;可以…

Jmeter事务控制器聚合报告

Jmeter 事务控制器。 在Jmeter中&#xff0c;默认一个取样器就是一个事务 事务控制器控制其子集取样器&#xff0c;合并为一个事务 添加&#xff1a;逻辑控制器/Logic Controller -> 事务控制器/Transaction Controller TPS: 服务器每秒处理的事务数 在事务控制器下添加多…

Http与Tcp协议的原理以及应用

OSI七层模型和相关协议 七层模型从上到下如下所示&#xff1a; 应用层&#xff1a;负责应用之间的通信&#xff0c;处理请求和响应的具体格式表示层&#xff1a;对于数据格式进行处理会话层&#xff1a;负责建立和断开通信连接&#xff0c;传输层&#xff1a;负责建立端口之间…