【C++初阶】一、C++入门基础(详细总结)-程序员宅基地

技术标签: C++  c++  

一、什么是C++

C++是基于C语言而产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计。

二、C++关键字(C++98)

C++总计63个关键字,其中C语言占32个关键字
在这里插入图片描述

其中画圈的是C语言的关键字。这里要注意了:false和true并不是C语言的关键字。
所以说:C++兼容C的绝大多数语言特性

三、命名空间

  在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染namespace 关键字的出现就是针对这种问题的。

#include <stdio.h>
#include <stdlib.h>//这个头文件中包含 rand这个库函数

int rand = 0;//定义的rand这个变量,与库函数中的rand函数重名,所以命名冲突了

int main()
{
    
	printf("%d\n",rand);
}
//这时如果进行编译,则会报错
//编译报错:error C2365: “rand”: 重定义;以前的定义是“函数”

// 命名冲突问题
// 1、我们自己定义的变量、函数可能跟库里面重名冲突
// 2、进入公司项目组以后,做的项目通常比较大。多人协作,两个同事写的代码,命名冲突。
// C语言没有办法很好的解决这个问题
// CPP提出一个新语法:命名空间--关键字namespace

3.1命名空间定义

定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字(随自己定义),然后接一对{} 即可,{}中即为命名空间的成员。

注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中

例如:将上方代码进行修正,如下:

#include <stdio.h>
#include <stdlib.h>
//定义了一个叫xnh的命名空间 -- 命名空间定义的是一个:域
namespace xnh
{
    
	int rand = 0;
}
int main()
{
    
	printf("%d\n", rand);//访问的是<stdlib.h>中的rand函数,打印的是以十进制打印的该函数地址
	
	printf("%d\n", xnh::rand);//访问的是xnh这个命名空间中的rand变量,打印显示为0
	return 0;
}

在上述代码中会发现,若想访问命名空间中的变量,则需要借助一个作用符 :: 这个符号叫做 域作用限定符,xnh :: rand 的意思就是,去左边这个叫xnh的域(命名空间)里面找rand这个变量。

若我们想打印全局域中的一个变量,可以如下图:
在这里插入图片描述

请注意:命名空间内的变量只能允许声明和初始化,而不能在其中进行赋值!

namespace xnh
{
    
	int a;//ok
	int b=10;//ok
	//b=20;//no
}
1.命名空间的普通定义
namespace xnh
{
    
	// 1、命名空间中可以定义变量/函数/类型
	int rand = 10;

	int Add(int left, int right)
	{
    
		return left + right;
	}

	struct Node
	{
    
		struct Node* next;
		int val;
	};
}

//使用
int main()
{
    
	xnh::rand = 20;

	struct xnh::Node node;//注意结构体与函数和变量的不同

	xnh::Add(1, 2);

	return 0;
}
2.命名空间可以嵌套定义
//2. 命名空间可以嵌套定义
namespace N1
{
    
	int a;
	int b;
	int Add(int left, int right)
	{
    
		return left + right;
	}

	namespace N2
	{
    
		int c;
		int d;
		int Sub(int left, int right)
		{
    
			return left - right;
		}
	}
}

//使用
int main()
{
    
	N1::a = 1;
	N1::N2::c = 2;
	N1::Add(1, 2);
	N1::N2::Sub(3, 4);
}
3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中

我们在一个工程中,我们可以将函数声明和定义分开来写,如下:

//在List.h头文件中只写声明
 namespace xnh
{
    
	int rand;
	struct ListNode
	{
    
		//...
	};

	void ListInit();
	void ListPushBack(struct ListNode* phead, int x);
}

//在List.cpp源文件中写定义
namespace xnh
{
    
	void ListInit()
	{
    
		// ...
	}

	void ListPushBack(struct ListNode* phead, int x)
	{
    
		//...
	}
}

//在test.cpp源文件中使用
int main()
{
    
	struct xnh::ListNode ln;
	xnh::ListPushBack(NULL, 1);
	
	return 0;
}

虽然将xnh这个命名空间分开写在了List.hList.cpp两个文件中,但最后会合成同一个命名空间中

3.2命名空间的使用

1.加命名空间名称及作用域限定符

在这里插入图片描述

2.使用using namespace 命名空间名称引入

using namespace 命名空间名称;

这句代码的意思就是把整个命名空间展开,这样当我们使用命名空间下的变量、函数等等就不需要加作用域限定符了,用起来方便,但隔离失效了。

日常练习,小程序,这么用可以,项目最好不要这么用。
例如:
在这里插入图片描述

会发生如下的情况:
在这里插入图片描述

这样容易造成命名冲突问题,为了解决这个问题,出现了第三种引入方法。

3.使用using将命名空间中成员引入

第三种方法就是指定展开–把常用的展开,自己在定义的时候避免跟常用重名即可
例如:
在这里插入图片描述

这种方法可以防止命名冲突的问题,因为它只引入了一部分。

四、C++输入与输出

cout :输出
cin :输入

我们初学一门语言,输出Hello world是必不可少的,C++输出方法如下:

#include<iostream>
using namespace std;

int main()
{
    
	cout<<"hello world!"<<endl;
	return 0;
}

对以上程序有几点解释:

首先是iostream头文件,是标准输入/输出文件,可以支持使用cout和cin等函数,分别代替printf和scanf;

其次是using namespace std,就是我们上文讲到的命名空间,std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中

还有就是c++特有的输入输出,cout,cin,都有与之对应的符号,如下:

<< :流插入操作符。与cout配合使用,可以将所有你需要输出的变量或者字符串流入到cout中,让cout负责输出。我们将cout相当于控制台就好理解了
>> :流提取操作符。与cin配合使用,可以将你输入的值流入到某变量中。
就相当于把你在屏幕上输入的数据或字符串通过>>符号流入到所需的某变量中。我们可以将cin相当于键盘

还有就是 endl与"\n"(换行符)等价,都是进行换行的意思。

注意:使用 cout标准输出(控制台)cin标准输入(键盘) 时,必须包含< iostream >头文件以及std标准命名空间。

cout可以自动识别变量的类型

使用示例:

int a = 10;
char arr[] = "abcdef";
double b = 1.11;

float c = 0.0;

cin>>c;
cout<<a<<endl;
cout<<arr<<endl;
cout<<c<<endl;

在这里插入图片描述

五、缺省参数

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

//缺省参数
#include<iostream>

using namespace std;

//这儿的 0 就相当于缺省参数,如果实参什么都没传过来,缺省参数就赋值给a。
void func(int a = 0)
{
    
	cout << a << endl;
}

int main()
{
    
	//调用
	func(10);
	func();  //在c语言中这样写肯定是不行的,但是在c++中有了缺省参数,如果你什么都不传,只要你前面有缺省参数的存在,就能过。
	return 0;
}

在这里插入图片描述

缺省参数分类

1.全缺省参数

全缺省参数就是为函数的所有参数都设置一个默认参数,例如:

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

我们调用此函数时,有以下几种方法:

Func();
Func(1);
Func(1,2);
Func(1,2,3);

传进去的实参必须是从左向右传的,而且必须连续的。比如以下传参方法就是错误的

Func(,10,);//错误,传参必须是从左往右,连续传参
2.半缺省参数

半缺省参数就是将函数的参数部分初始化,例如:

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

注意:
1.半缺省参数必须只能从右向左依次给出,而且必须连续,不能间隔着给。
例如以下方式就是不行的

int Func(int a,int b=10,int c);//错误,不连续
int Func(int a=10,int b=20,int c);//错误,不是从右往左给出

2.缺省参数不能在函数声明和定义中同时出现,推荐在声明中给

//a.h
void Func(int a = 10);

// a.c
void Func(int a = 20)
{
    
	//.....
}

// 注意:如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。

3.缺省值必须是常量或者全局变量
4. C语言不支持(编译器不支持)

六、函数重载

重载的意思是具有多重含义,那么函数重载即是一个函数具有多种功能,也就是对同一个函数名的不同的“解释”。

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

比如:

//参数类型不同
int Add(int a, int b)
{
    
	return a+b;
}
double Add(double a, double b)
{
    
   return a+b;
}

//参数个数不同
int Func(int a);
int Func(int a,int b);

//参数顺序不同 -- 顺序不同指的是,形参类型的顺序不同
int Sub(int a, char b);
int Sub(char b, int a);

注意:函数重载与函数返回值类型无关。

6.1函数重载的原理

为什么C++支持函数重载而C语言却不支持呢?
在C/C++中,一个程序要运行起来,C/C++的源文件都是需要进行预处理,编译,汇编,链接,最后生成可执行程序的。和C的源文件一样,都是源文件先单独编译生成目标文件合到一起链接成可执行程序。

实际我们的项目通常是由多个头文件和多个源文件构成,函数的声明和定义分离会使编译器在编译源文件时暂时找不到函数的地址,那么其查找函数的规则则会在链接时体现。所以将函数的声明和实现放到两个文件中,更方便观察C++和C对于函数名字修饰规则的不同。

由于Windows下vs的修饰规则过于复杂,而Linux下gcc的修饰规则简单易懂,下面我们使用了gcc演示了这个修饰后的名字。

采用C语言编译器编译后结果:
在这里插入图片描述

结论:在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变。

采用C++编译器编译后结果:
在这里插入图片描述

结论:在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中。

通过这里就可以理解C语言无法支持重载了,因为c++会根据函数的参数对函数名进行修饰,只要参数不同,修饰出来的名字就不一样,就支持了重载,而C对函数名却不会修饰,所以没办法支持重载,因为同名函数没办法区分。

函数名不相同,其生成的函数地址也不会相同,所以在调用的时候也不会产生冲突

我们看完 函数重载 的定义后,可能会产生一个疑问,如:

返回值不同,构成重载吗??为什么呢?? 请看下面调用函数:

//两者返回值不同
int f(int a, int b)
{
    
	cout << "f(int a,char b)" << endl;
	return 0;
}

char f(int a, int b)
{
    
	cout << "f(int a,char b)" << endl;
	return 'A';
}

int main()
{
    
	f(1, 1);
	f(2, 2);
	//这里调用会存在二义性,因为调用时不指定返回类型,所以他不知道该调用哪个函数
	return 0;
}

返回值不同,不构成重载原因,并不是函数名修饰规则
真正原因是调用时的二义性,无法区分,
因为调用时不指定返回值类型

七、引用

引用是C++中一个重要的语法,在后期应用非常广泛。相较于指针,它更加方便也更好理解。

定义:

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

通俗来讲,就是为一个变量取一个别名。就如同家人为你取一个小名,两个名字都代表着同一个人。

引用的基本使用:
类型+& +引用变量名(对象名) = 引用实体;

void func() 
{
    
	int a = 10;
	int& b = a;//为a取别名叫b
	b = 20;
	int& c = b;	//为b取别名叫c
	c = 30;
}

运行结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从这里可以看出这里分别给a取了两个别名,并且别名值的改变也会影响变量a,因为别名本身代表的就是a,同时,这三个变量的地址都是同一地址,所以证明引用实体和引用变量共用同一块内存空间。

7.1 引用的特性

1.引用在定义时必须初始化
void func()
{
    
	int a = 10;
	//int& b;  //err,定义引用必须初始化
	int &b = a;//正确处理	
}
2.一个变量可以有多个引用
void func()
{
    
	int a = 10;
	int &b = a;//正确处理
	int &c = a;
	int &d = a;
}
3.引用一旦引用了一个实体,就不能再引用其他实体
int a = 10;
int& b = a;
int c = 20;
b = c;//这里是把c的值赋给b,并不是让b变为c的别名

在这里插入图片描述

7.2 常引用

常引用的定义:被const修饰的引用就是常引用。
常引用会涉及到权限的问题,如:

//权限放大
const int a = 0;
int& ra = a;//Err
//权限缩小
int b = 0;
const int& rb = b;
//权限相同
const int c = 0;
const int& rc = c;

引用const修饰的常变量时,如果引用不加const,那么就造成了权限扩大显然时不允许的。权限不允许扩大但是可以不变和缩小,在用引用做参数时,可以加const修饰防止实参被修改。

double d = 1.11;
int i = d;

double d = 1.11;
const int& i = d;

将浮点型变量d赋值给整型变量i时会发生隐式类型转换,中间会产生一个临时变量,将d的数据截断放入临时变量中再将临时变量赋值给i。类似于不同整型数据进行比较会发生整型提升,其实就是将各自产生的临时变量进行整型提升再进行比较,这也是二者的值不会发生改变的原因。
在这里插入图片描述
这样的临时变量是个右值,具有常属性,所以只有常引用才能引用这样的临时变量。也就使得const Type&的引用类型可以引用任意类型的变量。

7.3 引用的使用场景

1.引用做参数
void Swap(int& rx, int& ry) 
{
       //传引用
	int tmp = rx;
	rx = ry;
	ry = tmp;
}
int main() 
{
    
	int x = 0, y = 1;
	Swap(x, y);
	cout << x << " " << y << endl;
	return 0;
}

但引用做参数时,在一些情况下会产生歧义,例如:

void Swap(int x, int y) 
{
       //传值
    int tmp = x;
    x = y;
    y = tmp;
}
void Swap(int* px, int* py) 
{
    	//传址
	int tmp = *px;
	*px = *py;
	*py = tmp;
}
void Swap(int& rx, int& ry) 
{
    	//传引用
	int tmp = rx;
	rx = ry;
	ry = tmp;
}
int main() 
{
    
	int x = 0, y = 1;
	Swap(&x, &y);//这里调用没问题,是进行传址调用
	cout << x << " " << y << endl;
	Swap(x, y);//err,这里就会产生错误,编译器将不会知道是调用传值还是传引用,所以会产生歧义
	cout << x << " " << y << endl;
	return 0;
}

三者参数列表分别为整型、指针类型和引用类型,虽构成了函数重载,但调用时仍会有歧义。

引用做参数举例:
在这里插入图片描述
如图所示,单链表中因可能修改头指针plist而使用二级指针传参的方法可以选择一级指针的引用替代。phead就是plist的引用,改变phead就是改变plist。

2.引用做返回值

传引用返回:这点比较难理解,先看下面的例子:

// 传引用返回
int& Add(int a, int b)
{
    
	int c = a + b;
	return c;
}
int main()
{
    
	int& ret = Add(1, 2);
	cout << ret << endl;
	return 0;
}

当Add函数栈帧销毁后,函数返回了c的引用,再执行int& ret=Add(1,2)相当于取c的值赋值给ret,而c的内存空间已返还给操作系统,就造成了非法访问。
在这里插入图片描述

mov eax, dword ptr [c]
//将变量c的值放入寄存器eax中
lea eax, [c]
//将变量c的地址放入寄存器eax中

返回值和返回引用的区别就是一个用临时变量存储了c的值返回的是拷贝对象,一个用引用访问了原来c的内存空间读取c的值返回的是返回值对象本身。之所以将地址放到寄存器中带回,是因为引用的底层实现是指针,一会要通过地址访问c的内存空间。
在这里插入图片描述
例如:

int& Add(int a, int b) 
{
    
	int c = a + b;
	return c;
}
int main() 
{
    
	int& ret = Add(1, 2);
	cout << ret << endl;
	Add(10, 20);
	cout << ret << endl;
	return 0;
}

在这里插入图片描述
在这里插入图片描述
两次打印ret一次是3一次是30。从这个例子可以看出,引用将c的地址带了回来ret也初始化成为c的引用,Add栈帧销毁又创建,每次的c变量都在同一块空间ret也引用了这块空间,所以下次调用Add函数时这块空间的值被修改成了30。

但这输出的30是唯一的值吗?答案是错误的。

需要注意的是这个ret的结果可能会是随机值,因为c是一个局部变量,随着栈帧的销毁会一块销毁,当空间被销毁了,这块空间就会归还给操作系统,程序员就没有使用权限了,而为什么是随机值,是因为这块空间可能会被清空置成随机值,当我们在打印ret结果值前面加一个printf时,ret就会变为随机值

例如:
在这里插入图片描述

总结:

出了函数作用域,返回变量不存在了,不能用引用返回 ,因为引用返回的结果是未定义的
出了函数作用域,返回变量存在,才能用引用返回。

传值与传引用效率对比

#include <time.h>
struct A {
     int a[10000]; };
void TestFunc1(A a) {
    }
void TestFunc2(A& a) {
    }
void TestRefAndValue()
{
    
	A a;
	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc1(a);
	size_t end1 = clock();
	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2(a);
	size_t end2 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

在这里插入图片描述
传引用和传指针差不多,每次调用都访问的是同一块空间,而传值每次调用都会开辟一样大的空间,所以传引用的效率比传值高很多,数据越大对性能的提升越大。引用可以作输出型参数或输出型返回值,就是达到形参改变外面的实参的目的。

7.4 引用和指针的区别

在这里插入图片描述
引用与指针的不同点:

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

语法层:
指针和引用是完全不同的概念,指针是开辟空间,存储变量的地址,引用不用开辟空间,仅仅是对变量取别名。

底层汇编时:
从上图汇编代码可以看出,引用是用指针实现的。

八、内联函数

在频繁调用一些小的,简单,仅有几行代码的函数时,我们就会反复的开辟栈空间,然后进行函数压栈,销毁等一系列操作,这样不仅会浪费空间,还降低效率。解决这种问题除了使用C语言的语法宏以外,C++还有新语法叫内联函数。

8.1 概念

inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。
在这里插入图片描述
如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。
在这里插入图片描述
这时就没有调用ADD函数,而是运行到此语句时,将函数进行了展开,这样就提升了程序的效率。

内联函数只在release模式下起作用,若想在Debug模式下查看需要配置项目属性。
在这里插入图片描述

8.2 特性

  1. inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。
  2. 内联函数仅是对编译器的一种建议,编译器自动优化,如果不符上述要求,编译器会放弃优化。
  3. 内联函数不建议声明和定义分离,内联函数不会调用就没有函数地址分离会导致链接失败。

宏的优点是可以增强代码的复用性,提高性能,缺点是无法调试,可维护性差,没有类型检查。C++出台内联函数就是推荐使用内联函数。

九、auto关键字(c++11)

auto是c++11引进的关键字.
C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型

int TestAuto()
{
    
	return 10;
}
int main()
{
    
	int a = 10;
	auto b = a;
	auto c = 'a';
	auto d = TestAuto();

	//typeid(变量).name 查看变量类型
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;

	//auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化
	return 0;
}

在这里插入图片描述

9.1 auto的使用

1.auto与指针和引用结合起来使用

用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&

int main()
{
    
 int x = 10;
 auto a = &x;    //用auto和auto*没有任何区别
 auto* b = &x;   //用auto和auto*没有任何区别
 auto& c = x;    //用auto声明引用类型时则必须加&
 cout << typeid(a).name() << endl;
 cout << typeid(b).name() << endl;
 cout << typeid(c).name() << endl;
 *a = 20;
 *b = 30;
 c = 40;
 return 0; 
 }

在这里插入图片描述

2.在同一行定义多个变量

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

void TestAuto()
{
    
 auto a = 1, b = 2; 
 auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}

9.2 auto不能推导的场景

1.auto做为函数的参数
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)//err
{
    }
2.auto不能直接用来声明数组
void TestAuto()
{
    
 int a[] = {
    1,2,3};
 auto b[] = {
    456};//err
}
  1. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法

  2. auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等进行配合使用。

十、基于范围的for循环(c++11)

10.1 范围for的语法

对于一个有范围的集合仍需说明它的范围,这无疑是多余的,因此C++11引入范围for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
:前是循环变量,后面是循环范围

//C
for (int i = 0; i < sz; i++) 
{
    
    cout << arr[i] << endl;
}

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

注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。

范围 for 循环返回的对象是数组元素值的拷贝,所以若要写入数组元素的话,需要使用引用。

for (int i = 0; i < sz; i++) 
{
    
    arr[i] *= 2;
} 
for (auto& e : arr) 
{
    
    e *= 2;
}

10.2 范围for的使用条件

1.for循环迭代的范围必须是确定的

对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。

void TestFor(int arr[]) 
{
    
	for (auto e : arr) 
	{
    
		cout << e << endl;
	}
}

数组传参本质就是指针,所以不知道数组的具体范围,因此是错误的。

2. 迭代的对象要实现++和==的操作

十一、指针空值nullptr(C++11)

C++98中的指针空值

在良好的C/C++编程习惯中,在声明一个变量的同时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误。比如未初始化的指针,如果一个指针没有合法的指向,我们基本都是按如下方式对其进行初始化:

int* p1 = NULL;
int* p2 = 0;

NULL其实是一个宏,在传统的C头文件(stddef.h)中可以看到如下代码:

#ifndef NULL
#ifdef __cplusplus
#define NULL    0
#else  
#define NULL    ((void *)0)
#endif  
#endif 

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

#include <iostream>
using namespace std;
void Fun(int p)
{
    
	cout << "Fun(int)" << endl;
}
void Fun(int* p)
{
    
	cout << "Fun(int*)" << endl;
}
int main()
{
    
	Fun(0);           //打印结果为 Fun(int)
	Fun(NULL);        //打印结果为 Fun(int)
	Fun((int*)NULL);  //打印结果为 Fun(int*)
	return 0;
}

程序本意本意是想通过Fun(NULL)调用指针版本的Fun(int* p)函数,但是由于NULL被定义为0,Fun(NULL)最终调用的是Fun(int p)函数。

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

C++11中的指针空值

对于C++98中的问题,C++11引入了关键字nullptr

  1. 在使用nullptr表示指针空值时不需要包含头文件,因为nullptr是C++11作为关键字引入的。
  2. 在C++11中,sizeof(nullptr)与sizeof((void*)0)所占的字节数相同,大小都为4
  3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_58124165/article/details/121505854

智能推荐

while循环&CPU占用率高问题深入分析与解决方案_main函数使用while(1)循环cpu占用99-程序员宅基地

文章浏览阅读3.8k次,点赞9次,收藏28次。直接上一个工作中碰到的问题,另外一个系统开启多线程调用我这边的接口,然后我这边会开启多线程批量查询第三方接口并且返回给调用方。使用的是两三年前别人遗留下来的方法,放到线上后发现确实是可以正常取到结果,但是一旦调用,CPU占用就直接100%(部署环境是win server服务器)。因此查看了下相关的老代码并使用JProfiler查看发现是在某个while循环的时候有问题。具体项目代码就不贴了,类似于下面这段代码。​​​​​​while(flag) {//your code;}这里的flag._main函数使用while(1)循环cpu占用99

【无标题】jetbrains idea shift f6不生效_idea shift +f6快捷键不生效-程序员宅基地

文章浏览阅读347次。idea shift f6 快捷键无效_idea shift +f6快捷键不生效

node.js学习笔记之Node中的核心模块_node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是-程序员宅基地

文章浏览阅读135次。Ecmacript 中没有DOM 和 BOM核心模块Node为JavaScript提供了很多服务器级别,这些API绝大多数都被包装到了一个具名和核心模块中了,例如文件操作的 fs 核心模块 ,http服务构建的http 模块 path 路径操作模块 os 操作系统信息模块// 用来获取机器信息的var os = require('os')// 用来操作路径的var path = require('path')// 获取当前机器的 CPU 信息console.log(os.cpus._node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是

数学建模【SPSS 下载-安装、方差分析与回归分析的SPSS实现(软件概述、方差分析、回归分析)】_化工数学模型数据回归软件-程序员宅基地

文章浏览阅读10w+次,点赞435次,收藏3.4k次。SPSS 22 下载安装过程7.6 方差分析与回归分析的SPSS实现7.6.1 SPSS软件概述1 SPSS版本与安装2 SPSS界面3 SPSS特点4 SPSS数据7.6.2 SPSS与方差分析1 单因素方差分析2 双因素方差分析7.6.3 SPSS与回归分析SPSS回归分析过程牙膏价格问题的回归分析_化工数学模型数据回归软件

利用hutool实现邮件发送功能_hutool发送邮件-程序员宅基地

文章浏览阅读7.5k次。如何利用hutool工具包实现邮件发送功能呢?1、首先引入hutool依赖<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.19</version></dependency>2、编写邮件发送工具类package com.pc.c..._hutool发送邮件

docker安装elasticsearch,elasticsearch-head,kibana,ik分词器_docker安装kibana连接elasticsearch并且elasticsearch有密码-程序员宅基地

文章浏览阅读867次,点赞2次,收藏2次。docker安装elasticsearch,elasticsearch-head,kibana,ik分词器安装方式基本有两种,一种是pull的方式,一种是Dockerfile的方式,由于pull的方式pull下来后还需配置许多东西且不便于复用,个人比较喜欢使用Dockerfile的方式所有docker支持的镜像基本都在https://hub.docker.com/docker的官网上能找到合..._docker安装kibana连接elasticsearch并且elasticsearch有密码

随便推点

Python 攻克移动开发失败!_beeware-程序员宅基地

文章浏览阅读1.3w次,点赞57次,收藏92次。整理 | 郑丽媛出品 | CSDN(ID:CSDNnews)近年来,随着机器学习的兴起,有一门编程语言逐渐变得火热——Python。得益于其针对机器学习提供了大量开源框架和第三方模块,内置..._beeware

Swift4.0_Timer 的基本使用_swift timer 暂停-程序员宅基地

文章浏览阅读7.9k次。//// ViewController.swift// Day_10_Timer//// Created by dongqiangfei on 2018/10/15.// Copyright 2018年 飞飞. All rights reserved.//import UIKitclass ViewController: UIViewController { ..._swift timer 暂停

元素三大等待-程序员宅基地

文章浏览阅读986次,点赞2次,收藏2次。1.硬性等待让当前线程暂停执行,应用场景:代码执行速度太快了,但是UI元素没有立马加载出来,造成两者不同步,这时候就可以让代码等待一下,再去执行找元素的动作线程休眠,强制等待 Thread.sleep(long mills)package com.example.demo;import org.junit.jupiter.api.Test;import org.openqa.selenium.By;import org.openqa.selenium.firefox.Firefox.._元素三大等待

Java软件工程师职位分析_java岗位分析-程序员宅基地

文章浏览阅读3k次,点赞4次,收藏14次。Java软件工程师职位分析_java岗位分析

Java:Unreachable code的解决方法_java unreachable code-程序员宅基地

文章浏览阅读2k次。Java:Unreachable code的解决方法_java unreachable code

标签data-*自定义属性值和根据data属性值查找对应标签_如何根据data-*属性获取对应的标签对象-程序员宅基地

文章浏览阅读1w次。1、html中设置标签data-*的值 标题 11111 222222、点击获取当前标签的data-url的值$('dd').on('click', function() { var urlVal = $(this).data('ur_如何根据data-*属性获取对应的标签对象

推荐文章

热门文章

相关标签