目录
1、前言
什么是C++
2、关键字
3、命名空间
C语言命名冲突
命名空间定义
命名空间使用
4、C++输入&输出
5、缺省参数
缺省参数概念
缺省参数分类
全缺省参数
半缺省参数
缺省参数意义
缺省参数注意点
既博主学过C语言后又一新的语言,C++。总算既C语言 -- > 数据结构后又一新专栏专属于C plus plus。相信博主会通过博客的方式将C++的各个知识点细化的讲解出来,一起见证~的成长
C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机, 20世纪80年代, 计算机界提出了OOP思想,支持面向对象的程序设计语言应运而生。
1982年,Bjarne Stroustrup博士在C语言的基础上引入并扩充了面向对象的概念,发明了一种新的程序语言。为了表达该语言与C语言的渊源关系,命名为C++。因此:C++是基于C语言而产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计。
正文开始
C++总计63个关键字,C语言32个关键字,先看个总览,具体每一个对应的是什么在后面的博文中会进行剖析
asm do if return try continue auto double inline short typedef for bool dynamic_cast int signed typeid public break else long sizeof typename throw case enum mutable static union wchar_t catch explicit namespace static_cast unsigned default char export new struct using friend class extern operator switch virtual register const false private template void true const_cast float protected this volatile while delete goto reinterpret_cast
- 可能诸位在看别人写的C++代码中,在一开始会包这个头文件:
#include
这个头文件等价于我们在C语言学习到的#include
,它是用来跟我们的控制台输入和输出的,这里简要提下,后续详谈。
- 除了上面这个头文件,还有这样一行代码:
using namespace std;
namespace就是我们要接触C++的第一个关键字,它就是命名空间
- 作用如下:
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
在正式引入namespace前,再来回顾下C语言的命名冲突问题:
#include
//命名冲突 int rand = 0; int main() { printf("%d", rand); return 0; } 如上的代码中,头文件我们只用了intclude
,暂无其它。我们定义了全局变量rand,并且代码可以正常编译没有任何错误。 但是要知道C语言存在一个库函数正是rand,但是要包上头文件#include
,包上了这个头文件,再运行试试: 这里很明显发生命名冲突了,我们定义的全局变量rand和库里的rand函数冲突
想要解决此问题也非常简单,可能有人会说我修改变量名就可以了,确实可以,但并不是长久之计,如若我在不知情的状态下使用该变量超过100次,难道你要一个一个修改吗,这就充分体现了C语言的命名冲突
- 在C++中,引入的命名空间namespace就很好解决了C语言的命名冲突问题。
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{ }即可,{}中即为命名空间的成员。
如下:
同一个作用域不能出现两个相同变量,此时的rand被关在n1的命名空间域里了,跟其它东西进行了隔离。所以在stdlib.h头文件展开时并不会发生命名冲突。此时rand的打印均是库函数里rand的地址,rand就是一个函数指针,打印的就是地址
再比如:
此段代码更充分的体现了加上命名空间,不仅可以避免命名冲突,而且还告诉我们,此时再访问变量m、c、f,均是在全局域里访问的,而xzy这个命名空间域里的变量与全局域建立了一道围墙,互不干扰。不过这里c和m依旧是全局变量,命名空间不影响生命周期。
命名空间有三大特性:
- 1、命名空间可以定义变量,函数,类型
//1. 普通的命名空间 namespace N1 // N1为命名空间的名称 { // 命名空间中的内容,既可以定义变量,也可以定义函数,也可以定义类型 int a; //变量 int Add(int left, int right) //函数 { return left + right; } struct ListNode //类型 { int val; struct ListNode* next; } }
- 2、命名空间可以嵌套
//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、同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中
//3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。 namespace N1 { int Mul(int left, int right) { return left * right; } }
我们都清楚在C语言中,存在局部优先规则,如下:
int a = 0; //全局域 int main() { int a = 1; //局部域 printf("%d\n", a); // 1 局部优先 return 0; }
我们都清楚这里的结果是1,但是如若我非要打印全局域里的a呢?
这里引出域作用限定符 ( :: ),效果如下:
加上了( :: ) ,此时访问的a,就是全局域,这里是全局域的原因是“ ::”的前面是空白,如若是空白,那么访问的就是全局域,这么看的话,我在“ ::”前面换成命名空间域,不就可以访问命名空间域里的内容了吗。其实“ ::”就是命名空间使用的一种方式。
比如我们定义了如下的命名空间:
namespace n1 { int f = 0; int rand = 0; }
现在该如何访问命名空间域里的内容呢?其实有3种方法:
- 加命名空间名称及作用域限定符“ ::”
- 使用using namespace 命名空间名称全部展开
- 使用using将命名空间中成员部分展开
- 1、加命名空间及作用域限定符“ ::”
int main() { printf("%d\n", n1::f); //0 printf("%d\n", n1::rand);//0 return 0; }
为了防止定义相同的变量或类型,我们可以定义多个命名空间来避免
namespace ret { struct ListNode { int val; struct ListNode* next; }; } namespace tmp { struct ListNode { int val; struct ListNode* next; }; struct QueueNode { int val; struct QueueNode* next; }; }
当我们要使用它们时,如下:
int main() { struct ret::ListNode* n1 = NULL; struct tmp::ListNode* n2 = NULL; return 0; }
针对命名空间的嵌套,如下:
可以这样进行访问:
int main() { struct tx::List::Node* n1; //访问List.h文件中的Node struct tx::Queue::Node* n2;//访问Queue.h文件中的Node }
但是上述访问的方式有点过于麻烦,可不可以省略些重复的呢?比如不写tx::,这里就引出命名空间访问的第二种方法:
- 2、使用using namespace 命名空间名称全部展开
using namespace tx;
这句话的意思是把tx这个命名空间定义的东西放出来,所以我们就可这样访问:
int main() { struct List::Node* n1; //访问List.h文件中的Node struct Queue::Node* n2;//访问Queue.h文件中的Node }
当然,我还可以再拆一层,如下:
using namespace tx; using namespace List; int main() { struct Node* n1; //访问List.h文件中的Node struct Queue::Node* n2;//访问Queue.h文件中的Node }
展开时要注意tx和List的顺序不能颠倒
这种访问方式是可以达到简化效果,但是也会存在一定风险:命名空间全部释放又重新回到命名冲突。
所以针对某些特定会出现命名冲突问题的,需要单独讨论:
由此我们得知:全部展开并不好,我们需要按需索取,用什么展开什么,由此引出第三种使用方法
- 3、使用using将命名空间中成员展开
针对上述代码,我们只放f出来
namespace n1 { int f = 0; int rand = 0; } using n1::f; int main() { f += 2; printf("%d\n", f); n1::rand += 2; printf("%d\n", n1::rand); }
- 学到这,我们来看下C++的标准库命名空间:
#include
using namespace std; //std 是封C++库的命名空间 int main() { cout << "hello world" << endl; // hello world return 0; } 如若省去了这行代码:
using namespace std;
想要输出hello world就要这样做:
#include
int main() { std::cout << "hello world" << std::endl; return 0; } 当然也可以这样:
#include
using std::cout; int main() { cout << "hello world" << std::endl; return 0; } 这就充分运用到了命名空间,至于为什么会这样相信不需要我解释大家就能悟出来哈。
C语言中,我们都清楚输入用scanf,输出用printf,可是在C++中,我们同样可以用C语言的,不过C++也独有一套输入cin输出cout。
- 说明:
使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含< iostream >头文件以及std标准命名空间。
注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持格式,后续编译器已不支持,因此推荐使用 +std的方式。 // >> 流提取运算符 cin >> a; // << 流插入运算符 cout << a;
C++里的输入输出流可以自动识别类型,不需要像C语言一样不需增加数据格式控制,比如:整形--%d,字符--%c
注意:endl是换行符,等价于C语言的 ' \n '。
最后,我们依旧是以经典的hello world结束C++的输入输出
#include
using namespace std; int main() { cout << "hello world" << endl; }
缺省参数(默认参数)是声明或定义函数时为函数的参数指定的一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。
//缺省参数 / 默认参数 void TestFunc(int a = 0) { cout << a << endl; } int main() { TestFunc(); // 0 没有传参时,使用参数的默认值 TestFunc(10); // 10 传参时,使用指定的实参 }
根据上述代码,我们得知,如若你不传参数,那我就用缺省参数,函数默认的参数值,这里是0。如若你传了参数,那就用你自己的。
像极了渣男渣女行为哈哈。我今儿个有新欢,就没你这个备胎啥事了,我若和对象吵架,来来来,备胎就位哈哈,(快来@你身边的~哈哈哈)你若安好,备胎到老。
缺省参数分为两类:
- 全缺省
- 半缺省
好家伙备胎怪多的哈哈~
直接看代码:
void TestFunc(int a = 10, int b = 20, int c = 30) { cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl << endl; }
根据这段代码,我们这个缺省参数有3个,那么我在调用函数的时候,就有4种调用方式:(乖乖更渣)
int main() { TestFunc(); //全用默认的 TestFunc(1); //a用自己的,b和c用默认的 TestFunc(1, 2);//a和b用自己的,c用默认的 TestFunc(1, 2, 3);//全用自己的 return 0; }
注意:在传参数的时候要按照顺序来传,不能说我第一个还没传就先传第二个,如下:
//错误:TestFunc( ,1, ) //err
半缺省参数听名字就应该清楚,无非是备胎少了一些呗。
//版缺省:缺省部分 void TestFunc(int a, int b = 20, int c = 30) { cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl << endl; }
上述代码就已经很明确了,我在调用函数时,传的参数至少是一个,有以下三种调用方式:
int main() { TestFunc(1); //全用默认的 TestFunc(1, 2);//只有c用默认的 TestFunc(1, 2, 3);//全用自己的 return 0; }
注意:半缺省参数必须从右往左依次给出默认参数,不能间隔着给
//错误 void TestFunc(int a = 10, int b, int c = 30) //err { //…… 错误 }
我们以栈为例:
struct Stack { int* a; int size; int capacity; }; //以前: void StackInit(struct Stack* ps) { //……} //现在:缺省参数版 void StackInit(struct Stack* ps, int n = 4) { assert(ps); ps->a = (int*)malloc(sizeof(int) * n); ps->size = 0; ps->capacity = n; }
现在我们在初始化栈的时候可以直接使用缺省参数,在调用时,就可以这样:
int main() { Stack st; StackInit(&st); StackInit(&st, 100); return 0; }
如若我初始化时不给值,它自动就帮我开辟了4个int大小的空间,如若我嫌空间小,手动传一个自己想要的空间大小,有了缺省参数可以有效避免多次增容而带来的性能损耗。
到底用全缺省还是半缺省因题而定。
- 缺省参数不能在函数声明和定义中同时出现。
假设我们在Queue.h文件中声明如下:
在Queue.cpp文件中定义了此函数
接下来我们在test.cpp包上头文件后编译:
很明显编译错误。
- 那我们声明给缺省参数,定义不给呢?
答案很明显,可以
- 如果声明不给定义给呢?
这里我就不给出图示了,非常简单,感兴趣的下来可以自己去尝试一番,这里直接给出答案:不行
综上,总结出三大结论:
- 缺省参数不能在声明和定义中同时出现,防止出现不同的赋值导致奇异
- 缺省参数不能声明不给定义给,会出现链接错误
- 缺省参数可以声明给定义不给