一、C++关键字
与c语言的32个关键字不同,C++一共有63个关键字,这里不做详细介绍,等后面用到会一一介绍
二、命名空间
命名空间的出现主要是防止出现命名冲突的情况,在C语言中,我们不能同名定义,尤其是我们定义的变量名,函数名等不能和库里面的命名相同,为了解决这种情况,C++有了namespace这个关键字,用法如下
#include<iostream>
//命名空间里可以定义变量,结构体,函数
//跟结构体类似
//相当于在全局里面又开辟了一块独立的空间
namespace zxws{//这里的zxws就是这个命名空间的名字,自己随便取
int rand;//这个变量名其实和库里的rand()函数重名,但在命名空间里,可以很好的区分开来
int Add(int x,int y){
return x+y;
}
struct ListNode{
int val;
struct ListNode*next;
};
}
那么我们如何用命名空间里的变量/函数/结构体呢?共有三种用法如下
方法一:命名空间的名字+作用域限定符::
namespace zxws {
int rand;
int Add(int x, int y) {
return x + y;
}
struct ListNode {
int val;
struct ListNode* next;
};
}
int Add(int x, int y) {
return x + y;
}
//方法一:命名空间的名字+作用域限定符::
int main() {
printf("%p\n", rand);//这个是库里的函数的地址
printf("%d\n", zxws::rand);//这个是命名空间里的变量
printf("%d\n", Add(1, 2));//这个是在命名空间外的加法函数
printf("%d\n", zxws::Add(1, 2));//这个是在命名空间里的加法函数
struct zxws::ListNode s;
return 0;
}
方法二:使用using将命名空间里的某个成员引入
#include<iostream>
namespace zxws {
int rand;
int Add(int x, int y) {
return x + y;
}
struct ListNode {
int val;
struct ListNode* next;
};
}
//int Add(int x, int y) {
// return x + y;
//}
using zxws::Add;//这里的意思就是将Add这个函数放开,即可以在全局访问这个函数
//但是如果这时外面已经有了Add函数,就会出问题,编辑器会不知道访问哪个函数,这就是为什么将Add函数注释掉的原因
//using zxws::rand;//问题同上
using zxws::ListNode;
int main() {
printf("%d",Add(1,2));
struct ListNode s;
return 0;
}
方法三:using+命名空间
namespace zxws{
int rand;
int Add(int x, int y) {
return x + y;
}
struct ListNode {
int val;
struct ListNode* next;
};
}
using namespace zxws;//将命名空间里定义的所有东西放开
int main(){
//printf("%d",rand);//这时编译器就会分不清要打印哪个rand
printf("%d",Add(1,2));
return 0;
}
这里有两点补充:
1.一个工程中允许有多个相同名称的命名空间,编辑器会将它们合并成一个
2.命名空间是可以"无限套娃"的,如下
//套娃
namespace zxws{
int a=0;
//...
namespace zxw{
int b=1;
//...
namespace zx{
//...
};
};
}
//访问
using zxws::zxw::b;
//using namespace zxws;//注意使用这种方法不能访问b,因为b在zwx里面,得用下面的那行代码
using namespace zxws::zxw;
int main(){
printf("%d",b);
printf("%d",zxws::zxw::b);
return 0;
}
三、C++的输入、输出
#include<iostream>
using namespace std;
//打开标准库的命名空间,因为cin/cout/endl在命名空间里面
int main(){
int a=10;
//因为C++兼容C,所以scanf/printf函数依旧可以用,注意要包含头文件
//scanf("%d",&a);
//printf("%d",a);
cin>>a;//输入---自动识别类型
cout<<a<<endl;//输出---自动识别类型,endl等价于'\n'
return 0;
}
四、缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
举个例子:
#include<iostream>
using namespace std;
void Print(int a = 0) {
cout << a << endl;
}
int main(){
Print();//不传参时,a默认为0
Print(10);//传参时,a就等于传过来的值
return 0;
}
缺省参数又分为两种:全缺省和半缺省
//全缺省
void Print1(int a=1,int b=2,int c=3){
cout<<a<<" "<<b<<" "<<c<<endl;
}
//半缺省(部分缺省)
void Print2(int a,int b,int c=10){
cout<<a<<" "<<b<<" "<<c<<endl;
}
int main(){
//传参时,从左到右依次对应
Print1();
Print1(10);
Print1(10,20);
Print1(10,20,30);
cout<<endl;
Print2(10,20);
Print2(10,20,30);
return 0;
}
注意:
- 半缺省参数必须从右往左依次来给出,不能间隔着给
- 缺省参数不能在函数声明和定义中同时出现,声明给,定义不给
- 缺省值必须是常量或者全局变量
五、函数重载
重载函数是函数的一种特殊情况,为方便使用,C++允许在同一范围中声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同,也就是说用同一个函数完成不同的功能。这就是重载函数。重载函数常用来实现功能类似而所处理的数据类型不同的问题。不能只有函数返回值类型不同。
//1.参数个数不同
void f(){
cout<<"void f()"<<endl;
}
void f(int x){
cout<<"void f(int x)"<<endl;
}
//2.参数的类型不同
void f(char x){
cout<<"void f(char x)"<<endl;
}
void f(double x){
cout<<"void f(double x)"<<endl;
}
//3.参数的顺序不同
void f(int x,float y){
cout<<"void f(int x,float y)"<<endl;
}
void f(float x,int y){
cout<<"void f(float x,int y)"<<endl;
}
使用场景:
//交换
void Swap(int*p1,int*p2){
int tmp=*p1;
*p1=*p2;
*p2=tmp;
}
void Swap(double*p1,double*p2){
double tmp=*p1;
*p1=*p2;
*p2=tmp;
}
int main(){
int a = 10, b = 20;
cout << a << " " << b << endl;
Swap(&a, &b);
cout << a << " " << b << endl;
double x = 1.1, y = 2.2;
cout << x << " " << y << endl;
Swap(&x, &y);
cout << x << " " << y << endl;
return 0;
}
六、引用
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。从语法上来说,引用不占用空间,但是,从引用的底层实现来说,它其实使用指针实现的,所以会占用空间
int main() {
int a = 0;
//指针
int* p = &a;
(*p)++;
//引用
int& c = a;
c++;
return 0;
}
从上面的汇编代码我们可以看出指针和引用的实现是一样的,所以引用在做参数时的效率就会比传值要高。
那么,问个问题:下面代码的运行结果一样吗?
int main() {
int a = 10;
int& ra = a;
printf("%p\n", &a);
printf("%p\n", &ra);
return 0;
}
相信很多人在看完上面说的引用和指针的实现一样就会认为,ra和a的地址不同,但结果真的如此吗?
答案显而易见,虽然实现的方式一样,但是语法层面是不一样的,指针是存放地址的变量,引用就是取了某个变量的别名,那么既然是别名,那么语法本质上来说,ra和a是通一个东西,是同一块空间的不同名称,就和人有小名也有大名,我们不能因为他的叫法不同,而认为他不是同一个人
指针和引用之间的联系和区别:
- 引用概念上定义一个变量的别名,指针存储一个变量地址。
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
- 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数
- 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
引用的特性
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
int main() {
int a = 10;
//int& x;//会报错
int& b = a;
int& c = a;
//...
return 0;
}
当然,引用也有用const修饰的常引用,如下
int main() {
int a = 10;
//权限的平移,对a的操作,对b也能执行
int& b = a;
//权限的缩小,常引用不能修改值
const int& c = a;
const int x = 10;
//权限的放大,不被允许
//int& y = x;//会报错
return 0;
}
使用场景:
//做参数
void swap(int& x, int& y) {
int tmp = x;
x = y;
y = tmp;
}
//做返回值
int& f() {
static int c = 5;
//注意这里的c一定要是全局/静态的,这样出了作用域才不会被销毁,不然就会出现类似野指针的情况
return c;
}
七、内联函数
内联函数其实是为了替代宏而产生的,它在宏的基础上更上一层楼,属于取其精华去其糟泊了。
首先,我们用宏写的一个加法来复习一下宏是什么
#define ADD(x,y) ((x)+(y))
那么上面的代码有什么缺点,优点(宏的优点缺点)
优点:1.加强了代码的复用性 2.提高了性能(不需要建立栈帧)
缺点:1.不方便调试 (宏替换是预编译期间做的) 2.代码的可读性差 3.没有严格的类型检查
由此,C++创建了inline这个关键字,用来改善宏的缺点
inline int ADD(int x, int y) {
return x + y;
}
int main() {
cout << ADD(1, 2) << endl;
return 0;
}
上面写的ADD函数就是一个内联函数,写法上和普通函数没啥区别,单纯加了一个关键字inline,但是编辑器在处理时,就会将ADD在调用的地方之间展开,不会和函数一样去建立栈帧,提高了效率。
但需要我们注意的是,内联函数在编译期间不会生成地址(因为是在调用位置直接展开,所以不需要地址),所以内联函数的声明和定义要写在一起,不然编辑器会找不到内联函数,其次,内联函数不是我们说了算的,我们只是建议将某个函数设为内联函数,具体还得看编辑器,就和register关键字一样(一般只有小规模的函数才能被设置成内联函数)
八、auto关键字
随着程序越来越复杂,就会出现很复杂的类型,这时候就需要用到我们的auto关键字,可以自动的推导类型,用法如下:
#include <string>
#include <map>
int main() {
//一般情况下,没啥用
int a = 1;
auto b = a;
//但是当类型复杂之后,就会很有用(我们现在只要知道下面是一种类型)
std::map<std::string, std::string> m;
//这时我们就需要用到auto
auto x = m;
//这里介绍一个函数用来看函数的类型
cout << typeid(m).name() << endl;
cout << typeid(x).name() << endl;
return 0;
}
打印的是最原本的类型名,上面代码中的类型名还是被 typedef 过的。
注意:
- auto在定义变量时一定要初始化,编辑器要根据你给的数据来进行推导类型,
- 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
auto不能推导的场景:
1.不能作为函数参数
2.不能定义数组
3.如下
int main() {
auto x = 1, y = 1.0;//报错,编辑器只会对第一个变量进行推导,后面的变量需要保证类型一致
return 0;
}
C++11标准还引入了一种新的auto用法,如下:
int main() {
//这里就先然大家认识一下这种写法,后面会和大家具体讲这种语法规则
int arr[] = { 1,2,3,4,5,6,7,8,9 };
for (auto& x : arr) {
x *= 2;
}
for (auto x : arr) {
cout << x << " ";
}
return 0;
}
九、空指针
在C++中,NULL被定义为0,nullptr才是空指针,nullptr作为关键字存在,不要包头文件