引言

学习一门语言,一般都是从Hello,world!开始的。我们先来看一下C++的Hello,world!

1
2
3
4
5
6
#include <iostream>
using namespace std;
int main(){
cout<<"Hello,world!"<<endl;
return 0;
}

这时初学者一般会有两个疑惑:

  1. namespace是什么鬼?
  2. cout和endl又是什么?
  3. 为什么头文件没有.h

这三个问题将在本节解决。

命名空间

情景

在使用C语言进行协同项目开发时,可能会有这样的情况:A和B在编写各自的模块时,都定义了相同名字的函数。他们各自的代码能够编译通过,但是最后合并的时候,编译器却会报错。这个问题就是重定义。

如下图,A和B都定义了一个名叫foo的函数。我们分别编译A和B的代码,是没有问题的。但是,最后链接的时候,编译器报错”ambiguating new declaration“(重定义)。

如果对C的编译和链接有疑惑,可以去简单看看这篇文章:

这个问题,即相同名字的函数(当然不局限于函数)发生冲突,在C语言中是没有解决的。至少我不会/脸黑。但是就这个问题,C++给出的明确的解决方案就是使用”命名空间“。

命名空间的作用

  • 简单点来说,我们把名字关进命名空间这个”小黑屋”。编译器在外面是不知道里面有什么的,当我们把小黑屋打开,编译器才能找到里面的名字。同时,这个屋内的变量名字即使同另外一个的一样,但是他们本质是不一样的。使用时我们按照一定的方式打开”小黑屋”(取出命名空间中的成员)即可。

  • 严谨点来说,命名空间通过将标识(zhi)符的名称本地化,防止相同名字的标识符之间产生冲突

本地化的意思就是编译器编译时会在名字前面加一些前缀(一个命名空间对应一个前缀),以防止同名导致冲突。当我们使用某个命名空间的时候,编译器就会默认加上相应的前缀,从而找到对应的函数/变量。

命名空间的定义

使用namespace关键字:

1
2
3
4
5
6
namespace Mynamespace{
//存放变量
int a;
//存放函数
int foo(void);
}

命名空间的使用

使用命名空间有三种方法:

  1. 暴力导入。一次性全部释放出命名空间中的所有成员:

    1
    using namespace Mynamespace; 

    这样确实省力,但是全部释放后这个命名空间就相当于不存在了,没有起到命名空间的作用。所以,这种方法一定要慎用!!!

  2. 一个个导入。事先想好自己要用到这个命名空间中的哪些成员,把他们单独释放出来:

    1
    2
    //释放出命名空间中的a变量
    using Mynamespace::a;

    这要求我们提前想好要使用命名空间的哪些成员(当然也可以用到时加,但是程序会显得很乱)。这种方法适用于使用比较频繁的成员

  3. 使用命名空间名称和作用域限制符。例如:

    1
    std::cout<<"Hello,World!"<<std::endl;

    这样就不需要使用using了,同时也是最安全的一种做法。缺点是比较繁琐,适用于使用不是那么频繁的成员

前两种方法相当于事先将命名空间里面的成员拿出来;第三种相当于要用的时候去对应命名空间取,用完再放回去。

输入和输出

为什么叫cin和cout

想必这个问题会困扰初学者许久。也许你会认为因为是C++,所以在前面加了个C。但是,实际上并不是这样。

  • cinclavier input的缩写,即键盘输入。表示程序接收来自键盘的输入。

  • coutconsole output的缩写,即控制台输出。表示将值输出到控制台(命令行)。

  • endlend line的缩写,即换行,它的作用同转义字符\n一样。

    没用的知识增加了……

cout

cout支持连续输出自动类型推断

1
2
3
using namespace std;
int a=1;
cout<<"Hello,World!"<<endl<<"换行后继续输出"<<'\n'<<"再次换行后继续输出"<<a;

这里的endl\n等价。

但是,cout不能控制输出的格式。比如,对于一个int型,不能指定以八进制或十六进制输出,这时我们应该使用标准化输出函数printf

cout之所以支持自动类型推断,是因为对<<进行了运算符重载。

cin

cin支持连续输入。

1
2
3
4
int a;
char b;
//连续输入a和b
cin>>a>>b;

但是,cin不能指定以什么样的方式输入(空格,不空格,换行……),这时我们应该使用scanf

据我的测试,cin使用空格和换行分隔都是可以的,但是不能使用逗号分隔!所以需要用逗号作为分隔符或指定输入方式的就要用scanf

说明

scanfprintf 其实是 C 语言提供的函数。大多数情况下,它们的速度比 cincout 更快,并且能够方便地控制输入输出格式。与之相比,cincout的使用更加灵活。它不需要我们去说明输出的格式,可以自己推导,并且能够连续输入和输出

读者不妨用下面的代码进行一个简单的测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <time.h>
using namespace std;
int main(){
clock_t start=clock();
for(int i=1;i<=100000;i++)
//测试cout的速度
cout<<i;
//测试printf的速度
//printf("%d",i);
clock_t stop=clock();
cout<<'\n'<<stop-start;
return 0;
}

实际使用中,对于应该使用scanf , printf 还是cincout,需要我们根据自己的需求进行选择。


上面的结果对电脑性能依赖较大。而且有时候因为编译器的优化,cincout甚至会快过scanfprintf!所以我们还不能一概而论。

除此此外,OI可能会涉及到一些卡常的技巧。为了使程序运行得更快,很多大佬都推荐使用cincout我在OI Wiki问过,大佬们给我的是这个答复。

最后

  1. 什么是std

    std是 C++ 标准库所使用的 命名空间cincout都放在命名空间std里面。

  2. 为什么头文件没有.h

    这是对C++进行标准化的方式。C++引入命名空间以后,为了区分以前全局空间的头文件,所以用同名不带.h的方式对它们加以区别。而最前面加c表示来源于C语言。如:

    查看例子

    C++标准化之前的头文件去掉.h

    • iostream.h->iostream

    从C语言引入的头文件去掉.h,开头加上c

    • string.h->cstring
    • math.h->cmath