鼠鼠最近在学C++,那么好,俺来做笔记了!
目录
1.C++关键字(C++98)
2.命名空间
2.1.命名空间定义
2.2.命名空间的使用
3.C++的输入&&输出
4.缺省参数
4.1缺省参数概念
4.2.缺省参数的分类
5.函数重载
5.1.函数重载概念
5.2.C++支持函数重载的原理--名字修饰(name Mangling)
C++是在C的基础之上,容纳进去了面向对象编程思想,并增加了许多有用的库,以及编程范式 等。本博客介绍的知识点主要是补充C语言语法的不足,以及C++是如何对C语言设计不合理的地方进行优化的,比如:作用 域方面、IO方面、函数方面、指针方面、宏方面等。
1.C++关键字(C++98)
C++总计63个关键字,C语言32个关键字。
本图片是本鼠借鉴别人的劳动成果(其实本鼠的博客很多都是捏)!
本鼠是初学者,肯定介绍不出这些个关键字的所以然来,以后本鼠学习到的话会在后面的博客介绍的捏!
2.命名空间
我们知道在C语言中,在同一个域里面不能有重复的函数名,变量名。例如C语言:
#include <stdio.h>
#include <stdlib.h>
int rand = 10;
// C语言没办法解决类似这样的命名冲突问题,所以C++提出了namespace来解决
int main()
{printf("%d\n", rand);return 0;
}
// 编译后后报错:error C2365: “rand”: 重定义;以前的定义是“函数”
头文件<stdlib.h>中定义过rand是“函数”,并且“函数”rand定义在全局域当中,我们又定义了全局变量rand,导致rand重定义!
但是在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存 在于全局作用域中,可能会导致很多冲突。所以C++有了命名空间(C语言可没有)。使用命名空间的目的是对标识符的名称进行本地化, 以避免命名冲突或名字污染。namespace关键字的出现就是针对这种问题的。
2.1.命名空间定义
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{} 中即为命名空间的成员。
注意:以下代码如果写在.c文件中会报错!
1.正常的命名空间定义。
// HD是命名空间的名字,一般开发中是用项目名字做命名空间名
namespace HD
{// 命名空间的成员可以定义变量/函数/类型int rand = 10;int Add(int left, int right){return left + right;}struct LCD{int age;double weight;struct LCD* next;};
}
2. 命名空间可以嵌套。
namespace HD
{int rand = 10;int Add(int left, int right){return left + right;}//嵌套命名空间namespace LCD{int i = 100;struct LCD{int age;double weight;struct LCD* next;};}
}
3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
例如本鼠有一个工程,工程包含test.cpp和test.h。
在test.cpp中定义了:
namespace HD {int rand = 10;int Add(int left, int right){return left + right;}//嵌套命名空间namespace LCD{int i = 100;struct LCD{int age;double weight;struct LCD* next;};} }
在test.h中定义了:
namespace HD {typedef struct SListNode{int data; struct SListNode* next; }SListNode;char a[] = {'w','x','h','l','c','d'}; }
那么好,神奇的事发生了,一个工程中的test.h和test.cpp中两个HD会被合并成一个 。
再例如本鼠有一个工程,工程包含只test.cpp文件。
在test.cpp中定义了:
namespace HD {int rand = 10;int Add(int left, int right){return left + right;}//嵌套命名空间namespace LCD{int i = 100;struct LCD{int age;double weight;struct LCD* next;};} }namespace HD {typedef struct SListNode{int data; struct SListNode* next; }SListNode;char a[] = {'w','x','h','l','c','d'}; }
那么好,一个上面的两个HD也会被合并成一个 。
注意:
1.一个命名空间就定义了一个新的作用域(命名空间域使用using将命名空间中某个成员引入),命名空间中的所有内容都局限于该命名空间中。
2.局部域和全局域会影响生命周期和访问,而命名空间域只影响访问。如何影响访问?请看下面本鼠介绍的编译器的访问规则。
3.命名空间封装的成员生命周期与全局变量的生命周期是一样的,命名空间定义的变量都是全局变量,所以说命名空间域不影响生命周期。
2.2.命名空间的使用
如何使用命名空间的成员,例如:
#include<stdio.h>
namespace HD
{int rand = 10;int Add(int left, int right){return left + right;}//嵌套命名空间namespace LCD{int i = 100;struct LCD{int age;double weight;struct LCD* next;};}
}int main()
{//编译报错:error C2065: “rand”: 未声明的标识符printf("%d", rand);return 0;
}
这里为什么报错?
不得不提编译器的访问规则:
1.如果没有指定域。优先访问局部域,若没有访问到则访问全局域,若全局域还没有访问到,报错!
2.如果指定了域。直接去指定的域访问,若没有访问到,报错!
这里没有指定域,所以编译器先后去局部域和全局域搜索都搜索不到rand的定义,所以报错!
那么如何解决,有三种办法,请看:
1.加命名空间名称及作用域限定符::
#include<stdio.h>
namespace HD
{int rand = 10;int Add(int left, int right){return left + right;}//嵌套命名空间namespace LCD{int i = 100;struct LCD{int age;double weight;struct LCD* next;};}
}
int main()
{printf("%d\n", HD::rand);printf("%d\n", HD::LCD::i);return 0;
}
作用域限定符:: 就是指定域用的。如果作用域限定符::前面为空,那么默认指定访问全局域,如:
#include<stdio.h>
namespace HD
{int rand = 10;
}
int rand = 100;
int main()
{int rand = 1000;printf("%d\n", HD::rand);printf("%d\n", ::rand);printf("%d\n", rand);return 0;
}
第一个打印10,因为指定了域,所以直接去命名空间域HD搜索了!
第二个打印100,因为指定了域,所以直接去全局域搜索了!
第三个打印1000,因为没有指定域,所以优先访问局部域且访问到了rand。
2. 使用using将命名空间中某个成员引入
#include<stdio.h>
namespace HD
{int rand = 10;int Add(int left, int right){return left + right;}//嵌套命名空间namespace LCD{int i = 100;struct LCD{int age;double weight;struct LCD* next;};}
}
using HD::rand;
using HD::LCD::i;
int main()
{printf("%d\n", rand);printf("%d\n", i);return 0;
}
这个被引入的成员能在编译器访问全局域的时候被访问到!
3.使用using namespace 命名空间名称引入
#include<stdio.h>
namespace HD
{int rand = 10;int Add(int left, int right){return left + right;}//嵌套命名空间namespace LCD{int i = 100;struct LCD{int age;double weight;struct LCD* next;};}
}
using namespace HD;
using namespace HD::LCD;
int main()
{printf("%d\n", rand);printf("%d\n", i);return 0;
}
那么这个命名空间的全部成员都可以在编译器访问全局域的时候被访问到!
本鼠顺便提一嘴,这种方式慎用,因为这样就将命名空间成员全部引入了,那样子命名空间不就形同虚设了咩!!!控制不好很容易导致命名冲突!
介绍完了三种命名空间的使用方法,本鼠考考读者老爷们,下面代码运行结果是什么:
#include<stdio.h>
namespace HD
{int rand = 10;int Add(int left, int right){return left + right;}//嵌套命名空间namespace LCD{int i = 100;struct LCD{int age;double weight;struct LCD* next;};}
}
using namespace HD;
int main()
{int rand=100;printf("%d\n", rand);return 0;
}
本鼠认为结果是打印100。因为即使使用using namespace HD引入,将命名空间HD的成员都引入了。但编译器对于没有指定域的话,优先访问局部域,那么局部域有变量rand的定义当然打印100了。
如果访问局部域没有找到rand的定义,那么编译器会访问全局域。由于使用了using namespace HD引入,所以编译器在访问全局域的时候能访问到命名空间HD的成员rand。
#include<stdio.h>
namespace HD
{int rand = 10;int Add(int left, int right){return left + right;}//嵌套命名空间namespace LCD{int i = 100;struct LCD{int age;double weight;struct LCD* next;};}
}
using namespace HD;
int main()
{int RAND=100;printf("%d\n", rand);return 0;
}
3.C++的输入&&输出
本鼠写一个C++版本的Hello world!来讲解:
#include<iostream>
// std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
using namespace std;
int main()
{cout << "Hello world!" << endl;return 0;
}
要看懂这个代码,要了解:
1.std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中。
2.cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含< iostream >头文件中。
3.cout标准输出对象(控制台)可以笼统认为等于C语言的printf,cin标准输入对象(键盘)可以笼统认为等于C语言的scanf,endl可以笼统认为等于C语言的换行符"\n"。
4.使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件以及按命名空间使用方法使用std。
5.C++中<<是流插入运算符,>>是流提取运算符。当然C++兼容C语言的大部分语法,所以<<在C++中也可以是左移操作符,>>在C++中也可以是右移操作符。
6.使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。 C++的输入(cin)输出(cout)可以自动识别变量类型。
那么看完上面6点,看结果:
其实很形象的,”Hello world!“ 和endl依次顺着<<”流入“cout打印在控制台上咩!
我们看到打印"Hello world!"并不用像C语言那样手动控制格式“%s”,自动识别了!
注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应 头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间, 规定C++头文件不带.h;旧编译器(vc 6.0)中还支持格式,后续编译器已不支持,因 此推荐使用+std的方式。
std命名空间的使用惯例: std是C++标准库的命名空间,如何展开std使用更合理呢?
1. 在日常练习中,建议直接using namespace std即可,这样就很方便。
2. using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对 象/函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模 大,就很容易出现。所以建议在项目开发中使用,像std::cout这样使用时指定命名空间 + using std::cout展开常用的库对象/类型等方式。
4.缺省参数
4.1缺省参数概念
缺省参数(或者叫默认参数)是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实 参则采用该形参的缺省值,否则使用指定的实参。
#include<iostream>
using namespace std;
void Func(int a = 10)
{cout << a << endl;
}
int main()
{Func();// 没有传参时,使用参数的缺省值Func(100);// 传参时,使用指定的实参return 0;
}
简单易懂好吧!
缺省参数有几点注意事项:
1.缺省参数不能在函数声明和定义中同时出现,当函数声明和定义分离时在声明给缺省参数。
2.缺省值必须是常量或者全局变量。
3.C语言不支持(编译器不支持)缺省参数。
4.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;}
要注意半缺省参数必须从右往左依次来给出,不能间隔着给 。
调用一个看看:
#include<iostream>
using namespace std;
void Func(int a , int b = 20, int c = 30)
{cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl;
}
int main()
{Func(1,10);return 0;
}
这里函数Func参数a没有指定缺省值,所有调用Func函数的时候必须给a一个实参,b、c随便。
5.函数重载
5.1.函数重载概念
C语言不支持同名函数,但是C++支持。因为C++有:
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这 些同名函数的形参列表(参数个数或类型或类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
1.参数类型不同构成函数重载
#include<iostream>
using namespace std;
int Add(int a = 10, int b = 20)
{cout << "Add(int a=10,int b=20)->";return a + b;
}
double Add(double a = 10.0, double b = 10.0)
{cout << "Add(double a=10.0,double b=10.0)->";return a + b;
}
int main()
{cout << Add(5, 5) << endl;cout << Add(10.0, 9.9) << endl;return 0;
}
编译器会根据传入实参类型不同调用相应的Add函数。
2.参数个数不同构成函数重载
#include<iostream>
using namespace std;
void Func(int a,int b)
{cout << "Func(int a,int b)"<<endl;}
void Func(int a,int b,int c)
{cout << "Func(int a,int b,int c)"<<endl;
}
int main()
{Func(1, 2);Func(1, 2, 3);return 0;
}
编译器会根据传入实参个数不同调用相应Func函数。
3.参数类型顺序不同构成函数重载
#include<iostream>
using namespace std;
void Func(int a,double b)
{cout << "Func(int a,double b)"<<endl;}
void Func(double a,int b)
{cout << "Func(double a,int b)"<<endl;
}
int main()
{Func(1, 2.2);Func(1.1, 2);return 0;
}
编译器会根据传入实参类型顺序不同调用相应Func函数。
5.2.C++支持函数重载的原理--名字修饰(name Mangling)
本鼠也说不是很清楚,本鼠大概知道原理。所以本鼠大概讲讲,如有不足,恳请斧正!
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
C语言编译器在链接时,直接根据函数名去找函数地址。由于函数名相同,导致找到多个地址,编译器不知道该链接哪一个地址,所以不支持函数重载。
C++编译器有函数名修饰规则(这个规则不统一,各个编译器可能不同),会引入函数参数类型信息添加到修改后的函数名中,只要参数不同,修饰出来的名字就不一样,那么找到的地址就不同,就支持函数重载了!
本博客就到这里吧,感谢阅读,下次再见!