C++:基于C的语法优化

C++:基于C的语法优化

    • 命名空间
      • 命名空间域
      • 域作用限定符
      • 展开命名空间域
    • 输入输出
    • 缺省参数
      • 全缺省参数
      • 半缺省参数
    • 函数重载
      • 参数类型不同
      • 参数个数不同
      • 参数类型的顺序不同
    • 引用
      • 基本语法
      • 按引用传递
      • 返回引用
      • 引用与指针的区别
    • 内联函数
    • auto
      • auto与指针和引用结合
    • 范围for循环
    • nullptr


C++语言是基于C语言优化而出的函数,由于C语言是一门比较古早的语言。随着学者们对计算机的理解不断加深,越来越好用的语法与概念提出,C语言就遗留了许多历史问题。C++之父为了优化C语言的问题,于是不断为其添加新语法,新概念,逐渐衍生出了一门新的语言C++。

命名空间

先看到一段C语言的代码:

#include <stdio.h>
#include <stdlib.h>

int rand = 1;

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

这段代码看似没有问题,但是运行后,编译器会报出“rand重定义”的问题。
这是因为我们引入了头文件stdlib.h,而其内部有rand函数,用户的变量名与头文件冲突了。
这该这么解决?
在C语言中,好像没有什么很好的办法,让不同头文件中的同名变量共存,只能让其中一者改变自己的变量。

C++设计者认为这个特性不利于项目合作,当我们对多个员工编写的C语言代码进行合并时,就有可能出现,此时只能让其中一者修改代码。
C++为此设计了一种新的域:命名空间域

命名空间域

在不同的域中,是可以存在同名变量的,而C语言只存在局部域与全局域两种域。C++的命名空间域则是一种可以根据用户需要自己定义的域。

语法:

namespace (名称)
{
	//代码
}

命名空间域通过关键字namespace指定,其大括号内部算作单独的一块域,不受外界域的影响,比如这样:

namespace A
{
	int a = 1;
}

namespace B
{
	int a = 2;
}

int a = 3;

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

在以上代码片中,我们有三个变量aa = 1处于命名空间域A中,a = 1处于命名空间域B中,而a = 1处于全局中。

我们此时输出printf("%d", a)会输出谁?
答案是3。

其变量的基本查找规则如下:

  1. 现在当前作用域查找
  2. 如果当前作用域查找不到,就向上级作用域查找
  3. 直到查找到全局作用域,如果此时还没有,编译器报错

所以a在访问时,会访问到全局的a。

命名空间域有以下特性:

  1. 当两个命名空间域重名,两个域内部的代码会合并
  2. 作用域可以嵌套
  3. 变量,结构体,函数等等都可以写入这个域中

那么我们要如何访问到我们自己指定的命名空间域中的变量呢?
这就要通过域作用限定符了:


域作用限定符

::是C++中的域作用限定符,将其放在变量前,可以改变此变量的查找规则,使之直接到指定域中查找

比如以下代码:

namespace A
{
	int a = 1;
}

int a = 3;

int main()
{
	printf("%d", A::a);
	return 0;
}

其中A::a就是直接在命名空间域中查找a变量。
所以代码输出1。

此外:域作用限定符左侧没有值时,默认到全局变量查找
这一点很重要,因为在基本的查找规则中,是先查找局部作用域,再查找全局作用域的。而当::左侧没有值时,会直接跳过局部变量,在全局中查找。
比如以下代码:

int a = 3;

int main()
{
	int a = 4;
	printf("%d", ::a);
	return 0;
}

上述代码的输出结果是3。
虽然在局部中有一个a = 4,但是::a会直接跳过局部,直接去全局查找,所以最后输出了3.

访问嵌套的命名空间域:
想要访问嵌套的命名空间域,只需要依据从外层->内层的顺序,利用::将每个名称分隔开,就可以访问了,如下:

namespace A
{
	namespace B
	{
		namespace C
		{
			int a = 2;
		}
	}
}

int main()
{
	printf("%d", A::B::C::a);
	return 0;
}

以上代码中,我们嵌套了三层命名空间域,在访问a时,从外层到内层按照A::B::C::a访问。

所以我们可以按照如下方式解决不同文件变量可能存在冲突的问题:每个.cpp文件最外层,用一个命名空间域包含起来,后续引入文件时,每个人编写的文件独自享有一个域,就不会发生冲突问题了。
比如这样:

user1.cpp

namespace user1
{
	int a = 0;
	int b = 1;

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

user2.cpp

namespace user2
{
	int a = 1;
	int b = 0;

	float Add(float x, float y)
	{
		return x + y;
	}
}

每一份.cpp文件都用一个命名空间域包在最外层,需要使用谁的代码时,就到哪一个空间域中查找。

那如果这样的话,在main函数中想要访问其它文件内的内容是不是很冗余,几乎大部分变量都要加上::前缀,这就太麻烦了。于是又产生了展开命名空间域这一功能。


展开命名空间域

所谓展开命名空间域,就是对某个空间域进行展开,将其内部的变量放到全局中。也就是说,一个空间域的内容,经过展开后就会变成全局变量,而变量查找规则中,最后一层就是在全局中查找,所以可以不使用::就访问到想要的变量。
语法:

using namespace (名称);

示例:

namespace user1
{
	int a = 0;
	int b = 1;
}

using namespace user1;

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

在以上示例中,我们使用using namespace user1;user1展开了,此时user1内部的变量就被释放到全局了,后续就无需对ab使用域限定操作符,也可以直接使用了。

但是有时候我们并不是需要一个命名空间域中的所有内容,如果将整个空间域有些没必要。
此时我们可以使用部分展开

using (名称)::(变量名)

示例:

namespace user1
{
	int a = 0;
	int b = 1;

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

using user1::Add;
using user1::a;

int main()
{
	printf("%d",a);
	Add(3, 5);
	return 0;
}

上述代码中,我们创建了一个空间域user1,其内部有ab两个变量以及Add函数。
随后将using user1::Add;using user1::a;进行了部分展开。
最后我们就可以直接访问变量a以及调用Add函数了。


输入输出

C++的输入输出是基于对象的操作,但是此处仅做入门知识讲解,所以不深入讲解,只讲解基本输入输出语句。

输出语句:

cout << "Hello World" << endl;

在以上语句中,cout本质是一个对象,如果你无法理解什么是对象,那么可以暂时理解它是一个控制台,可以看到输出语句。
随后利用了<<流插入运算符,你可以理解为将"Hello World"这个字符串放到了cout中,随后 << endl的意思是换行endl相当于C语言中的\n,用于换行。所以以上语句也可以写成:

cout << "Hello World" << '\n';

效果是一致的。

输入语句:
C++的输入语句是通过cin对象,其可以获取用户输入的内容。那么我们要如何获得cin提取的内容?
利用流提取操作符<<,就可以提取到cin的返回值。比如这样:

int a = 0;
cin >> a;

以上代码就可以实现用户输入一个值,将其赋值给a。

相比于C语言的输入输出,需要使用%d%s%f这样的占位符来控制输入类型。C++的输入输出操作明显的优势就是:自动识别类型。其中cout可以拆分为c + out,所以用于输出;cin可以拆分为c + in,所以用于输入


缺省参数

全缺省参数

缺省参数是值可以为函数的参数设置初始值,如果调用时没有传入参数,则此参数以初始值调用函数。

比如以下代码:

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

int main()
{
	Add(1, 2);
	Add(1);
	Add();

	return 0;
}

上述代码中,我们定义了一个函数Add,其带有两个参数xy,其中为x设置初始值x = 5,给y设置初始值y = 10

第一次调用Add(1, 2);xy都传了参数,此时完成的是1 + 2
第二次调用Add(1);只为x传入了参数,此时y以初始值调用此函数,完成的是1 + 10
第三次调用Add();没有传入参数,此时xy都以初始值调用此函数,完成的是5 +10

这种参数缺省叫做全缺省参数,即所有的参数都赋予了初始值,哪怕一个参数都不传,也可以调用函数。
注意:传入参数必须从左往右传入,不能有空缺
比如以下代码:

int Add(int x = 5, int y = 10, int z = 20)
{
	return x + y + z;
}

int main()
{
	Add(1, ,6);
	return 0;
}

此代码中Add(1, ,6);的意图是让x = 5z = 20,让y取初始值。但是这是不允许的,调用函数时,必须从左向右连续传入,不能间断地缺省参数

半缺省参数

半缺省参数是指,缺省参数时,有一些值不赋予初始值,必须传入值

比如这样:

int Add(int x, int y = 10, int z = 20)
{
	return x + y + z;
}

此时x就是一个不可以被缺省的参数,在调用函数时,必须为x传入值。
要注意:半缺省参数中不赋予初始值的参数,必须从左往右连续,不可以间断地缺省。
比如以下情况:

int Add(int x = 5, int y, int z = 20)
{
	return x + y + z;
}

此代码中,x是被缺省的,那么其右边的yz也必须被缺省,不能跳过y直接缺省z

最后还有一个注意点:不能在声明和定义时同时缺省参数。
什么意思呢?
看到一个示例:
test.h文件中:

void func(int a = 10);

test.cpp文件中:

void func(int a = 10)
{
	cout << a * 5 << endl;
}

以上代码我们将函数声明在了test.h文件中,声明在了test.cpp文件中。
这样会造成重定义的错误,程序无法运行,如果想要将缺省参数声明在.h文件中,那么在定义时就不要写出缺省参数了。
以上代码的正确形式如下:

test.h文件中:

void func(int a = 10);

test.cpp文件中:

void func(int a)
{
	cout << a * 5 << endl;
}

函数重载

函数重载是指C++允许在同一作用域中声明的同名函数,但是其必须遵守一项规则:保证同名函数的形参列表不同。

形参列表不同就是要求满足以下三者之一:

  1. 函数的参数个数不同
  2. 函数的参数类型不同
  3. 函数的参数类型的顺序不同

接下来我带大家理解这三种情况。

参数类型不同

void Add(int left, int right)
{
	cout << "I am int Add" << endl;
}

void Add(double left, double right)
{
	cout << "I am double Add" << endl;
}

在以上代码中,我们定义了两次Add函数,第一次定义时两个参数的类型都是int,而第二次定义时,两个参数的类型都是double,此时两个Add函数就构成了重载。
在调用Add函数时,会根据传入参数的类型来决定调用哪一个函数。
比如以下代码:

int main()
{
	int a = 1;
	int b = 2;
	Add(1, 2);

	double c = 3.0;
	double d = 4.0;
	Add(c, d);

	return 0;
}

第一次调用Add(1, 2);传入了两个int变量,此时与函数void Add(int left, int right)类型匹配,调用此函数,输出"I am int Add"

第二次调用Add(c, d);传入了两个double变量,此时与函数void Add(double left, double right)类型匹配,调用此函数,输出"I am double Add"


参数个数不同

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

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

void f(int a, int b)
{
	cout << "f(int a, int b)" << endl;
}

以上代码中,我们定义了三个f函数,三者的区别就是函数的参数个数不同,那么我们传入不同数量的参数,也就会调用不同的函数了。

int main()
{
	f();
	f(1);
	f(1, 2);
	return 0;
}

第一次调用f();,没有传入参数,与void f()参数数目匹配,调用此函数。
第二次调用f(1);,传入一个参数,与void f(int a)参数数目匹配,调用此函数。
第三次调用f(1, 2);,传入两个参数,与void f(int a, int b)参数数目匹配,调用此函数。


参数类型的顺序不同

void f(int a, char b)
{
	cout << "f(int a,char b)" << endl;
}

void f(char b, int a)
{
	cout << "f(char b, int a)" << endl;
}

以上代码中,我们定义了两个函数f,第一个函数的参数列表为int, char第二个参数的参数列表为char, int此时两个参数类型的顺不同,构成函数重载。
示例:

int main()
{
	int a = 0;
	char b = '0';

	f(a, b);
	f(b, a);

	return 0;
}

第一次调用,传入了f(a, b);,与参数列表int, char匹配,调用函数输出"f(int a,char b)"
第二次调用,传入了f(b, a);,与参数列表char, int匹配,调用函数输出"f(char b, int a)"


引用

基本语法

C++的引用是一种特殊的变量类型,用于给已经存在的变量起一个别名。通过引用,我们可以通过一个已存在的变量名来访问和操作另一个变量的值。

引用可以被看作是一个已存在变量的别名,引用和被引用的变量始终指向同一块内存空间,对引用的操作实际上就是对被引用变量的操作。

引用的语法如下:

type& 别名 = 变量名;

其中,type是被引用变量的类型。

下面是一个使用引用的简单示例:

int main() {
   int num = 10;
   int& ref = num;    // 创建一个引用ref,指向num

   cout << "num的值为:" << num << endl;   // 输出:num的值为:10
   cout << "ref的值为:" << ref << endl;   // 输出:ref的值为:10

      // 通过引用修改num的值

   cout << "num的新值为:" << num << endl;   // 输出:num的新值为:20
   cout << "ref的新值为:" << ref << endl;   // 输出:ref的新值为:20

   return 0;
}

在上面的示例中,我们创建了一个整数变量num,并通过引用ref给它起了一个别名。后续通过引用ref来修改num的值,实际上就是对num的直接操作。

其中:

   int num = 10;
   int& ref = num;  
   
	ref = 20;

相当于:

	int num = 10;
	int* ref = &num;

	*ref = 20;

需要注意的是,引用不同于指针,它不能指向空值或者没有初始化的变量。因此,在定义引用时必须保证所引用的变量已经存在,并且在定义引用时必须进行初始化

也就是说下面的语句是非法的:

int& a;

这语句中,a是一个引用,但是它没有初始化,此时编译器会报错。
但是在指针中:

int* a;

是合法的。


引用其实不单单只是代替指针这么简单,其还可以作为返回值,参数等。

按引用传递

C++中的按引用传递是一种参数传递方式,它允许函数通过引用来操作调用者提供的实参。

按引用传递是将实参的引用传递给形参。

按引用传递的语法是在函数的参数前加上&符号。例如,以下的函数原型中使用了按引用传递:

void Function(int& x);

按引用传递有以下几个作用:

  1. 通过引用传递参数可以避免对大型对象的复制。当传递一个大型对象时,按值传递会进行一次复制操作,而按引用传递只需要传递对象的引用而不需进行复制,从而提高了程序的效率

  2. 通过引用传递参数可以实现函数对实参的修改。在函数内部,通过引用可以直接操作实参,对实参的修改会在函数外部产生影响。而按值传递只能修改函数内部的形参副本,对实参没有影响

比如我们想实现一个交换函数:
利用指针来实现:

void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

int main()
{
	int x = 1;
	int y = 3;

	Swap(&x, &y);

	return 0;
}

此函数中,不仅需要多次对参数解引用,而且每次调用都需要对变量取地址,用起来还是有点难受的。

此时我们可以利用按引用传递实现:


void Swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

int main()
{
	int x = 1;
	int y = 3;

	Swap(x, y);

	return 0;
}

相比于刚才的代码,这串代码就畅快多了,一方面是在函数内部使用参数时不用额外解引用,在传参时也不需要取地址了。

总之,按引用传递是一种高效且灵活的参数传递方式,可以减少内存的复制操作,实现对实参的修改。在C++中,通过引用传递可以提高程序的效率和可读性。


返回引用

在C++中,返回引用是指从函数中返回一个引用类型的值。返回引用的主要目的是允许函数返回一个对于某个变量的引用,从而允许在函数外部对该变量进行修改。

返回引用的主要用途有以下几个:

  1. 允许函数直接修改函数外部的变量。
  2. 允许在函数调用中连续进行操作,类似于链式操作。
  3. 优化性能,避免创建临时对象。

下面通过案例来分别说明这几个功能:

  1. 允许函数直接修改函数外部的变量:
int& increment(int& num) {
  num++;
  return num;
}

int main() {
  int num = 5;
  increment(num) = 10;
  cout << num << endl;  // 输出为 10
  return 0;
}

在上面的例子中,increment函数返回了对num的引用。在main函数中,我们可以直接对increment(num)进行赋值操作,相当于对num进行了修改。

  1. 允许在函数调用中连续进行操作:
int& add(int& num, int value) {
  num += value;
  return num;
}

int main() {
  int num = 5;
  add(add(num, 3), 2);
  cout << num << endl;  // 输出为 10
  return 0;
}

在上面的例子中,add函数返回了对num的引用。我们可以连续调用add函数,每次都对num进行修改。

  1. 优化性能,避免创建临时对象:
string& concatenate(string& str1, const string& str2) {
  str1 += str2;
  return str1;
}

int main() {
  string str1 = "Hello";
  string str2 = " World";
  concatenate(str1, str2) += "!";
  cout << str1 << endl;  // 输出为 "Hello World!"
  return 0;
}

在上面的例子中,concatenate函数返回了对str1的引用。通过返回引用,我们可以直接对str1进行修改,避免了创建临时对象。在调用concatenate函数的时候,我们可以将返回的引用与另一个字符串连接操作进行连续调用。

需要注意的是,返回引用时,被返回的变量应该仍然存在,否则返回的引用就会变成悬空引用,可能导致不可预期的行为。此外,如果返回引用指向了一个局部变量,函数返回后该变量将被销毁,返回的引用将变得无效。因此,返回引用时需要确保引用的有效性。


引用与指针的区别

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

内联函数

在讲解内联函数前,我们前看看C语言中的宏的缺点。
C语言宏的缺点有以下几个:

  1. 没有类型检查: 宏是在预处理阶段进行替换,没有类型检查的机制。因此,使用宏时要特别小心,否则可能会出现类型不匹配的错误。
  2. 可读性差: 宏通常会展开为较长的代码,可能会使代码变得难以阅读和理解。特别是在宏内部使用复杂的表达式或多行代码时,会使代码的可读性大大降低。
  3. 可能引起副作用: 宏通常会直接对参数进行替换,可能会导致意外的副作用。例如,一个宏可能会多次计算参数的值,如果参数是一个函数调用或者是一个带有副作用的表达式,那么可能会引发错误。
  4. 可能导致重复的代码: 使用宏可能导致代码中出现大量的重复代码。当多个地方使用相同的宏时,如果需要修改宏的实现方式,就需要修改所有使用该宏的地方,增加了代码维护的复杂性。
  5. 调试困难: 宏在展开后的代码中看不到宏本身的定义,因此在调试时很难跟踪和查找问题。由于宏在编译阶段被替换,调试器无法直接定位到宏的定义位置,这给调试带来了一定的困难。

综上所述,虽然宏在C语言中具有一定的灵活性和便利性,但也存在一些缺点。在使用宏时应当谨慎,特别是在处理复杂的表达式或有副作用的代码时,应考虑使用其他更安全和可读性更高的替代方法。

C++认为宏是一个不太好的特性,于是在C++中推荐使用enum枚举和const替换掉宏常量。用内联函数inline替换掉宏函数。

于是内联函数被设计了出来。

被inline修饰的函数叫做内联函数,在编译时C++编译器会在调用内联函数的地方将内联函数展开,不额外创建栈帧来执行函数,提高程序的效率。没错,这也是宏函数最重要的一点,不会创建栈帧。内联函数延续的宏函数的优点,但是又做了许多优化。

比如以下函数就是一个内联函数:

inline int Add(int x, int y)
{
	int z = x + y;
	return z;
}

在函数的前方加一个inline关键字,这样调用函数时,函数就会直接在目标位置展开。

在相比于宏函数,内联函数会对参数类型进行确定,防止错误类型的传入。

如果宏函数非常长,那么对其展开时会导致代码重复性非常高,这已经违背了函数设计的初衷:代码复用。
所以内联函数有另外一个特性:当函数体内部代码长度超过一定值时,其会转化为普通函数,不会直接展开,而是创建栈帧,防止代码冗余


auto

在C++中,auto关键字可以用来自动推断变量的类型,它在编译时会根据初始化表达式的类型来确定变量的类型。

使用auto的主要好处是可以简化代码并提高可读性。它可以减少手动指定变量类型的工作,并且可以防止类型错误。相比于显式指定变量类型,使用auto可以让代码更加灵活和易于维护。

  1. 自动推断基本类型变量的类型
auto age = 25; // 推断age为int类型
auto salary = 5000.50; // 推断salary为double类型
  1. 自动推断容器中迭代器的类型
std::vector<int> numbers = {1, 2, 3, 4, 5};
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
    std::cout << *it << " ";
}

如果你看不懂这一段也没关系,这一段主要是讲解有的时候获得变量的类型会需要很长的代码。使用auto可以缩短变量类型的长度。

auto与指针和引用结合

auto也可以自动推断指针的类型,比如这样:

int x = 10;
auto y = &x;

此时y的类型自动判别为int*
那么我们可不可以为auto加上*来识别指针?

看到一段代码:

int x = 10;

auto* a1 = x;
auto* a2 = &x;
auto a3 = &x;

auto* a1 = x;中,x的类型是int,那么auto本应将其值判别为int,但是由于auto**限制了,此时auto必须得到一个指针,所以编译器会报错;而auto* a2 = &x;得到的就是指针,此时代码不会报错,可以正常识别为int*

在本质上auto* a2 = &x;auto a3 = &x;的结果是没有区别的,只是auto*要求得到的必须是一个指针类型,而auto不限制其类型。
同理的auto&会要求必须是一个引用类型,否则会报错。

auto也有许多限制,要注意以下问题:

  1. auto不能作为函数的参数

  2. auto不能用于声明数组

比如以下代码:

int arr1[] = {1, 3, 5, 7, 9};
auto arr2[] = {1, 3, 5, 7, 9};

此时第二条代码就会报错,因为其用auto类型定义了一个数组。

  1. 在同一行定义多个变量时,如果将auto作为其类型,必须一整行都是同一个类型的变量。

比如以下代码:

	int x = 1, y = 2;
	auto a = 3, b = 4;
	auto c = 5, d = 6.0;

以上代码中,auto a = 3, b = 4;是合法的,因为一行内都是int类型。
但是auto c = 5, d = 6.0;是非法的,因为同一行内有不同类型,会报错。


范围for循环

范围for循环是C++11引入的一种新的循环结构,它可以方便地遍历数组或者其他具有迭代器的对象。

范围for循环的语法如下:

for (auto element : collection) {
   // 执行语句
}

其中,element 是一个临时变量,用来存储集合中的每个元素的副本,collection 是一个可迭代的对象,可以是数组或者其他具有迭代器的对象。
其中auto也可以换为intfloat等类型,只是结合auto会更好用。

下面是一个简单的例子:

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

   for (auto element : numbers) {
      cout << element << " ";
   }

   return 0;
}

输出结果为:1 2 3 4 5

在上面的例子中,我们定义了一个整型数组 numbers,范围for循环遍历了整个数组,每次迭代将数组中的一个元素赋值给临时变量 element,然后我们将该元素输出到控制台。

如果你希望修改这个数组内部的值,可以在auto后加上&,将其变为一个引用。

就像这样:

   for (auto& element : numbers) {
      element *= 2;
   }

就可以完成元素的乘以2的操作。


nullptr

在C++11标准中,引入了nullptr关键字来表示空指针。C++推荐使用nullptr而不是使用传统的NULL宏定义。

看到一段代码:
在这里插入图片描述
这串代码是C++对NULL的定义,其本质是一个宏,如果在C语言环境允许,那么NULL就是((void*)0),也就是将整型0强制转化为了void*的0地址。

但是当运行环境是C++,NULL就被定义为0,这导致空指针可能被识别为整型。所以C++引入了nullptr替代NULL

NULL在传统的C++中只是一个宏定义为0,会被隐式转换为整型,这可能导致一些类型安全性问题。nullptr不会被隐式转换为其他类型,只能赋值给指针类型,从而避免了潜在的类型错误。

其次是代码清晰度,nullptr相比于NULL更加直观明了,能够更好地表示空指针的含义即null + ptrnull表示空ptr表示指针。这样可以提高代码的可读性。

所以在C++中,定义一个空指针最好用nullptr


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

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

相关文章

官方版2345加速浏览器(好用的浏览器分享)

官方版2345加速浏览器&#xff08;好用的浏览器分享&#xff09; 2345加速浏览器拥有智能拦截骚扰广告&#xff0c;识别欺诈网站&#xff0c;云收藏夹等功能&#xff0c;高速上网、不假死、不卡机&#xff0c;是一款强大的多功能网页浏览器。 使用2345加速浏览器,您可以轻松应对…

DHCP配置(路由器,交换机)

DHCP接口地址池配置 拓扑 PC配置DHCP点击应用。 路由器配置命令 <Huawei>sy Enter system view, return user view with CtrlZ. [Huawei]int g0/0/1[Huawei-GigabitEthernet0/0/1]ip address 10.1.1.1 24[Huawei-GigabitEthernet0/0/1]q[Huawei]dhcp enable Info: T…

【日常聊聊】边缘计算的挑战和机遇

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a; 日常聊聊 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 边缘计算的挑战和机遇 一&#xff1a;数据安全与隐私保护 二&#xff1a;网络稳定性与可靠性 三&#xff1a;实时性与性能优…

电压检测芯片适用于哪些应用领域?

原文链接&#xff1a; 电压检测芯片适用于哪些应用领域&#xff1f; - 知乎 (zhihu.com) 电压检测基本涉及到电子世界的方方面面。 我上一份工作是做无人机飞控研发&#xff0c;无人机在使用过程中是需要事件监测电压的&#xff0c;还需要针对电压对航行进行预估&#xff0c;…

推荐新版AI智能聊天系统网站源码ChatGPT NineAi

Nine AI.ChatGPT是基于ChatGPT开发的一个人工智能技术驱动的自然语言处理工具&#xff0c;它能够通过学习和理解人类的语言来进行对话&#xff0c;还能根据聊天的上下文进行互动&#xff0c;真正像人类一样来聊天交流&#xff0c;甚至能完成撰写邮件、视频脚本、文案、翻译、代…

SpringMVC(八)处理AJAX请求

一、处理AJAX之准备工作: 首先我们创建一个新的工程: 我们将pom.xml复制过来: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-in…

两条链表相同位数相加[中等]

优质博文IT-BLOG-CN 一、题目 给你两个非空的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照逆序的方式存储的&#xff0c;并且每个节点只能存储一位数字。请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。你可以假设除了数字0之外&#xff0c;这…

HCIA-HarmonyOS设备开发认证-HarmonyOS简介

目录 前言目标一、HarmonyOS简介1.1、初识HarmonyOS1.2、HarmonyOS典型应用场景 二、HarmonyOS架构与安全2.1、HarmonyOS架构 前言 本章主要介绍HarmonyOS分布式操作系统的概念、关键技术与能力以及HarmonyOS典型的应用场景。 目标 学习完成本课程后&#xff0c;您将能够&…

二、用户管理(上)

目录 1.用户/组基本概念 用户基本信息文件&#xff1a;vim /etc/passwd&#xff08;冒号为分隔&#xff0c;分为7列字段&#xff09; 用户密码信息文件&#xff1a;/etc/shadow 组信息文件&#xff1a;/etc/group。 2.用户/组管理 查看当前用户&#xff1a;whoami 创建用…

解锁黑匣子:Chain-of-Note如何为(RAG)带来透明度

英文原文地址&#xff1a;https://ai.plainenglish.io/unlocking-the-black-box-how-chain-of-note-brings-transparency-to-retrieval-augmented-models-rag-ae1ebb007876 论文地址&#xff1a;https://arxiv.org/pdf/2311.09210.pdf 2023 年 11 月 16 日 介绍 检索增强语…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《基于混合博弈的配电网与多综合能源微网优化运行》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 这个标题涉及到配电网和多综合能源微网的优化运行&#xff0c;而优化的方法基于混合博弈理论。让我们逐步解读这个标题的关键部分&#xff1a; 基于混合…

django邮件通知功能-

需求&#xff1a; 1&#xff1a;下单人员下订单时需要向组长和投流手发送邮件通知 2&#xff1a;为何使用邮件通知功能&#xff1f;因为没钱去开通短信通知功能 设计 1&#xff1a;给用户信息表添加2个字段 第一个字段为&#xff1a;是否开通邮件通知的布尔值 第二个字段为: 用…

一键完成,批量转换HTML为PDF格式的方法,提升办公效率

在当今数字化的时代&#xff0c;HTML和PDF已经成为两种最常用的文件格式。HTML用于网页内容的展示&#xff0c;而PDF则以其高度的可读性和不依赖于平台的特性&#xff0c;成为文档分享和传播的首选格式。然而&#xff0c;在办公环境中&#xff0c;我们经常需要在这两种格式之间…

外包干了5个月,技术退步明显...

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能测…

VS Code + Python + Selenium 自动化测试基础-01

VS Code Python Selenium 自动化测试基础-01 让我们来讲一个故事为什么要写自动化开发前的准备工作牛刀小试开常用的web DriverAPI-定位元素id定位&#xff1a;find_element_by_id()name 定位&#xff1a;find_element_by_name()class 定位&#xff1a;find_element_by_class…

logstack 日志技术栈-04-opensource 开源工具 OpenObserve+Grafana Loki

日志技术栈 日志管理包含日志数据存储、处理、分析和可视化&#xff0c;通过利用日志管理工具&#xff0c;可以监控性能趋势、解决问题、检测异常并优化整体系统性能。 近年来&#xff0c;开源日志管理解决方案在大家寻求灵活且经济有效的方式来管理现代系统典型的大量日志数…

04 思维导图的方式回顾ospf

思维导图的方式回顾OSPF 1 ospf 领行学习思维导图 1.1 ospf 的工作过程 建立领据表同步数据库计算路由表1.2 ospf 的状态 1.3 ospf的报文 1.4 ospf的L

Unity中URP下的SimpleLit的 Lambert漫反射计算

文章目录 前言一、Lambert漫反射计算11、MixRealtimeAndBakedGI 函数有三个重载2、3号 调用了 2号3、1号调用了 SubtractDirectMainLightFromLightmap函数4、我们重点来看 Lambert漫反射的实现部分5、其余部分 二、Lambert漫反射计算21、LightingLambert 前言 在之前的文章中&…

hot100:06三数之和

题目链接&#xff1a; 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 算法思想&#xff1a; 使用双指针的思想&#xff0c;首先需要先对数组进行排序&#xff0c;让数组满足单调性&#xff0c;这样在相加的时候更加方便更新条件&#xff1b;再遍历…

【代码整理】基于COCO格式的pytorch Dataset类实现

import模块 import numpy as np import torch from functools import partial from PIL import Image from torch.utils.data.dataset import Dataset from torch.utils.data import DataLoader import random import albumentations as A from pycocotools.coco import COCO …