【C++】C++入门

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

C++ 总计 63 个关键字,C 语言 32 个关键字。C 语言的关键字在 C++ 中继续可以使用。 C++ 兼容 C 的绝大多数语法。

二、命名空间

在 C / C++ 中,变量、函数和类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是 对标识符的名称进行本地化,以避免命名冲突或名字污染 ,namespace 关键字的出现就是针对这种问题的。
#include <stdio.h>
#include <stdlib.h>int rand = 10;
// C语言没办法解决类似这样的命名冲突问题,所以C++提出了namespace来解决
int main()
{printf("%d\n", rand);return 0;
}
// 编译后报错:error C2365: “rand”: 重定义;以前的定义是“函数”

在不同的作用域中,可以定义同名的变量;但在同一作用域下,不能定义同名的变量。 

用命名空间来解决变量 rand 和 stdlib 库里的命名冲突。

#include<stdio.h>
#include<stdlib.h>namespace yln // 定义了一个命名空间域
{int rand = 10; // 定义变量int Add(int x, int y) // 定义函数{return x + y;	}struct Node // 定义结构体类型{struct Node* next;int val;	};
}int main()
{printf("%p\n", rand);//函数指针printf("%d\n", yln::rand); // rand变量;'::'叫做域作用限定符yln::Add(3, 5);//调用函数struct yln::Node node1; // 结构体return 0;
}

1、命名空间的定义

定义命名空间,需要使用到  namespace 关键字,后面 + 命名空间的名字 ,然后 接一对 {}  即可,{} 中即为命名空间的 成员
(1)正常的命名空间定义 
namespace yln
{int rand = 10; // 命名空间中可以定义变量int Add(int left, int right) // 命名空间中可以定义函数{return left + right;}struct Node // 命名空间中可以定义类型{int val;struct Node* next;};
}

yln 是命名空间的名字,一般开发中是用项目名字做命名空间名。


(2)命名空间可以嵌套
// test.cpp
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()
{printf("%d\n", N1::N2::Sub(10, 20)); // 访问嵌套命名空间return 0;
}

(3)同一个工程中允许存在多个相同名称的命名空间
// test.h
namespace N1
{int Mul(int left, int right){return left * right;}
}

同一个工程中的 test.h 和上面 test.cpp 中的两个 N1 会被编译器合并成在同一个命名空间中。

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


2、命名空间的使用

命名空间的使用有三种方式:
(1)加命名空间名称及作用域限定符
int main()
{printf("%d\n", N::a);return 0;    
}

⚪优点:不存在命名污染。

⚪缺点:如果要去访问多个命名空间里的东西时,需要一一指定。


(2)使用 using 将命名空间中某个成员引入
using N::b;
int main()
{printf("%d\n", N::a);printf("%d\n", b);return 0;    
}

⚪优点:不会造成大面积的污染;把常用的展开后,也不需要一一指定。


(3)使用 using namespace 将命名空间名称引入
using namespace N;
int main()
{printf("%d\n", N::a);printf("%d\n", b);Add(10, 20);return 0;    
}

⚪优点:方便。

⚪缺点:自己定义的东西会暴露出去,导致命名污染。


三、C++ 输入&输出

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

std 是 C++ 标准库的命名空间名,C++ 将标准库的定义实现都放到这个命名空间中。

  1. 使用 cout 标准输出对象(控制台)和 cin 标准输入对象(键盘)时,必须包含 <iostream> 头文件以及按命名空间使用方法使用 std
  2. cout 和 cin 类似 C 语言的 printf 和 scanf,这里先简单了解一下,因为对于 C 语言中的 I/O 是函数,而 C++ 是对象。cout 和 cin 是全局流对象endl 是特殊的 C++ 符号,表示换行输出,他们都包含在包含 <iostream> 头文件中。
  3. << 是流插入运算符,>> 是流提取运算符
  4. 使用 C++ 输入输出更方便,不需要像 printf / scanf 输入输出时那样,需要手动控制格式。
  5. C++ 的输入输出可以自动识别变量类型。
  6. 实际上 cout 和 cin 分别是 ostream 和 istream 类型的对象,>> 和 << 也涉及运算符重载等知识。
注意 :早期标准库将所有功能在全局域中实现,声明在 .h 后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在 std 命名空间下,为了和 C 头文件区分,也为了正确使用命名空间,规定 C++ 头文件不带 .h ;旧编译器 (vc 6.0) 中还支持 <iostream.h> 格式,后续编译器已不再支持,所以推荐使用 <iostream> + std 的方式
#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;
}

关于 cout 和 cin 还有很多更复杂的用法,比如控制浮点数输出精度,控制整形输出进制格式等。但是因为 C++ 是兼容 C 语言的用法,这些又用得不是很多,所以我们一般可以直接用按照 C 语言的用法来写,就不用再进一步学习 C++ 这方面的内容了。

std 命名空间的使用惯例:
std 是 C++ 标准库的命名空间,如何展开 std 使用更合理呢?
  1. 日常练习中,建议直接 using namespace std 即可,这样就很方便。
  2. using namespace std 展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型 / 对象 / 函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模大,就很容易出现这种情况。所以建议在项目开发中使用,像 std::cout 这样使用时指定命名空间 + using std::cout 展开常用的库对象 / 类型等方式
#include<iostream>// 展开常用即可,工程项目中常见的对命名空间的用法
using std::cout;
using std::endl;int main()
{// 只要是库里的都得指定stdstd::cout << "Hello world!" << std::endl//cout << "Hello world!" << endl;return 0;
}

四、缺省参数

1、概念

缺省参数 声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参
void Func(int a = 0)
{cout << a << endl;
}int main()
{Func();   // 没有传参时,使用参数的默认值 - 0Func(10); // 传参时,使用指定的实参 - 10return 0;
}

2、分类

(1)全缺省参数
void Func(int a = 10, int b = 20, int c = 30)
{cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl;
}

(2)半缺省参数
void Func(int a, int b = 10, int c = 20)
{cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl;
}

注意: 

  1. 半缺省参数必须从右往左依次给出,不能间隔着给。
  2. 缺省参数不能在函数声明和定义中同时出现。
  3. 缺省值必须是 常量 或者 全局变量
  4. C 语言不支持(编译器不支持)。
// test.h
void Func(int a = 10);// test.cpp
void Func(int a = 20)
{}

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


五、函数重载

1、概念

函数重载 :是函数的一种特殊情况,C++ 允许在同一作用域中声明几个功能类似同名函数,这些同名函数的形参列表(参数个数 / 类型 / 类型顺序)不同,常用来处理实现 功能类似数据类型不同 的问题。
(1)参数类型不同
// test1.cpp
#include <iostream>
using namespace std;int Add(int left, int right)
{cout << "int Add(int left, int right)" << endl;return left + right;
}double Add(double left, double right)
{cout << "double Add(double left, double right)" << endl;return left + right;
}

(2)参数个数不同
void f()
{cout << "f()" << endl;
}void f(int a)
{cout << "f(int a)" << endl;
}

(3)参数类型顺序不同
// test2.cpp
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;
}int main()
{Add(10, 20);Add(10.1, 20.2);f();f(10);f(10, 'a');f('a', 10);return 0;
}

2、C++支持函数重载的原理 -- 名字修饰 (name Mangling)

为什么C++支持函数重载,而C语言不支持函数重载呢?
在 C / C++ 中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接

可参考【C语言】程序环境预处理 -- 详解_炫酷的伊莉娜的博客-CSDN博客 ,内有详细介绍。

        实际项目通常是由多个头文件和多个源文件构成,而通过 C 语言阶段学习的编译链接,我们 知道,当前 test2.cpp 中调用了 test1.cpp 中定义的 Add 函数,在编译后链接前,test2.o 的目标文件中没有 Add 的函数地址,因为 Add 是在 test1.cpp 中定义的,所以 Add 的地址在 test1.o 中,那该怎么办呢?

        所以链接阶段就是专门处理这种问题,链接器看到 test2.o 调用 Add,但是没有 Add 的地址,就会到 test1.o 的符号表中找 Add 的地址,然后链接到一起

        那么在链接时,面对 Add 函数,链接接器会使用哪个名字去找呢?这里每个编译器都有自己的函数名修饰规则。由于 Windows 下 VS 的修饰规则过于复杂,而 Linux 下 G++ 的修饰规则简单易懂,下面我们使用了 G++ 演示了这个修饰后的名字。

(1)采用 C 语言编译器编译后的结果

结论 :在 Linux 下,采用 GCC 编译完成后,函数名字的修饰没有发生改变函数名相同时,无法区分函数。

(2)采用 C++ 编译器编译后的结果
结论 在 Linux 下,采用 G++ 编译完成后,函数名字的修饰发生改变编译器将函数参 数类型信息添加到修改后的名字中

 补充通过上面我们可以看出 GCC 的函数修饰后名字不变,而 G++ 的函数修饰后变成 <_Z + 函数长度 + 函数名 + 类型首字母>

以 Add 函数为例:

_Z 是 GCC 编译器的修饰前缀,表示这是一个 C++ 函数名。

3 是函数名的长度

Add 是函数名

ii / dd 是函数参数类型的首字母,如果是 int* i,那么就是 Pi。


(3)Windows 下名字的修饰规则 


【总结】 

通过这里就理解了 C 语言没办法支持重载,因为同名函数没办法区分。而 C++ 是通过函数修饰规则来区分只要参数不同,修饰出来的名字就不一样,就支持了重载。如果两个函数的函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分。


(4)extern "C" 

有时候在 C++ 工程中可能需要将某些 (部分) 函数按照 C 的风格来编译,在函数前加 extern “C”,意思是告诉编译器,将该函数按照 C 语言规则来编译。比如:tcmalloc 是 google 用 C++ 实现的一个项目,他提供 tcmallc() 和 tcfree两个接口来使用,但如果是 C 项目就没办法使用,那么他就使用 extern “C” 来解决。

extern "C" int Add(int left, int right);int main()
{Add(1,2);return 0; 
}
  1. C++ 项目可以调用 C++ 库,也可以调用 C 的库,C++ 是直接兼容 C 的。
  2. C 项目可以调用 C 库,也可以使用 extern "C" 调用 C++ 库C++ 提供的函数加上 extern "C")。

六、引用

1、引用概念

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

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

void TestRef()
{int a = 10;int& ra = a;printf("%p\n", &a);printf("%p\n", &ra);// 地址相同
}

注意引用类型必须和引用实体同种类型的。


2、引用特性

  1. 引用定义时必须初始化
  2. 一个变量可以有多个引用
  3. 引用一旦引用一个实体,就再不能引用其他的实体。
void TestRef()
{int a = 10;// int& ra; // 编译出错 - 没有对引用变量进行初始化// 可以有多个引用int& ra = a;int& rra = a;printf("%p %p %p\n", &a, &ra, &rra);
}

3、常引用

void TestConstRef()
{const int a = 10;//int& ra = a;   // 编译出错,a为常量const int& ra = a;//int& b = 10; // 编译出错,b为常量const int& b = 10;double d = 12.34;//int& rd = d; // 编译出错,类型不同const int& rd = d;
}

在 C++ 中,引用必须与其引用的对象具有相同的类型,或者可以通过隐式类型转换来匹配。

  1. 函数传参如果想减少拷贝使用引用传参,如果函数中不改变这个参数最好使用 const 引用传参。
  2. const 引用的好处是保护实参,避免被误改,且它可以传普通对象也可以传 const 对象。

4、使用场景

(1)做参数
void Swap(int& left, int& right)
{int temp = left;left = right;right = temp;
}

(2)做返回值
int& Count()
{static int n = 0;n++;// ...return n;
}
int& Add(int a, int b)
{int c = a + b;return c; // 函数结束后,c变量就没有意义了
}int main()
{int& ret = Add(1, 2); // 引用Add函数返回值Add(3, 4);cout << "Add(1, 2) is :" << ret << endl; // 7// 具体得看平台销毁栈帧时是否会清理栈帧空间,但是这种写法本身就是越界的,是错误的return 0;
}

  1. 函数运行时,系统需要给该函数开辟独立的栈空间,用来保存该函数的形参,局部变量以及一些寄存器信息等。
  2. 函数运行结束后,该函数对应的栈空间就被系统回收了。
  3. 空间被回收指该块栈空间暂时不能使用,但是内存本身还在。 
注意 :如果函数返回时,出了函数的作用域,如果返回对象还在(还没还给系统),则可以使用引用返回;如果已经还给系统了,则必须使用传值返回。

5、传值、传引用比较

(1)传值、传引用的效率比较
#include <time.h>struct A{int a[10000];
};void TestFunc1(A a)
{}
void TestFunc2(A& a)
{}
void TestRefAndValue()
{A a;// 1、以值作为函数参数size_t begin1 = clock();for (size_t i = 0; i < 10000; ++i){TestFunc1(a);}size_t end1 = clock();// 2、以引用作为函数参数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;
}

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。 


 (2)值和引用的作为返回值类型的性能比较
#include <time.h>struct A{int a[10000];
};A a;A TestFunc1() // 值返回
{return a;
}
A& TestFunc2() // 引用返回
{return a;
}void TestReturnByRefOrValue()
{// 1、以值作为函数的返回值类型size_t begin1 = clock();for (size_t i = 0; i < 100000; ++i){TestFunc1();}size_t end1 = clock();// 2、以引用作为函数的返回值类型size_t begin2 = clock();for (size_t i = 0; i < 100000; ++i){TestFunc2();}size_t end2 = clock();// 计算两个函数运算完成之后的时间cout << "TestFunc1 time:" << end1 - begin1 << endl;cout << "TestFunc2 time:" << end2 - begin2 << endl;
}

通过上述代码运行后的结果比较,发现传值和指针在作为传参以及返回值类型上效率相差很大。 


6、引用和指针的区别

语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
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;int* pa = &a;*pa = 20;return 0;
}
引用和指针的汇编代码对比:

【引用和指针的不同点】

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


七、内联函数 

1、概念

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

如果在上述函数前增加 inline 关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。

查看方式:
  1. 在 release 模式下,查看编译器生成的汇编代码中是否存在 call Add
  2. 在 debug 模式下,需要对编译器进行设置,否则不会展开(因为在 debug 模式下,编译器默认不会对代码进行优化,下面给出 VS2019 的设置方式)。


2、特性

  1. inline 是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
  2. inline 对于编译器而言只是一个建议,不同编译器关于 inline 实现机制可能不同,一般建 议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用 inline 修饰,否则编译器会忽略 inline 特性。下图为  《C++prime》 第五版关于 inline 的建议
  3. inline 不建议声明和定义分离,分离会导致链接错误。因为 inline 被展开,就没有函数地址了,链接就会找不到。
// test.h
#include <iostream>
using namespace std;inline void f(int i);// test.cpp
#include "test.h"void f(int i)
{cout << i << endl;
}// main.cpp
#include "test.h"int main()
{f(10);return 0;
}

链接错误:main.obj : error LNK2019: 无法解析的外部符号 "void __cdecl f(int)" (?f@@YAXH@Z),该符号在函数 _main 中被引用。


【回顾】宏的优缺点?

优点:
  1. 增强代码的复用性。
  2. 提高性能。
缺点:
  1. 不方便调试宏。(因为预编译阶段进行了替换)
  2. 导致代码可读性差,可维护性差,容易误用。
  3. 没有类型安全的检查 。
C++ 有哪些技术替代宏?
  1. 常量定义换用 const enum 。
  2. 短小函数定义换用内联函数。

八、auto关键字(C++11)

1、类型别名思考

由于接触到的程序愈变复杂,导致类型难于拼写含义不明确导致容易出错

#include <string>
#include <map>int main()
{std::map<std::string, std::string> m{ { "apple", "苹果" }, { "orange", "橙子" }, {"pear","梨"} };std::map<std::string, std::string>::iterator it = m.begin();while (it != m.end()){//....}return 0;
}

std::map<std::string, std::string>::iterator 是一个类型,但是该类型太长了,很容易写错,我们可以通过 typedef 给类型取别名,比如:

#include <string>
#include <map>typedef std::map<std::string, std::string> Map;int main()
{Map m{ { "apple", "苹果" },{ "orange", "橙子" }, {"pear","梨"} };Map::iterator it = m.begin();while (it != m.end()){//....}return 0;
}

使用 typedef 给类型取别名确实可以简化代码,但是 typedef 有会遇到新的难题:

typedef char* pstring;int main()
{const pstring p1;const pstring* p2;return 0;
}
编译结果失败。在上述代码中,pstring 被定义为指向 char 类型的指针,而 const pstring p1; 则表示 p1 是一个常量指针,指向的值不能更改。然而,在定义常量指针时,必须给它进行初始化,而这里没有对 p1 进行初始化,因此会导致编译失败。同样地,const pstring* p2; 表示 p2 是一个指向常量指针的指针,同样需要进行初始化。编译器会报错提示缺少初始化。
在编程时,常常需要把表达式的值赋值给变量,这就要求在声明变量的时候清楚地知道表达式的
类型。

2、auto简介

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

因为在 C / C++ 中,如果没有使用任何存储类别关键字(如 auto、static、extern 等),则变量默认为自动存储类别。因此,使用 auto 关键字对于自动存储类别的变量来说没有实际意义。

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

int TestAuto()
{return 10;
}int main()
{int a = 10;auto b = a;auto c = 'a';auto d = TestAuto();cout << typeid(b).name() << endl; // intcout << typeid(c).name() << endl; // charcout << typeid(d).name() << endl; // int// auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化return 0;
}
注意 使用 auto 定义变量时必须对其进行初始化 ,在编译阶段编译器需要根据初始化表达式来推导 auto 的实际类型。因此 auto 并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在 编译 期会将 auto 替换为变量实际的类型

3、auto的使用细则

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

(2)在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错。因为 编译
器实际只对第一个类型进行推导 ,然后用推导出来的类型来定义其他的变量。
void TestAuto()
{auto a = 1, b = 2; // 编译成功auto c = 3, d = 4.0; // 编译失败,因为c和d的初始化表达式类型不同
}

(3)auto不能推导的场景
  1. auto 不能作为函数的参数
    void TestAuto(auto a)
    {}
    // 编译失败:auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
  2. auto 不能直接用来声明数组
    void TestAuto()
    {int a[] = {1, 2, 3};auto b[] = {4,5,6}; // error
    }
  3. 为了避免与 C++98 中的 auto 发生混淆,C++11 只保留了 auto 作为类型指示符的用法。
  4. auto 在实际中最常见的优势用法就是跟以后会讲到的 C++11 提供的新式 for 循环,还有在  lambda 表达式等进行配合使用。

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

1、范围for的语法

在 C++98 中如果要遍历一个数组,可以按照以下方式进行:

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 循环后的括号由冒号 ":" 分为两部分:第一部分是 范围内用于迭代的变量 ,第二部分则表示 被迭代的范围
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的使用条件

(1)for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于而言,应该提供 begin 和 end 的方法,begin 和 end 就是 for 循环迭代的范围
void TestFor(int array[])
{for(auto& e : array) // 该代码有问题,因为for的范围不确定{cout << e << endl;}
}

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

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

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 。

注意

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

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

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

相关文章

DVWA-impossible代码审计

本篇文章针对于DVWA靶场的impossible级别进行代码审计&#xff0c;基于DVWA的每一个漏洞的impossible级别&#xff0c;从整体思路上出发&#xff0c;进行代码审计&#xff0c;最后对其总结。从中可以学习到一些漏洞的防御手法&#xff0c;并且对漏洞有更深入的理解。 文章目录 …

led台灯哪个牌子最好?2022最新的台灯牌子排名

想要选好护眼台灯首先我们要知道什么是护眼台灯&#xff0c;大的方向来看&#xff0c;护眼台灯就是可以保护视力的台灯&#xff0c;深入些讲就是具备让灯发出接近自然光特性的光线&#xff0c;同时光线不会伤害人眼而出现造成眼部不适甚至是视力降低的照明设备。 从细节上看就…

Linux——补充点(页表映射及LWP)

目录 补充点1&#xff1a;进程地址空间堆区管理 补充点2&#xff1a;Linux内核进程上下文切换 补充点3&#xff1a;页表映射 补充点4&#xff1a;两级页表 补充点1&#xff1a;进程地址空间堆区管理 Linux内核通过一个被称为进程描述符的task_struct结构体来管理进程&#…

【Java 进阶篇】MySQL多表查询之外连接详解

在MySQL数据库中&#xff0c;多表查询是一种常见且强大的功能&#xff0c;允许您在多个表之间执行联接操作&#xff0c;从而检索、过滤和组合数据。在本篇博客中&#xff0c;我们将深入探讨多表查询的一种类型&#xff0c;即外连接&#xff08;Outer Join&#xff09;&#xff…

医疗小程序开发:技术门槛高?

随着移动互联网的普及&#xff0c;医疗行业也逐渐转向线上。医疗小程序开发成为了很多企业和医疗机构关注的焦点。但是&#xff0c;对于一些技术小白来说&#xff0c;可能会觉得医疗小程序开发技术门槛高&#xff0c;无从下手。实际上&#xff0c;使用乔拓云平台进入后台&#…

C理解(一):内存与位操作

本文主要探讨C语言的内存和为操作操作相关知识。 冯诺依曼结构和哈佛结构 冯诺依曼结构&#xff1a;数据和代码放在一起,便于读取和修改,安全性低 哈佛结构是&#xff1a;数据和代码分开存放,安全性高,读取和修麻烦 内存 内存是用来存储全局变量、局…

Prometheus+Grafana监控K8S集群(基于K8S环境部署)

文章目录 一、环境信息二、部署前准备工作三、部署Prometheus监控系统四、部署Node_exporter组件五、部署Kube_state_metrics组件六、部署Grafana可视化平台七、Grafana可视化显示Prometheus收集数据八、Grafana添加监控模板九、拓展 一、环境信息 1、服务器及K8S版本信息&…

Nim游戏

891. Nim游戏 - AcWing题库 全部异或起来&#xff0c;如果不为零&#xff0c;则可以一步使其变为0&#xff1a; 设异或和为x&#xff0c;x的最高位为第k位&#xff0c;令第k位为1的a[i]变为a[i]^x&#xff0c;a[i]^x < a[i]&#xff0c;这样就可以使异或和为0。 如此往复最…

目标检测如何演变:从区域提议和 Haar 级联到零样本技术

目录 一、说明 二、目标检测路线图 2.1 路线图&#xff08;一般&#xff09; 2.2 路线图&#xff08;更传统的方法&#xff09; 2.3 路线图&#xff08;深度学习方法&#xff09; 2.4 对象检测指标的改进 三、传统检测方法 3.1 维奥拉-琼斯探测器 (2001) 3.2 HOG探测器…

el-table实现穿梭功能

第一种 <template><el-row :gutter"20"><el-col :span"10"><!-- 搜索 --><div class"search-bg"><YcSearchInput title"手机号" v-model"search.phone" /><div class"search-s…

EasyX趣味化编程note2,绘制基本图形

创意化编程&#xff0c;让编程更有趣 今天介绍的仍为比较简单的效果&#xff0c;由浅入深来进行学习 介绍每个函数都会附上代码和运行结果&#xff0c;感兴趣的大家可以复制粘贴运行一下看看效果&#xff0c;也可以自己进行改动&#xff0c;非常好玩且加深印象。 上节课的知识…

idea Springboot在线商城系统VS开发mysql数据库web结构java编程计算机网页源码maven项目

一、源码特点 springboot 在线商城系统是一套完善的信息系统&#xff0c;结合springboot框架和bootstrap完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用springboot框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有 完整的源代码和数据库&…

机器学习笔记:Huber Loss smooth L1 loss

1 Huber loss 1.1 介绍 Huber Loss是回归问题中的一种损失函数&#xff0c;它结合了均方误差MSE和绝对误差MAE的特点。 Huber Loss在误差较小的时候是平方损失&#xff0c;而在误差较大的时候是线性损失。因此&#xff0c;它在处理有噪声的数据时&#xff0c;尤其是存在离群点…

手机搜狗输入法,输入拼音时如何分割拼音,调出“分词“功能,如何微信或QQ使用发送按钮而不是换行?

背景 有时候打字&#xff0c;输入 “xian” 的时候我们的意图是 “xi’an” &#xff08;西安&#xff09;&#xff0c;或者输入 “yue” 的时候希望是 “yu’e”&#xff08;余额&#xff09; 如何输入这个分隔符 ’ 呢&#xff1f; 设置方法 默认页面如图 希望设置成 点…

家电行业 EDI:Miele EDI 需求分析

Miele是一家创立于1899年的德国公司&#xff0c;以其卓越的工程技术和不懈的创新精神而闻名于世。作为全球领先的家电制造商&#xff0c;Miele的经营范围覆盖了厨房、洗衣和清洁领域&#xff0c;致力于提供高品质、可持续和智能化的家电产品。公司的使命是为全球消费者创造更美…

【Java 进阶篇】深入理解 SQL 聚合函数

在 SQL 数据库中&#xff0c;聚合函数是一组强大的工具&#xff0c;用于处理和分析数据。它们可以帮助您对数据进行统计、计算总和、平均值、最大值、最小值等操作。无论您是数据库开发者、数据分析师还是希望更好地了解 SQL 数据库的用户&#xff0c;了解聚合函数都是非常重要…

CSS详细基础(四)显示模式

本帖开始介绍CSS中更复杂的内容 目录 一.显示模式 1.行内元素 2.块级元素 3.行内块元素 二.背景样式 一.显示模式 顾名思义&#xff0c;在CSS中&#xff0c;元素主要有3种显示模式&#xff1a;行内元素、块级元素、行内块元素~ 所谓块级元素&#xff0c;指的是该元素在…

ChatGPT AIGC 非常实用的AI工具集合大全

实战AI 工具箱 AIGC ChatGPT 职场案例60集, Power BI 商业智能 68集, 数据库Mysql8.0 54集 数据库Oracle21C 142集, Office, Python ,ETL Excel 2021 实操,函数,图表,大屏可视化 案例实战 http://t.csdn.cn/zBytu

GD32工程创建

1.创建空工程 在任意路径下创建空的test文件夹。打开keil5空工程创建空工程 选择对应的芯片型号&#xff1a; 然后把空工程保存到test文件夹下。会自动生成如下文件。 2. 添加组 下载GD32F10X的固件库&#xff1a;在百度里搜索GD32进入官网。 下载下来对应的文件如下&#xff…

MYSQL常用命令

一.数据类型 MySQL中有多种数据类型&#xff0c;每种类型用于存储不同类型的数据。以下是MySQL中常见的数据类型&#xff1a; 数值类型&#xff1a; INT&#xff1a;整数类型&#xff0c;存储范围为-2,147,483,648到2,147,483,647。BIGINT&#xff1a;大整数类型&#xff0c;存…