Hello大家中秋节快乐,不知道大家有没有吃月饼,反正小编事没吃到,今天我们继续分享的是C++入门知识,这样才能让我们在后面学习C++铺垫,我们今天就继续之前的内容进行讲解。
命名空间
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存
在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,
以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的.
那我们应该在什么场景使用呢,可以是我们有两个相同的结构体,也可以是我们有相同的变量的时候,这个时候我们就可以加上命名空间namespce,但是我们使用的时候也需要注意写法,比如我们定义命名空间一个namespace A 那我们相当于要从这个命名空间里去提取,那我们用的时候就得加上::这个里操作符来提取的,这个是C++的操作符,不是C语言中的,所以我们的源文件必须写成.cpp后缀,要不然会出现编译问题。
命名空间可以嵌套
namespace bit
{
// 命名空间中可以定义变量/函数/类型
int rand = 10;
int Add(int left, int right)
{
return left + right;
}
/*注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中
2.2 命名空间使用
命名空间中成员该如何使用呢?比如:*/
struct Node
{
struct Node* next;
int val;
};
}
//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;
}
}
}
//3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
// ps:一个工程中的test.h和上面test.cpp中两个N1会被合并成一个
// test.h
namespace N1
{
int Mul(int left, int right)
{
return left * right;
}
}
有时候我们写项目的时候就拿我们要写一个加法的计算器,但是我们的加法函数我们都叫ADD,但是我们如果可以遇到int 和 double类型呢(虽然后面的函数重载会解决)所有同一个函数有相同名字,我们也可以这样进行解决。
2.2 命名空间使用
那我们这里给几个例子给大家看看是怎么使用的吧
namespace N
{
// 命名空间中可以定义变量/函数/类型
int a = 0;
int b = 1;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
int main()
{
// 编译报错:error C2065: “a”: 未声明的标识符
printf("%d\n", a);
return 0;
}
那我们归纳成部分展开和全局展开,还有指定展开
加命名空间名称及作用域限定符
int main()
{
printf("%d\n", N::a);
return 0;
}
这是指定展开,因为命名空间是N,那就是打印N中的a
使用using将命名空间中某个成员引入
我们在一开始的时候加上using就是全局展开,就好比我们一般情况写cout这些都会在前面加上using namespace std这句指令,这两个是相同的道理。
using namespce N;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
Add(10, 20);
return 0;
}
还有就是部分展开,选择我们经常用的加上using
using N::b;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
return 0;
}
比如我们的打印经常用就可以写成using std::cout
这和上面是一样的道理
- C++输入&输出
在C语言中我们用printf 和scanf 那我们的C++也有一样的输入输出,让我们来看看吧
#include<iostream>
// std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
using namespace std;
int main()
{
cout<<"Hello world!!!"<<endl;
return 0;
}
#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;
}
C++的输入输出和我们的C语言有些不一样,C语言需要输入类型,C++祖师爷觉得太麻烦了,C++可以自动识别类型。这提供一些便利,但是我们C++兼容C语言,所以我们有些场景下可以继续使用C语言,两个一起使用。
那我们对上面的代码进行一个说明吧
说明:
- 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件
以及按命名空间使用方法使用std。 - cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含<
iostream >头文件中。 - <<是流插入运算符,>>是流提取运算符。
- 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。
C++的输入输出可以自动识别变量类型。 - 实际上cout和cin分别是ostream和istream类型的对象,>>和<<也涉及运算符重载等知识,
这些知识我们我们后续才会学习,所以我们这里只是简单学习他们的使用。后面我们还有有
一个章节更深入的学习IO流用法及原理。
注意:早期标准库将所有功能在全局域中实现,声明在.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;
}
<<这个就相当于我们的从流中输出数据,这个>>就是从流中输入数据和scanf一样
std命名空间的使用惯例:
std是C++标准库的命名空间,如何展开std使用更合理呢?
- 在日常练习中,建议直接using namespace std即可,这样就很方便。
- using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对
象/函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模
大,就很容易出现。所以建议在项目开发中使用,像std::cout这样使用时指定命名空间 +
using std::cout展开常用的库对象/类型等方式。
我们可以全局展开,也可以把常用的写上,取决与程序员自己。
4. 缺省参数
4.1 缺省参数概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实
参则采用该形参的缺省值,否则使用指定的实参。
void Func(int a = 0)
{
cout<<a<<endl;
}
int main()
{
Func(); // 没有传参时,使用参数的默认值
Func(10); // 传参时,使用指定的实参
return 0;
}
说的难听点,缺省参数我们就可以把它当成生活中的小编(舔狗),为什么这样说呢,就是如果我们函数参数传参的时候没有给定值,但是函数的定义当中我们给上的话,输出的就是函数定义中的值,这就好比我们的女神今天突然想看电影,但是电影票有点贵,相当于我们这里的变量a,这个时候女神就叫上舔狗(a),让他来买电影票,但是如果是我们代码的第二种情况,就是女神自己有钱买电影票,他也不想和你去看,觉得你太烦了,看电影还影响他的心情,所以就算你给值,我们的输出结果就是主函数里的10,这里告诉我们,身边处处有舔狗。
我们缺省参数有全部缺省,还有半缺省
void Func(int a = 10, int b = 20, int c = 30)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
上面的代码就是全缺省,我们在主函数里传参的时候就可以写成Func(),写成这样就行
void Func(int a, int b = 10, int c = 20)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
半缺省,相当于我们有两个舔狗,主函数传参数就要写成Func(1)这种,括号是个整型就行,这里只是举例子。
- 半缺省参数必须从右往左依次来给出,不能间隔着给
- 缺省参数不能在函数声明和定义中同时出现
这里的意思就是缺省值得放在定义中。
- 缺省值必须是常量或者全局变量
5. 函数重载
自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重
载了。
比如:以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个
是男足。前者是“谁也赢不了!”,后者是“谁也赢不了“。
5.1 函数重载概念
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这
些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型
不同的问题
在C语言中我们有时候会出现相同的函数名,但是我们取名字是个很麻烦的过程,因为我们的名字都有它自己的含义,除非加上注释,但是还是有问题,我们在后面做项目的时候会遇到一些问题,当我们这个项目是由两个人一起完成,难免会有时候一些函数会出现相同的情况,所以我们的祖师爷发明函数重载这个好东西
#include<iostream>
using namespace std;
// 1、参数类型不同
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、参数类型顺序不同
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;
}
那为什么能支持重载,我们都知道函数会建立一个函数栈帧,这个就是操作系统开辟的空间给我们使用,在汇编的时候有一个call的指令会跳到这个函数的地方,我们函数会给一个地址给我们call,不同的编译器定义函数的修饰规则不同,反正就是编译器会加上一点修饰,让我们的函数名变的与众不同一点,这样我们在调用的时候就会出现不同的地址
5.2 C++支持函数重载的原理–名字修饰(name Mangling)
为什么C++支持函数重载,而C语言不支持函数重载呢?
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
- 实际项目通常是由多个头文件和多个源文件构成,而通过C语言阶段学习的编译链接,我们
可以知道,【当前a.cpp中调用了b.cpp中定义的Add函数时】,编译后链接前,a.o的目标
文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么
怎么办呢? - 所以链接阶段就是专门处理这种问题,链接器看到a.o调用Add,但是没有Add的地址,就
会到b.o的符号表中找Add的地址,然后链接到一起。(老师要带同学们回顾一下) - 那么链接时,面对Add函数,链接接器会使用哪个名字去找呢?这里每个编译器都有自己的
函数名修饰规则。 - 由于Windows下vs的修饰规则过于复杂,而Linux下g++的修饰规则简单易懂,下面我们使
用了g++演示了这个修饰后的名字。 - 通过下面我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰后变成【_Z+函数长度+函数名+类型首字母】
这是在linux下我们的函数名经过修饰后的样子。
6. 通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修
饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
7. 如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办
法区分。
VS下的修饰过于麻烦,小编也不会 linux下就是前面有个前缀,然后加上函数名的长度加上函数名和它的类型,所以我们函数重载的条件也出来了,函数参数不同,类型不同,顺序不同,但是这里大家一定要注意的是我们可没说返回值。。。
- 引用
6.1 引用概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空
间,它和它引用的变量共用同一块内存空间。
比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。
引用的概念我们现在其实只要把它当成用的是同一个内存空间就可以了。我们来举例子说明把
先看我们的怎么用
类型& 引用变量名(对象名) = 引用实体;
int i = 0;
int& a = i;
a++;
这里a++就是i++的意思
void TestRef()
{
int a = 10;
int& ra = a;//<====定义引用类型
printf("%p\n", &a);
printf("%p\n", &ra);
}
这里我们打印出来的地址是一样的。
注意:引用类型必须和引用实体是同种类型的
如果一个int类型的我们用double来就存在问题了
6.2 引用特性
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
void TestRef()
{
int a = 10;
// int& ra; // 该条语句编译时会出错
int& ra = a;
int& rra = a;
printf("%p %p %p\n", &a, &ra, &rra);
}
相当于我们经常说的初始化,但是这里呢不是这个意思就是我们需要一个实体,我们C语言的指针需要初始化,那这里的引用也需要指向一个实体
那我们在来看几个例子
在这里的编译结果就是我们因为用的同一块空间,所以每次加1,就是a+1,结果也就是3.
那我们再来看看之前的swap函数,我们那个时候写的时候是传地址,原因就是我们形参只是实参的一份临时拷贝,改变形参并不能改变实参,所以要传地址过去,那因为我们引用时可以用同一块空间(引用的本质其实也是指针,我后面会讲,但是我们这里需要了解的就是当他们时用同一快空间的就行了)
那我们两个交换都写一吧
#include<iostream>
using namespace std;
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
int main()
{
int a = 10;
int b = 20;
cout << "交换前" << a << " " << b << endl;
Swap(&a, &b);
cout << "交换后" << a << " " << b << endl;
return 0;
}
接下来我们就用引用的方式写一个代码吧
#include<iostream>
using namespace std;
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int a = 10;
int b = 20;
cout << "交换前" << a << " " << b << endl;
Swap(a, b);
cout << "交换后" << a << " " << b << endl;
return 0;
}
可以看到我们用引用的方法也是实现了交换
我们可以看到交换的时候更加方便,不用再继续用指针来进行交换,但是还是要强调一个东西,那就是引用的本质其实就是指针,只是编译器的实现方便我们解决这些问题了。
不知道大家还记不记得我们之前讲过的链表,那个时候我们可能被二级指针搞的对我们的代码不是特别理解,但是有了引用可能就会变得好一点,举个例子,就拿我们认识的单链表来说,我们来看一下之前写的链表是怎样的吧
#include<stdio.h>
typedef int SLTDataType;
typedef struct ListNode
{
struct ListNode* next;
SLTDataType data;
}SLTNode,* PLTNode;//这里表示的就是typedef struct ListNode* PLTNode;这句代码
void PushBack(PLTNode& Phead, int x)
{
}
int main()
{
SLTNode* plist = NULL;
PushBack(plist, 1);
PushBack(plist, 1);
PushBack(plist, 1);
PushBack(plist, 1);
return 0;
}
就是相当于我们之前的代码就可以写成这个样子,这里就是一个二级指针的方式,本来我们改变一级指针的内容,需要二级指针,因为引用就是取别名,所以就解决了我们这个问题。后面我们还是会继续讲的。
6.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;
}
这是什么原因呢,我们要知道一个前提,那就是我们的权限是只能缩小或者相等,不能放大的。
const就让我们的变量具有常性,还有就是大家需要记得临时变量也是具有常性的。
我们来看几个关于权限的例子吧
int main()
{
int a = 0;
int& ra = a;
//权限没有发生变化
const int b = 10;
//int& rb = b;
//这样就是错的,我们权限进行放大了
const int& rb = b;
//这样权限就不变了
return 0;
}
但是大家一定要记住的是我们这个权限的方法只能用到引用和指针上,我们平常的赋值整型这些是不要考虑这些的。
6.4 使用场景
我们的引用肯定是有一定的使用场景的,虽然说C++很好,但是我们还是兼容C语言的,原因就是有些地方我们用C语言的看起来更让人通俗易懂,我们来看看吧
- 做参数
第一种就是做参数,可以看到我们刚刚写的链表和交换函数就可以这样用,我就不在演示了,大家只要记住我们现阶段只要了解引用就是取别名的意思就okl
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
- 做返回值
还有一个重要的地方就是做返回值。
之前在函数栈帧的时候讲过我们的函数返回的时候要把返回值给我们寄存器,然后销毁函数栈帧,记得不是摧毁这个空间,不是灭霸打响指的事,而这里我们用引用返回就是返回这个空间。不是返回它的临时拷贝。
int& Count()
{
static int n = 0;
n++;
// ...
return n;
}
看到代码static修饰表示它是一个静态区的,出栈并不会销毁,看到我们返回用了引用,表示返回的是n的别名,就是n,但是我们有没有想过一个问题,如果n不是static这样些还对吗,答案是错误的,因为我们出栈这块空间就返回操作系统,那这样写就是有问题了。这个值我们还是原因的值了,答案是不确定,取决与编译器。
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;
}
看这段代码就行了,这样写就是错误的,原因很简单,首先我们返回的是函数栈帧的内容,然后返回引用,相当于返回这个n,但是n出栈销毁,ret又是别名,最后导致我们访问的就是一个随机数。
上面这个大家可以理解一下,或者直接看那篇关于函数栈帧的文章。
6.5 传值、传引用效率比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直
接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效
率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低
#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;
}
我们可以用这个代码来进行测试。。
发现传值调用最慢
#include <time.h>
struct A{ int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a;}
// 引用返回
A& TestFunc2(){ return a;}
void TestReturnByRefOrValue()
{
// 以值作为函数的返回值类型
size_t begin1 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc1();
size_t end1 = clock();
// 以引用作为函数的返回值类型
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.6 引用和指针的区别
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间
在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
int main()
{
int a = 10;
int& ra = a;
ra = 20;
int* pa = &a;
*pa = 20;
return 0;
}
这就是我们指针和引用的有些区别。
7. 内联函数
我们之前在C语言中学过宏函数,宏函数的话的缺点就是不方便调试,宏会直接被代替,函数参数不是很安全,没有类型检查.
7.1 概念
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调
用建立栈帧的开销,内联函数提升程序运行的效率。
我们都知道普通的函数调用都会产生函数栈帧,产生函数栈帧就会使用空间,而内联函数的话就不会创建函数栈帧。普通函数调用的时候,我们会产生一个地址然后先call到那个地址在jump到函数里,内联函数就不会这样,而是直接展开,但是我们的声明和定义不能分开,因为我们是直接展开的,如果分开的话,我们在编译链接的时候就会产生错误,所以声明和定义要放一起。
7.2 特性
- inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会
用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运
行效率。 - inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建
议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不
是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。下图为
《C++prime》第五版关于inline的建议:
内联函数只是向编译器发出一个请求,但是编译器可以选择忽略这个请求。
这是什么意思呢,就是比如我们代码太多的话,原地展开就不太好,所以编译器这个·时候可以选择忽略。
- inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址
了,链接就会找不到。
.8 auto关键字(C++11)
8.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;
}
这些名字都是很长的定义类型,有模板的一些知识,这些我们先不讲,大家现在只要知道auto可以自动识别类型,那我们就可以像上面的代码一样直接这样写
这里我们其他的用法也先不多说,给大家分享一些特殊情况
- 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;
}
typeid().name是检查类型的
- 在同一行定义多个变量
void TestAuto()
{
auto a = 1, b = 2;
auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}
这样就不行,我们的代码必须是一样的类型才行
8.3 auto不能推导的场景
- auto不能作为函数的参数
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
- auto不能直接用来声明数组
void TestAuto()
{
int a[] = {1,2,3};
auto b[] = {4,5,6};
}
- 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
- auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有
lambda表达式等进行配合使用。
下面我们在讲一个东西,初始C++就结束了,讲这些主要是为了大家更好的理解后面的内容。
- 基于范围的for循环(C++11)
我们平常遍历数组都是要写一个for循环的语句,我们现在写成这样就行了。
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;
}
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for (auto e : array)
{
cout << e << " ";
}
}
但是我们函数调用的时候一定要注意一个事,那就是我们调用传过去的时候事地址传数组名的时候,这个时候我们可以用引用。。
10. 指针空值nullptr(C++11)
10.1 C++98中的指针空值在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化:
void TestPtr()
{
int* p1 = NULL;
int* p2 = 0;
// ……
}
NULL其实是一个宏
程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的
初衷相悖。
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器
默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void
*)0。
注意:
- 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入
的。 - 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
- 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
今天的分享就到这里,我们后面就是讲类和对象了