<C++>C++入门

news2024/12/24 9:44:18

目录

前言

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

二、命名空间

1. 命名空间定义 

2. 命名空间的使用 

3. 命名空间的使用有三种方式:

三、C++输入&输出

四、缺省参数

1. 缺省参数概念

2. 缺省参数的分类

3. 缺省参数的应用

五、函数重载

六、引用

1. 引用的概念

2. 用法

3. 引用的特性

4. 引用的使用场景

5. 引用和指针的区别

6. 常引用

七、内联函数

1. 内联函数的概念

2. 来源

3. 特性

八、auto关键字

1. 简介

2. auto的使用

3. auto不能推导的场景

九、基于范围的for循环(C++11) 

1. 范围for的语法

2. 范围for的使用条件

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

1. C++98中的指针空值

总结


前言

        伴随着数据结构与C的学习,我们现在来学习C++语言

        C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机, 20世纪80年代, 计算机界提出了OOP(object oriented programming:面向对象)思想,支持面向对象的程序设计语言应运而生。

        1982年,Bjarne Stroustrup博士在C语言的基础上引入并扩充了面向对象的概念,发明了一种新的程序语言。为了表达该语言与C语言的渊源关系,命名为C++。因此:C++是基于C语言而产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计。

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

C++总计63个关键字,C语言32个关键字

asmdoifreturntry

continue

autodoubleinlineshorttypedeffor
booldynamic_castintsignedtypeidpublic
breakelselongsizeoftypenamethrow
caseenummutablestaticunionwchr_t
catchexplicitnamespacestatic_castunsigneddefault
charexportnewstructusingfriend
classenternoperatorswitchvirtualregister
constfalseprivatetemplatevoidtrue
const_castfloatprotectedthisvolatilewhile
deletegotoreinterpret_cast

二、命名空间

        没有C++使用c语言写项目时,可能会遇到两人代码写完后合并时发生命名冲突,这不得不使其中一个人放弃该命名,修改为其他名称,但是如果代码量很长,例如1万行,那么手动修改就太过麻烦。所以需要一个特殊的功能,来避免这个问题。

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

c语言命名冲突的几种情况

1.跟关键字冲突

2.跟库函数名冲突

3.互相之间冲突(同事之间) 

如果冲突,要改的地方太多,那么就太麻烦了,令人心烦,所以c++加入了命名空间 

1. 命名空间定义 

        定义命名空间,使用namespace关键字,后面跟命名空间的名字,然后接一对{}即可,在{}中书写命名空间的成员,成员可以是变量,也可以是函数

        嵌套定义,嵌套调用,例如下面的N2命名空间调用c,N2::N3::c

//1. 普通的命名空间
namespace N1 // N1为命名空间的名称
{
	// 命名空间中的内容,既可以定义变量,也可以定义函数
	int a;
	int Add(int left, int right)
	{
		return left + right;
	}
}
//2. 命名空间可以嵌套
namespace N2
{
	int a;
	int b;
	int Add(int left, int right)
	{
		return left + right;
	}

	namespace N3
	{
		int c;
		int d;
		int Sub(int left, int right)
		{
			return left - right;
		}
	}
}
//3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
namespace N1
{
	int Mul(int left, int right)
	{
		return left * right;
	}
}

2. 命名空间的使用 

        注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中只有展开命名空间,编译器才会到此命名空间搜索变量名,如果不展开该命名空间,那么即使整个程序中的某个变量名只定义在该命名空间中,编译器也不会自动进里面寻找。

        补充在程序中,变量的使用优先级局部 > 全局 > 展开了命名空间域 or 指定访问命名空间域 当局部域与全局域冲突,优先局部域,如果想访问全局变量,可以使用::(域作用限定符)

//全局域
int a = 0;

//命名空间域
namespace  kool
{
	int a = 1;
}

int main()
{
	//局部域
	int a = 2;

	printf("%d\n", a);
	printf("%d\n", ::a);//加上限定符,默认是全局的
	printf("%d\n", kool::a);

	return 0;
}

打印:

2
1
0

例如:

namespace N
{
 int a = 10;
 int b = 20;
 int Add(int left, int right)
 {
 return left + right;
 }
 int Sub(int left, int right)
 {
 return left - right;
 }
}
int main()
{
 printf("%d\n", a); // 该语句编译出错,无法识别a
 return 0;
}

3. 命名空间的使用有三种方式:

  • 加命名空间名称及作用域限定符
int main()
{
     printf("%d\n", N::a);
     return 0; 
}
  • 使用using将命名空间中成员引入 
using N::b;
int main()
{
     printf("%d\n", N::a);
     printf("%d\n", b);
     return 0; 
}
  •  使用using namespace命名空间名称引入
using namespce N;
int main()
{
     printf("%d\n", N::a);
     printf("%d\n", b);
     Add(10, 20);
     return 0; 
}

       

      使用using namespace std会展开std内所有变量名和函数名,但是我们难免会不了解其中有哪些名称被占用,当写完程序后,发现命名冲突,也是不行的,所以,建议项目里面不要去展开,建议自己练习时使用,项目中建议指定访问(单独using std::   ),不要轻易展开命名空间,因为展开会有风险,可能自己定义的变量名与库冲突,所以我们可以单独展开常用的,比如endl、cout等等  using std::endl;

        命名空间的展开,指编译器是会到此命名空间搜索,如果不展开,编译器不会自动进里面找。

三、C++输入&输出

输出cout,输入cin

例如:我们使用cout,来输出 Hello world!!!

#include<iostream>
using namespace std;
int main()
{
	cout << "Hello world!!!" << endl;
	return 0;
}

 说明:

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

        注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下(定义在其中)namespace std封装了起来,为了和老的C语言库区分,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此推荐使用 <iostream>+std的方式。

        2. 使用C++输入输出更方便,不需增加数据格式控制,比如:整形--%d,字符--%c  

输出时cout自动识别类型,但是如果规定输出几位小数时,cout就不是很方便了,因为C++兼容C,所以我们可以使用printf函数来输出,两者结合使效率最大 

        printf与cout的效率:在C++中,如果有大量输入输出,那么printf  scanf效率会比cout cin效率高一点 ,因为C++的io流要兼容C,io都有缓冲区的,printf和cout缓冲区不同,在C++cout时会同步,它会去判断c语言的缓冲区有没有数据,有数据那就把数据刷出去,再进行C++缓冲区输出,所以效率会低一点

例如:
#include <iostream>
using namespace std;
int main()
{
	int a;
	double b;
	char c;
    //   >> 流提取运算符
	cin >> a;
	cin >> b >> c;


    //自动识别类型
	cout << a << endl;
	cout << b << " " << c << endl;
	return 0;
}

四、缺省参数

1. 缺省参数概念

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

void TestFunc(int a = 0)
{
	cout << a << endl;
}
int main()
{
	TestFunc(); // 没有传参时,使用参数的默认值
	TestFunc(10); // 传参时,使用指定的实参
}

2. 缺省参数的分类

  • 全缺省参数(即定义时全赋上初值)
void TestFunc(int a = 10, int b = 20, int c = 30)
{
 cout<<"a = "<<a<<endl;
 cout<<"b = "<<b<<endl;
 cout<<"c = "<<c<<endl;
}

举例:

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

	return 0;
}

 打印:

  • 半缺省参数(定义时有一个以上没有赋初值,就必须在传参时至少传一个参数)
void TestFunc(int a, int b = 10, int c = 20)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;
}

举例: 

 //半缺省 -- 从右往左缺省
void Func(int a, int b = 20, int c = 30)
{
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << "c = " << c << endl << endl;
}

int main()
{
    //Func();
    Func(1);
    Func(1,2);
    Func(1,2,3);

    return 0;
}

打印: 

 注意:

  • 半缺省参数必须从右往左依次来给出,不能间隔着给
  • 缺省参数不能再函数声明和定义中同时出现

//a.h
void TestFunc(int a = 10);
// a.c
void TestFunc(int a = 20)
{}
// 注意:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那
//个缺省值。
  • 缺省值必须是常量或者全局变量
  • C语言(编译器)不支持该操作
  • 函数传参时,参数从左往右传 

3. 缺省参数的应用

        例如栈的初始化,我们默认初始开辟4个int大小空间,可是当我们知道要提前开100个空间,那么让栈自己进行扩容太慢了,效率低下,所以我们可以借用缺省参数这一特点,int defaultCapacity = 4,当提前知道扩多少空间时,传参数,不知道时,不传参数。

struct Stack
{
	int* a;
	int top;
	int capacity;
};

void StackInit(struct Stack* pst, int defaultCapacity = 4)
{
	pst->a = (int*)malloc(sizeof(int)* defaultCapacity);
	if (pst->a == NULL)
	{
		perror("malloc fail");
		return;
	}
	pst->top = 0;
	pst->capacity = defaultCapacity;
}

int main()
{
    struct Stack st1;
    struct Stack st2;
    //插入100个数据
    StackInit(&st1,100);

    //不知道插入多少数据
    sruct Stack st2;
    StackInit(&st2);
    return 0;
}

五、函数重载

1 函数重载概念

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

int Add(int left, int right)
{
	return left + right;
}
double Add(double left, double right)
{
	return left + right;
}
long Add(long left, long right)
{
	return left + right;
}
int main()
{
	Add(10, 20);
	Add(10.0, 20.0);
	Add(10L, 20L);

	return 0;
}

六、引用

1. 引用的概念

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

2. 用法

        类型& 引用变量名(对象名)=  引用实体

3. 引用的特性

  1. 引用在定义时必须初始化
  2. 一个变量可以有多个引用,相当于一个空间多个名字
  3. 引用一旦引用一个实体,就不能引用其他实体

#include<iostream>
using namespace std;

#include<assert.h>

int main()
{
	// 一个变量可以有多个引用
	int a = 0;
	int& b = a;
	int& c = b;


	// 引用在定义时必须初始化
	int& b;
    //这时编译错误的写法


	// 引用一旦引用一个实体,再不能引用其他实体
	int x = 10;
	c = x; 
    // x的值赋给c,c依旧是a/b对象别名

	return 0;
}

4. 引用的使用场景

4.1.做参数

作用:

  1. 输出型参数(函数内部改变,影响外部)
  2. 提高效率——大对象/深度拷贝对象,因为如果实参所占空间很大,形参再开辟同样的空间,会浪费内存空间且效率低下,使用引用,我们不会造成任何的空间浪费,当然也可以使用指针,但相较于引用,引用书写简洁
void Swap(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}

 从这里我们就会认识到c与c++的区别,在c中,我们要写交换函数,形参是指针int* left,而在c++中,我们有了另一种选择——引用

4.2 做返回值

作用:

  1. 修改返回值+获取返回值,但不是所有类型都适合引用返回,因为函数结束后,栈帧销毁,栈帧的内存空间使用权归还操作系统,根据不同的编译器,对该空间的回收操作不同,有些编译器可能会初始化该内存空间,即使有引用返回,但是空间数据改变,就会产生随机值
  2. 提高效率,但很危险,所有返回数值的函数都会开辟临时变量暂存数据(不一定是寄存器),即便是全局变量、静态区变量都会生成临时变量,再在主函数调用处返回值,然后销毁,如果函数的返回值所占内存空间过大,那么效率就不是很高,这时我们用引用返回,就不会再开辟临时变量,提高效率,但是根据不同的编译器,对栈帧摧毁后的操作不同,可能产生随机值,所以用引用做返回值是很危险的。 
int& Add(int a, int b)
{
 int c = a + b;
 return c;
}
int main()
{
 int& ret = Add(1, 2);
 Add(3, 4);
 cout << "Add(1, 2) is :"<< ret <<endl;
 return 0;
}

总结:

        1. 基本任何场景都可以使用引用传参

        2. 引用做返回值是比较危险的,适用范围小。

        最适合的是返回的变量不是开辟再栈帧中的情况,例如静态区、堆区等等,这样一来,在栈帧销毁后,该空间不会归还操作系统,操作系统不会改变该空间的值,那就没有危险了。

        如果要返回的变量在栈帧内,那就有危险,该编译器是否会清理栈帧,该栈帧是否又被覆盖过,都将是危险因素

// 错误样例:不能用引用
int& Count(int x)
{
	int n = x;
	n++;
	// ...
	return n;
}

 //正确样例:可以用引用
int& Count(int x)
{
	static int n = x;
	n++;
	// ...
	return n;
}

 5. 引用和指针的区别

        在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。

int main()
{
	int a = 10;
	int& ra = a;

	cout << " &a = " << &a << endl;
	cout << "&ra = " << &ra << endl;
	return 0;
}

 但是在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

例如:

int main()
{
	int a = 10;

	int& ra = a;
	ra = 20;

	return 0;
}

语法层面:引用不开空间,是对a取别名

int main()
{
	int a = 10;

	int* pa = &a;
    *pa = 20;

	return 0;
}

语法层面:指针开空间,储存a的地址

         lea是取地址,从底层汇编指令的角度看引用是类似指针的方式实现的,所以两者本质相同,但是用法不同,引用更方便、更安全,因为不和指针一样有NULL情况从而导致错误,引用没有空引用。

引用和指针的不同点:

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

6. 常引用

规定:引用过程中,权限不能放大,只能平移或缩小

int main()
{
	// 不可以
	// 引用过程中,权限不能放大
	const int a = 0;
	int& b = a;//错误

	// 可以,c拷贝给d,没有放大权限,因为d的改变不影响c
	const int c = 0;
	int d = c;

	// 不可以
	// 引用过程中,权限可以平移或者缩小
	int x = 0;
	int& y = x;//平移
	const int& z = x;//缩小
	++x;//虽然z没有权限改变x,但是x本身是有权限修改的
	++z;//错误,z没有权限

	//类型转换
	const int& m = 10;

	double dd = 1.11;
	int ii = dd;

	const int& rii = dd;

	return 0;
}

注意:

        发生类型转换时 ,会产生临时变量(因为4字节不能改变为8字节数,只能开辟临时变量进行提升),它具有常性,相当于被const修饰,又因为引用权限不能放大,只能平移或缩小,所以这时定义引用,要加const

        函数返回只要不返回引用,那就有临时变量,临时变量具有常性,不用const修饰的引用不能接收。

例如:

int func1()
{
	static int x = 0;
	return x;
}

int& func2()
{
	static int x = 0;
	return x;
}

int main()
{
	int& ret1 = func1();  // 权限放大,编译错误
	const int& ret1 = func1(); // 权限平移
	int ret1 = func1();  // 拷贝

	int& ret2 = func2();		// 权限平移
	const int& rret2 = func2();  // 权限缩小

	return 0;
}

七、内联函数

1. 内联函数的概念

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

2. 来源

int Add(int x, int y)
{
	return (x + y) * 10;
}

int main()
{
	for (int i = 0; i < 10000; i++)
	{
		cout << Add(i, i + 1) << endl;
	}
	return 0;
}

        当一个函数代码量短且被频繁调用,我们就要考虑效率问题,因为不断地开辟栈帧并不高效,栈帧开辟需要开辟空间,寄存器移动等操作,在C语言中,我们可以使用宏函数,例如Add函数的宏定义:#define Add(x,y) ((x) + (y))  需要注意的是,在宏函数的定义中,我们一定要注意括号,宏起到的是替换的作用,如果宏函数参与算术运算,那么优先级就会出现问题,所以括号必不可少。

        慢慢的,问题就出现了,虽然宏函数不需要建立栈帧,提高了调用效率,但是他的缺点显著,可读性差、不可调试、容易出错、复杂......这时我们就需求一个新的概念来解决这个问题,内联函数就出现了,它完美弥补了宏函数的不足。

3. 特性

        内联函数不需要建立栈帧,不复杂,可以调试,可读性高,不容易出错,调用效率高

        内联函数使用短小且被频繁调用的函数(大致在10行以内),要设定内联函数只需要在函数前加上inline即可,但是并不是所有函数加上inline都会成为内联函数,究竟是否会成为内联函数,选择权在编译器上,我们加上inline对于编译器来说仅仅是一个建议,编译器认为,较长的函数不可以成为内联函数,递归函数不可成为内联函数。

现假设函数Func编译后有50行指令,在一个项目中Func被调用了10000次,提问:

  1. 如果Func不是内联函数,那么Func占了约多少行指令?
  2. 如果Func是内联函数,那么Func占了约多少行指令?

计算:

        普通函数的调用是通过地址调用的,所以10000次调用函数,相当于调用了10000次函数地址,所以Func占了指令总行数约为10000+50 = 10050

        内联函数不建立函数栈帧,直接展开在被调用指令处,被调用所10000次,所以Func占了指令总行数约为10000*50 = 500000

        可以看出,内联函数如果内部代码量过大,那么代码所占内存会变得非常大,安装包就会变大,并不是所有函数都适合内联函数。如果不频繁调用,建立栈帧也无所谓。

1. inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。

2. inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联

3. inline不要声明和定义分离分离会导致链接错误因为inline被展开,就没有函数地址了链接就会找不到。因为内联函数没有地址,不需要被call,也就不需要声明,直接在.h文件定义即可,因为.h文件会展开在程序

        注意:debug版本下,内联函数inline不会起作用,这样就无法调试了。

八、auto关键字

1. 简介

        在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有人去使用它,大家可思考下为什么?

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

 2. auto的使用

2.1 定义变量

// C++11
#include<map>
#include<string>
#include<vector>

int main()
{
	int a = 0;
	int b = a;
	auto c = a; // 根据右边的表达式自动推导c的类型
	auto d = 1 + 1.11; // 根据右边的表达式自动推导d的类型
	cout << typeid(c).name() << endl;  //int
	cout << typeid(d).name() << endl;  //double
    //以上的使用不能体现出auto的优势,我们来看下面代码

	vector<int> v;

	// 类型很长
	//vector<int>::iterator it = v.begin();
	// 等价于
	auto it = v.begin();

	std::map<std::string, std::string> dict;
	//std::map<std::string, std::string>::iterator dit = dict.begin();
	// 等价于
	auto dit = dict.begin();

	return 0;
}

 所以,基本只有当变量类型名很长时,我们才使用auto定义变量,这样十分方便。

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

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

 2.3 在同一行定义多个变量

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

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

3. auto不能推导的场景

3.1 auto不能作为函数的参数

// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}

3.2 auto不能直接用来声明数组 

void TestAuto()
{
     int a[] = {1,2,3};
     auto b[] = {4,5,6};
}

九、基于范围的for循环(C++11) 

1. 范围for的语法

在以往C或C++98中,如果要遍历一个数组,可以用sizeof(数.组名) / sizeof(数组名[0])来控制循环次数

void TestFor()
{
	int array[] = { 1, 2, 3, 4, 5 };
    //下标
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
		array[i] *= 2;
    
    //指针
	for (int* p = array; p < array + sizeof(array) / sizeof(array[0]); ++p)
		cout << *p << endl;
}

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号分为两部分第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。  

for (auto e : arr)  以此取数组中的数据赋值给e,自动迭代,自动判断结束,其中的e可以自行改名,auto也可以自行书写类型,但最好使用auto

如果要修改数据,那么要使用引用,不用引用直接 e *= 2; 是没用的,并且不能用指针,因为数据类型不匹配

void TestFor()
{
	int array[] = { 1, 2, 3, 4, 5 };
	for (auto& e : array)
		e *= 2;

	for (auto e : array)
		cout << e << " ";

	return 0;
}

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

2. 范围for的使用条件

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

        基本上要求必须是数组;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围

例如:array是指针不是数组,for的范围不确定,所以该循环编译错误

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

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

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

1. C++98中的指针空值

在良好的 C/C++ 编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化:
void TestPtr()
{
	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*)的常量不论采取何种定义,在

使用空值的指针时,都不可避免的会遇到一些麻烦,比如:

//可以不用参数接收,不加形参不影响,这里看的是匹配,不需要就可以不接收
void f(int)
{
	cout << "f(int)" << endl;
}
void f(int*)
{
	cout << "f(int*)" << endl;
}
int main()
{
	f(0);
	f(NULL);
	f((int*)NULL);
	return 0;
}

        程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。

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

         所以在C++11中,补充了nullptr关键字,代表空指针,从而弥补了NULL的小坑

注意:

  1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
  2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
  3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

总结

        c++入门,理解这些概念,再往后我们会多次使用,再次讲解。

下一篇,我将讲解C++的重难点——类和对象。最后,如果小帅的本文哪里有错误,还请大家指出,请在评论区留言(ps:抱大佬的腿),新手创作,实属不易,如果满意,还请给个免费的赞,三连也不是不可以(流口水幻想)嘿!那我们下期再见喽,拜拜!

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

web实验(2)

&#xff08;1&#xff09; 应用html标签和css完成如下所示页面效果&#xff0c;图片见附件。 说明&#xff1a; 内容相对于浏览器居中,宽860px鼠标移动至列表项上&#xff0c;显示背景色#F8F8F8分割线2px solid #ccc&#xff0c;每项高130px第一行文字&#xff1a;20px 黑体…

4.功能权限

基于角色的权限控制&#xff0c;用户分配角色&#xff0c;角色分配菜单。 1. 权限注解 1.基于【权限标识】的权限控制 权限标识&#xff0c;对应 system_menu 表的 permission 字段&#xff0c;推荐格式为 {系统}:{模块}:{操作}&#xff0c;例如说 system:admin:add 标识 sy…

chatgpt智能提效职场办公-ppt怎么做才好看又快

作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 制作ppt有几个方面可以考虑&#xff0c;以实现既好看又快速的目的&#xff1a; 使用模板&#xff1a;使用ppt模板可以更快速地制作出一…

JavaScript概述二(Date+正则表达式+Math+函数+面向对象)

1.Date 1.1 new一个Date对象表示当前系统时间 var nownew Date(); console.log(now);1.2 根据传入的时间格式表示时间 var date1new Date(2023-4-20 00:16:40); console.log(date1); 1.3 传入时间毫秒数&#xff0c;返回从1900年1月1日8时&#xff08;东八区&#xff09;X分X…

C语言入门篇——操作符篇

目录 1、操作符分类 2、操作符的属性 3、算术操作符 4、移位操作符 5、位操作符 6、赋值操作符 7、单目操作符 8、关系操作符 9、逻辑操作符 10、条件操作符 11、逗号操作符 12、下标引用、函数调用和结构成员 1、操作符分类 算术操作符&#xff08;&#xff0c;-&…

办公必备!不再被格式问题困扰,轻松搞定文档转换!

大家平时在工作中会需要将文档转换为其他格式吗&#xff1f; 日常工作中&#xff0c;经常碰到需要文件格式转换的情况&#xff0c;对于掌握了一些转换技能的朋友说&#xff0c;文件格式转换自然不在话下 对于不熟练的朋友来说&#xff0c;想要轻松转换文件格式&#xff0c;就…

c++ std::enable_shared_from_this作用

enable_shared_from_this 是什么 std::enable_shared_from_this 是一个类模板&#xff0c;用来返回指向当前对象的shared_ptr智能指针。在说明它的作用前我们可以看一下如下代码&#xff1a; demo.cpp #include <memory> #include <iostream>class A { public:A…

web实验(3)

应用JavaScript编写留言的功能&#xff0c;在文本中输入文字提交后&#xff0c;在下方进行显示。 提示&#xff1a;可将下方内容以列表体现&#xff0c;提交时动态创建列表的项。可使用以下两种方式之一的方法&#xff1a; 使用CreateElenment动态创建li标签及li中的文本 在列…

PADS-LOGIC项目原理图设计

最小板原理图设计 目录 1 菜单与工具使用 2 常用设置 2.1选项卡 2.2 图纸设置 2.3 颜色设置 3 设计技巧 3.1 模块化设计思路 3.2 元件放置 3.3 走线及连接符 4 原理图绘制 4.1 POWER原理图设计 4.2 MCU原理图设计 4.2.1晶振电路 4.2.2复位电路 4.2.3 BOOT电路 …

Windows 11快捷键功能大全 28个Windows 11快捷键功能介绍

Windows 11快捷键功能大全 28个Windows 11快捷键功能介绍 1. WinA 打开快速设置面板2. WinB 快速跳转系统托盘3. WinC 打开Microsoft Teams4. WinD 快速显示桌面5. WinE 打开资源管理器6. WinF 一键提交反馈7. WinG 启动Xbox Game Bar8. WinH 语音听写9. WinI 打开设置10. WinK…

如何正确高效地学习android开发?

每一个能成为行业大佬的人&#xff0c;一定有自己独特的方法… 之所以能成为大佬&#xff0c;是因为他们会有自己独特的见解&#xff0c;在一次次的尝试中不断否定&#xff0c;然后一次次的确定&#xff0c;一个程序员想要精益求精&#xff0c;必须要有高效的学习方法和良好的…

Spring Cloud Alibaba基于Sentinel实现限流降级自定义配置结果

hello&#xff0c;你好呀&#xff0c;我是灰小猿&#xff0c;一个超会写bug的程序员&#xff01; sentinel作为SpringCloudAlibaba的基本组件&#xff0c;在进行熔断、限流、降级等方面具有十分重要的作用&#xff0c;而且其基于Web界面对接口进行限流配置&#xff0c;使得实时…

环形链表II(链表篇)

给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整…

伪原创文章生成器-伪原创工具在线使用

文章伪原创工具 在如今数字时代&#xff0c;内容创作已经成为了一项必不可少的营销策略。然而&#xff0c;创作原创内容需要相当的时间和精力&#xff0c;尤其是对于需要大量输出内容的企业或个人而言。这时&#xff0c;文章伪原创工具就成为了一种快速、高效的选项。在本文中…

Doris(15):物化视图

1 概念 物化视图是将预先计算&#xff08;根据定义好的 SELECT 语句&#xff09;好的数据集&#xff0c;存储在 Doris 中的一个特殊的表。 物化视图的出现主要是为了满足用户&#xff0c;既能对原始明细数据的任意维度分析&#xff0c;也能快速的对固定维度进行分析查询。 首…

【C++】布尔类型(bool)

目录​​​​​​​ 1、缘起 2、笔记整理 4、用法 4.1、布尔变量的定义和初始化 4.2、布尔类型的运算符 4.3、布尔类型的条件语句 4.4、布尔类型的循环语句 5、总结 1、缘起 最近在 BiliBili 黑马程序员学习 C 编程语言&#xff0c;今天学习到了 布尔&#xff08;b…

算法套路十二——回溯法之排列型回溯

算法套路十二——回溯法之排列型回溯 该节是在第十节回溯法之子集型回溯的基础上进行描写&#xff0c;组合型回溯会在子集型回溯的基础上判断所选子集是否符合组合要求&#xff0c; 故请首先阅读第十节算法套路十——回溯法之子集型回溯 算法示例一&#xff1a;LeetCode46. 全…

windows环境安装tensorflow-gpu-2.10.1

Tensorflow 2.10是最后一个在本地windows上支持GPU的版本 1. 通过.whl文件方式安装2.创建anaconda虚拟环境3.安装对应的cuda与cudnn版本&#xff0c;local不必装cuda和cudnn4. 测试tensorflow gpu是否可用 1. 通过.whl文件方式安装 .whl文件的下载地址&#xff1a; tensorflow…

Linux — 线程概念和线程控制

目录 一、 线程的概念 什么是线程&#xff1f; 线程的优点 线程的缺点 线程异常 线程用途 二、线程的控制 创建线程 pthread_create函数 线程终止 pthread_exit函数 pthread_cancel函数 线程等待 pthread_join函数 分离线程 一、 线程的概念 之前的文章说过每个进程有…

IDE后端启动JetLinks 物联网基础平台(2.x)

目录 一、官网 二、文档中心 三、下载源码 四、安装依赖 五、IDE配置 六、修改配置文件&#xff1a;jetlinks-standalone/src/main/resources/application.yml 七、启动项目&#xff08;项目会自动建表&#xff09; 一、官网 JetLinkshttps://www.jetlinks.cn/#/ 二、…