关键字有98个,在之后的文章中再逐个写出来。
#include
using namespace std;
int main()
{
cout << "hello world" << endl;
return 0;
}
不同于printf,c++用cout,而endl也就是’\n’的意思,<<则是流输入。去掉 << endl就不是自动换行了。
C++兼容C的所有语法,我们可以直接用。
命名空间是什么?有什么作用?首先可以看到的是,这是一块空间。但这个空间为何叫命名?我们实际写代码时,如果多人写代码,然后引用到其中一个文件来,就会出现一个问题,如果有两个人创建的变量名一样,那么程序就出错了,所以C语言就有这样一个缺陷。C++为了解决这种冲突,就出现了命名空间这个语法。
我们先一点点来理解命名空间
C语言中有全局域和局部域 ,有了全局变量后,在一个函数里再加入一个同名局部变量,那么在函数里打印出来的值就是局部变量的值。看代码
int a = 0;
void f1()
{
int a = 1;
}
void f2()
{
int a = 2;
printf("%d\n", a);
}
int main()
{
printf("%d\n", a);
f2();
return 0;
}
这时候的结果就是0和2。如果我们在f2函数里也想打印全局变量怎么办?当然,在int a = 2之前就printf一下即可,不过这里用的不是这个方法。我们要用到一个域作用限定符。
void f2()
{
int a = 2;
printf("%d\n", a);
printf("%d\n", ::a);
}
::这个域作用限定符前面有空格就表示在全局范围寻找,如果全局没有相应的变量名,就会出错。
现在开始多个文件使用,创建两个.h文件。写入一些代码,让这些代码有重复命名的部分
struct Node
{
struct Node* next;
int val;
};
struct Queue
{
struct Node* head;
struct Node* tail;
};
struct Node
{
struct Node* next;
struct Node* prev;
int val;
};
这里会出现重定义的错误。在C语言里,这样只能改名;但是C++可以用命名空间解决。在刚才的域中,一个域里不能出现两个同名变量,f2函数里正常的输出a,就输出局部变量的值,而输出全局变量的值需要加上域作用限定符。这里的命名空间相当于加一个域,把文件所有的代码都套上一个空间,那么两个文件代码虽然相同,但在不同空间里,也就互不影响。所以能够看出命名空间影响使用。不过命名空间不影响生命周期,全局变量还是全局变量,局部变量还是局部变量。
namespace dd
{
struct Node
{
struct Node* next;
struct Node* prev;
int val;
};
}
像这样,包进命名空间里再引用就不会出问题了。但是问题还没结束。现在在cpp文件里建立一个变量,用struct Node类型,这时候程序还是出错了。虽然有了命名空间,但程序在搜索struct Node的声明时不会在命名空间里面找,而是在全局域和局部域里面找,所以还是报错了。如何解决?前面我们提到了域作用限定符,::前面为空格时是指定全局域里面找,现在加上命名空间,struct dd :: Node,struct yy :: Node,两个Node变量就不冲突了。至于不放在struct前面,是因为同名冲突的是Node,而不是struct,比如我还可以声明struct snode,那么查找的也就是snode这个标识。
#include "dd.h"
#include "yy.h"
int main()
{
struct yy::Node m;
struct dd::Node n;
return 0;
}
以及想要用同样名字的变量,也要放进命名空间里才可。在yy空间里再声明一个num的整型,那么外面就要用yy::num才能操作它。
如果命名空间也重名了怎么办?对于重名的空间,程序会把他们认为是一个空间,所以这就和没加命名空间一样。可以改里面的名字,也可以改命名空间的名,也可以嵌套一个空间。
那么用的时候就是yy::xx::num。
前面的代码都已经放入了命名空间,现在再往yy里添加一些函数,比如push,init等,然后在外部调用。如果直接调用,肯定是不行的,程序会报错,这应该怎么解决?
现在对于这个问题,我们有三个办法解决。
指定命名空间访问
编译器自然不会让你一个个都加上
#include "yy.h"
int main()
{
struct yy::Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
}
创建变量时带上命名空间即可,不过用不同的变量还是要每个都用符号,稍显麻烦,但也实用。
using全局展开
默认情况下,程序不会直接命名空间查找。但是我们可以把命名空间给展开,就像引用头文件一样。
#include "yy.h"
using namespace yy;
int main()
{
struct Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
}
不加上using这一行代码,程序就不会去命名空间里找。加上就会找一遍yy.h里非命名空间的代码,再找一遍命名空间。
C++为了区分用户写的和标准库自带的,把标准库也放到了一个命名空间,就是std。
比如cout就是std里的。实际写代码时,我们可以全局展开,也可以带上std::。不过一般写项目时很少全部展开,因为全部展开后相当于没有命名空间了,所有标准库的代码都包含了进来,那么用户写代码时就会局限很多。所以指定访问也就有用处了。
部分展开
这里还有第三个办法。部分展开的意思是用什么展开什么。我要用cout,endl,那就展开这两个
#include
using std::cout;
using std::endl;
之前已经写过,<<是流插入,endl等价于换行符
#include
using namespace std;
int main()
{
cout << "hello world" << endl;
cout << "hello world" << '\n';
return 0;
}
现在我们可以这样理解,cout相当于一个控制台,hello world字符串流向cout,endl流向字符串。另外还有流提取,比如cin。从后面的变量中提取出来给到cin,cin >> n。
C++的流插入流提取有个很好的特性——自动识别类型
//自动识别类型
int n = 0;
cin >> n;
double* a = (double*)malloc(sizeof(double) * n);
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
for (int i = 0; i < n; i++)
{
cout << a[i] << endl;
}
return 0;
能够看到a和n类型不一样,但cin和cout都可以帮你解决问题。默认情况下保留5位小数,如果想要控制精度可以使用C语言的方法,比如"%.4f"等等,而C++方面的则要复杂。
C语言并没有缺省参数,实际上这个东西很有用。先看代码。
void Func(int a = 0)
{
cout << a << endl;
}
int main()
{
Func(1);
Func();
return 0;
}
实际输出结果是1和0。缺省参数就是如果不传参,就按照自带的数值运行,传参就用传的参数来运行。
多个参数且都是缺省参数时就是全缺省参数,可以全部传,也可以只传一个,但是不能隔开传,比如传了第1和第3个参数,必须连续传参。
void Func(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << " ";
cout << "b = " << b << " ";
cout << "c = " << c << " ";
cout << endl;
}
int main()
{
Func(1, 2, 3);
Func(1, 2);
Func(1);
Func();
return 0;
}
除此以外,还有半缺省参数。其实就是一部分缺省,但必须从右往左连续缺省。
void Func(int a, int b = 20, int c = 30)
{
cout << "a = " << a << " ";
cout << "b = " << b << " ";
cout << "c = " << c << " ";
cout << endl;
}
int main()
{
Func(1, 2, 3);
Func(1, 2);
Func(1);
return 0;
}
关于缺省参数的好处,我们可以联想一下之前的数据结构。比如栈,初始化malloc开空间时,我们总得先开一块,然后不断calloc,对于具体的数值可能操作者不知道是多少,所以代码比较麻烦。用上缺省参数的话,我们就可以往函数传数值,不传也没关系,有固定的数值。
struct Stack
{
int* a;
int top;
int capacity;
};
void StackInit(struct Stack* ps, int n = 100)
{
ps->a = (int*)malloc(sizeof(int) * n);
ps->top = 0;
ps->capacity = 0;
}
int main()
{
Stack st;
StackInit(&st);
return 0;
}
缺省参数在使用的时候,头文件和源文件不能同时有缺省参数,否则程序会不清楚该用哪一个而报错。缺省参数在头文件声明时加上即可,加在源文件也会导致函数参数出错。
前面说到的命名空间,可以解决同名变量。C语言中,同名函数也有同样的问题,只能改名,而C++有函数重载。
int Add(int left, int right)
{
cout << left + right << endl;
return 0;
}
double Add(double left, double right)
{
cout << left + right << endl;
return 0;
}
int main()
{
Add(1, 2);
Add(1.1, 2.2);
return 0;
}
这段代码放在C语言里一定出错,但是C++却没有问题。对于这两个函数C++都能够识别出来并给出准确结果。函数重载一般的情况是两个函数参数类型,个数,类型顺序至少有一个不同。类型顺序不同指的是像下图这样:
类型和个数相同,但是顺序不同也要重载。函数重载发生也要函数都在一个空间里,不是一个命名空间也就不会又重载。
函数重载貌似就这样简单地写完了,但是实际上有个关键性问题没有解决。为什么两个同名函数,C++真的能识别出来,C语言就不行?
编译器要先辨别出两个不同的函数,这样比起C语言是不是要慢一点?函数重载并不在运行时识别的,而是在编译阶段识别的。编译的时候C++对函数进行了处理,这个处理就是修饰了函数名。我们先打一个断点,转到反汇编去看。
可以看到两个函数下面都有一个call,后面括号里的地址也不一样。看一下调用两个函数那两行时程序在干嘛
所以可以看到两个函数在编译阶段就已经不一样了, 变成了一行行指令。虽然如此,还是有疑问。程序走到了调用函数的那一行后,程序是怎么知道就要去调用这个函数?
这里就牵扯到C++的修饰。C语言是按照函数名来查找,而C++是修饰过后按照修饰的指令来找。每个平台的修饰都不同,但规则都一样。所以每个函数也就变成了一些唯一的指令,C++通过这些指令找到每个函数。在C语言中如果用同名函数,就会出现这样:
在C++里,你可以看到这样
这时候报的外部符号就是修饰过的函数名了。
关于函数重载这里也并不是细致的说明,用linux的也可以去linux看看它如何修饰的。
用C语言的时候,指针要分为一级,二级,三级指针等等,为了改变实参的数据,我们总得想到传地址,用相应的指针接收,这个指针就麻烦,有可能就出错,所以C++就出了引用这个概念。C++的引用和Python的赋值相似。创建 i 这个变量并赋值后,我们可以把i当做这个值的标签,而引用则是又给它加了一个标签,所以我们可以给他加更多标签,也可以嵌套标签。引用后,新变量名的变化,旧变量名也会相应地变化了。
#include
using namespace std;
int main()
{
int a = 0;
int b = a;
int& c = a;
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
return 0;
}
不过如上图那样,再来一个int& z = k,那么z++后k和i的值都加一了。另外,赋值的本质是什么?程序又创建了y这个变量,然后把值复制过去。所以j和k两个都++,不会影响到i。
void Swap(int* x, int* y)
{
;
}
int main()
{
int a = 0;
int b = 1;
Swap(&a, &b);
return 0;
}
之前C语言想要改变实参的数值,就需要传地址。现在到了C++,我们用引用来实现。
void Swap(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 0;
int b = 1;
Swap(a, b);
cout << a << endl;
cout << b << endl;
return 0;
}
给a和b各自带上一个标签,也就是引用,那么x和y的改变就会影响到a和b了。
指针也一样可以引用,相当于两个指针指向同一块空间,不过名字不一样而已。
再看一下之前写链表的一个场景。
typedef struct Node
{
struct Node* next;
int val;
}Node;
void PushBack(Node* phead, int x)
{
Node* newnode = (Node*)malloc(sizeof(Node) * 10);
if (phead == NULL)
{
phead = newnode;
}
}
int main()
{
Node* plist = NULL;
PushBack(plist, 1);
PushBack(plist, 2);
PushBack(plist, 3);
}
像这样的情况,我们就需要传二级指针才能改变链表的数据。但是在C++里,引用即可。
void PushBack(Node*& phead, int x)
{
Node* newnode = (Node*)malloc(sizeof(Node) * 10);
if (phead == NULL)
{
phead = newnode;
}
}
下一篇再继续写。
结束。