标准输入输出与命名空间

C++ 最初被设计为 “C with Classes”,完全兼容 C 语言。也就是说,C 语言中的代码是完全可以复制到 C++ 中运行的。比如,我们可以把 C 语言打印 "Hello, world!" 的代码复制到这里运行,代码如下:

#include <stdio.h>

int main() {
    printf("Hello, world!");
    return 0;
}

虽然以上代码可以正常运行,但是,在引入头文件 #include <stdio.h> 处会出现警告:

头文件名称应该使用 cstdio 而不是 stdio.h
头文件名称应该使用 cstdio 而不是 stdio.h

C++ 中包含了 C 标准库的移植版本,C 标准库的头文件 xxxx.h 基本上变成了 cxxxx。例如,stdio.h 在 C++ 中对应的是 cstdiomath.h 变成了 cmathstring 变成了 cstring

下面是 C++ 中打印 Hello World! 的代码:

#include <iostream>

int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

接下来我们讲解一下上面这段程序:

  • C++ 语言定义了一些头文件,这些头文件包含了程序中必需的或有用的信息。上面这段程序中,包含了头文件 <iostream>
  • int main() 是主函数,程序从这里开始执行。
  • std::cout << "Hello, World!" << std::endl;:会在屏幕上显示消息 "Hello World"。
  • return 0:终止 main() 函数,并向调用进程返回值 0。

C++ 的输入输出

在 C 语言中,我们通常会使用 scanf 和 printf 来对数据进行输入输出操作。在 C++ 语言中,C 语言的这一套输入输出库我们仍然能使用,但是 C++ 又增加了一套新的、更容易使用的输入输出库,即 iostream 库。这个库包含了两个主要的对象:cincout,分别用于输入和输出。

coutcin 都是 C++ 的内置对象,而不是关键字。C++ 库定义了大量的类(Class),程序员可以使用它们来创建对象,coutcin 就分别是 ostreamistream 类的对象,只不过它们是由标准库的开发者提前创建好的,可以直接拿来使用。这种在 C++ 中提前创建好的对象称为内置对象。

使用 cout 进行输出:

std::cout << "Hello, World!" << std::endl;
  • std:: 是命名空间前缀,表明 cout 属于 C++ 标准库(std 命名空间),用于避免名称冲突;
  • << 符号是输出运算符(或称为插入运算符)表示该语句把这个字符串发送给 cout
  • 结尾的 endlend of line 的缩写,等同于 \n
  • << 符号可以拼接输出:
char name[] = "罗大富";
int age = 30;
std::cout << "我的名字是" << name << ",今年 " << age << " 岁" << std::endl;

接着是使用 cin 进行输入:

int age;
std::cin >> age;

float height;
std::cin >> age;

char name[20];
std::cin >> age;

// 输出
std::cout << "我的名字是" << name << std::endl;
std::cout << "年龄:" << age << std::endl;
std::cout << "身高:" << height << "m" << std::endl;

使用 cout 进行输出时需要紧跟 << 运算符,使用 cin 进行输入时需要紧跟 >> 运算符,这两个运算符可以自行分析所处理的数据类型,因此无需像使用 scanfprintf 那样给出格式控制字符串。

cout 能够连续地输出。同样 cin 也是支持对多个变量连续输入,代码如下:

// 定义变量
char name[20];
int age;
float height;

// 连续输入
std::cin >> name >> age >> height >> gender;

// 输出
std::cout << "我的名字是" << name << std::endl;
std::cout << "年龄:" << age << std::endl;
std::cout << "身高:" << height << "m" << std::endl;

命名空间 - namespace

有时候我们也会看到这个形式的 C++ 打印 Hello, world 的程序:

#include <iostream>                     // 声明引用的头文件
using namespace std;

int main() {
    cout << "Hello, World!" << endl;
    return 0;
}

与之前的程序最大的区别在于,添加了代码 using namespace std; 后,调用 cout、endl、cin 时不再需要添加 std:: 前缀。

一个中大型软件往往由多名程序员共同开发,会使用大量的变量和函数,不可避免地会出现变量或函数的命名冲突。当所有人的代码都测试通过,没有问题时,将它们结合到一起就有可能会出现命名冲突。

因此,引入了命名空间这个概念,专门用于解决上面的问题,它可作为附加信息来区分不同库中相同名称的函数、类、变量等。

namespace 是 C++ 中的关键字,用来定义一个命名空间,语法格式为:

namespace 标识符 {
   // 代码声明,如变量、函数、类、宏定义等都可以放在其中
}

标识符 是命名空间的名字,它里面可以包含变量、函数、类、typedef、#define 等,最后由 {} 包围。例如下面代码中在两个命名空间中创建了相同的变量名和函数名:

namespace space_1 {
    int id = 123;

    void func() {
        std::cout << "调用了space_1 的 func() 函数" << std::endl;
    }
}

namespace space_2 {
    int id = 456;

    void func() {
        std::cout << "调用了space_2 的 func() 函数" << std::endl;
    }
}

为了调用带有命名空间的函数或变量,需要在前面加上命名空间的名称,如下所示:

name::code;  // code 可以是变量、函数等

:: 是一个新符号,称为域解析操作符,在 C++ 中用来指明要使用的命名空间。代码如下:

#include <iostream>

namespace space_1 {
    int id = 123;

    void func() {
        std::cout << "调用了space_1 的 func() 函数" << std::endl;
    }
}

namespace space_2 {
    int id = 456;

    void func() {
        std::cout << "调用了space_2 的 func() 函数" << std::endl;
    }
}


int main() {
    std::cout << "space_1 的 id:" << space_1::id << std::endl;
    space_1::func();

    std::cout << "space_2 的 id:" << space_2::id << std::endl;
    space_2::func();
}

除了直接使用域解析操作符,还可以采用 using namespace 命名空间 指令,这样在使用命名空间时就可以不用在前面加上命名空间的名称。这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。例如,我们可以在上面的代码中添加 using namespace space_1,这样,我峨嵋你在调用 space_1 中的变量函数时,就不需要再添加命名空间前缀,代码如下:

#include <iostream>

namespace space_1 {
    int id = 123;

    void func() {
        std::cout << "调用了space_1 的 func() 函数" << std::endl;
    }
}

namespace space_2 {
    int id = 456;

    void func() {
        std::cout << "调用了space_2 的 func() 函数" << std::endl;
    }
}

using namespace space_1;

int main() {
    std::cout << "space_1 的 id:" << id << std::endl;
    func();

    std::cout << "space_2 的 id:" << space_2::id << std::endl;
    space_2::func();
}

由于 space_1space_2 中含有相同的变量与函数,因此,不可以同时声明 space_1space_2

using 指令也可以用来指定命名空间中的特定项目。例如,如果只打算使用 space_1 中的 id,或者,只想使用 space_2 中的 func() 函数,我们就可以使用如下语句:

using space_1::id;
using space_2::func;        // 声明函数时,只需要写函数名,不需要加括号

int main() {
    std::cout << "id:" << id << std::endl;
    func();
}

了解了命名空间后,我们就可以回过头来学习 std 命名空间。按住 ctrl + 鼠标左键点击 iostream,进入 c++ 标准库可以发现:

iostream 库中所有的内容都是写在 std 命名空间中的
iostream 库中所有的内容都是写在 std 命名空间中的

因此,我们可以在代码中声明 using namespace std,这样,我们在调用 cin、cout、endl 时就不在需要添加 std:: 前缀,代码如下:

#include <iostream>
using namespace std;

int main() {
    int id;
    cin >> id;
    cout << "Hello, " << id << endl;
    return 0;
}

我们之前在学习 C 语言的时候,提到过作用域的问题,using 声明也是有作用域的,如果我们将 using namespace std; 写在 main 函数中,那么我们只能在 main 函数中调用标准库的代码时可以省略 std:: 前缀,在其他函数中则不行,示例如下:

#include <iostream>

void func() {
    std::cout << "你好" << std::endl;
}

int main() {
    using namespace std;
    int id;
    cin >> id;
    cout << "Hello, " << id << endl;
    func();
    return 0;
}
由于 using namespace std 写在了 main 函数中,main 函数外的 func 函数在调用标准库中的 cout、endl 时依然需要添加前缀。

注意

很多教程中都是这样做的,将 std 直接声明在所有函数外部,这样虽然使用方便,但在中大型项目开发中是不被推荐的,这样做增加了命名冲突的风险,我推荐在函数内部声明 std。

不过为了方便,本教程还是忍不住违反了原则,后面有很多代码都在全局范围内声明了 std。