第一个C++程序
1. <iostream>
(C++)
<iostream>
是 C++ 标准库中的头文件,用于处理输入输出操作。它提供了基于流(stream)的输入输出机制。
特点:
- 面向对象:C++ 中的输入输出操作是基于流的,这种机制是面向对象的。流可以看作是字节序列的抽象,输入流从数据源(如键盘或文件)读取数据,输出流将数据写入目标(如屏幕或文件)。
- 常用的流对象:
std::cin
:标准输入流,通常与键盘关联,用于从用户输入读取数据。std::cout
:标准输出流,通常与显示器关联,用于输出数据到屏幕。std::cerr
:标准错误流,通常与显示器关联,用于输出错误信息。std::clog
:标准日志流,用于输出日志信息。
2. <stdio.h>
(C)
<stdio.h>
是 C 标准库中的头文件,用于处理输入输出操作。它提供了一些用于文件操作和标准输入输出的函数。
特点:
- 基于函数的输入输出:C 语言中的输入输出操作是基于函数的。常见的输入输出函数包括
printf
、scanf
、fprintf
、fscanf
、fgets
、fputs
等。 - 低级别:与 C++ 的面向对象的流相比,C 的输入输出更为低级,操作更加直接。
//#include<stdio.h>
#include<iostream>
int main(){
// printf("hello world\n");
std::cout << "hello world" << std::endl;
return 0;
}
我们可以先暂时理解为endl
和 \n
一样,功能就是输出时换行,它们有一些重要的区别,endl
是 C++ 标准库中的一个操控符(manipulator),它不仅用于在输出流中插入一个换行符,还会刷新输出缓冲区(flush the output buffer)。这意味着,endl
会强制将缓冲区中的内容立即输出到目标设备(如屏幕),而不仅仅是插入一个换行符。对于作用限定符::我们可以先简单地理解为什么中的什么。
命名空间
命名空间(namespace) 是 C++ 中用于组织代码的一种机制,它允许将代码分组到不同的命名空间中,以避免名称冲突,特别是在大型项目或使用多个库时。命名空间为变量、函数、类等标识符提供了一个作用域,可以防止不同命名空间中的标识符相互干扰。
代码演示
#include<iostream>
namespace ICBC{
int g_money = 0;
void pay(int money){
g_money -= money;
}
void save(int money){
g_money += money;
}
}
namespace CCB{
int g_money = 0;
void pay(int money){
g_money -= money;
}
void save(int money){
g_money += money;
}
}
int main(){
ICBC::pay(1000);
ICBC::save(3000);
CCB::save(10000);
CCB::pay(2000);
std::cout << "工行卡的余额:" << ICBC::g_money << std::endl;
std::cout << "建行卡的余额:" << CCB::g_money << std::endl;
}
在使用命名空间后,相同名字的变量和函数并没有发生冲突
名字空间声明
代码演示
//名字空间声明
#include<iostream>
using namespace std;
namespace ns{
int v_tomato = 10;
}
int main(){
int v_tomato = 30;
using ns::v_tomato; //从这行代码开始,ns中的内容引入当前作用域(相当于定义)
//上面的定义会造成重定义报错
v_tomato = 20;
cout << "ns::v_tomato = " << ns::v_tomato << endl;
return 0;
}
因为所有标准库提供的 类型、对象和函数都位于std名字空间中 ,所以我们在使用using namespace std;后,就可以对输出代码的写法进行简化,省略std::
代码分析
编译器在编译每一个函数时都会构造两张表,在编译这个函数(main函数)结束后销毁。在函数体内定义的变量会放进定义表,对于函数体外定义的对本函数可见的变量会放进可见表。对于本例,使用名字空间声明也相当于在这个函数体内定义了这个变量,又因为函数体内先前已经定义了这个变量(int v_tomato=30),此时的名字空间声明会造成重定义,这是编译器绝对不允许的,因此会报错。当在函数内使用某个标识符时,如果存在同名,编译器需要确定你使用的是哪一个标识符,因此他会拿着这个名字先在定义表中查看,如果定义表中找不到,才会到可见表中找。
名字空间指令(using namespace [...])
名字空间指令是指使用 using namespace
语句将整个命名空间引入当前作用域。这意味着命名空间中的所有标识符都可以直接使用,而不必再通过命名空间限定符来引用。这种方式在简化代码的同时也可能带来名称冲突的风险,尤其是在大型项目中。
代码演示
//名字空间指令
#include<iostream>
using namespace std;
namespace ns{
int v_tomato = 10;
}
int main(){
using namespace ns; //从这行代码开始,ns中的内容在当前作用域中可见
//ns::v_tomato = 20;
v_tomato = 20;
//std::cout << ns::v_tomato << std::endl;
cout << "ns::v_tomato = " << ns::v_tomato << endl;
return 0;
}
在上面的代码中,using namespace ns;
是一个名字空间指令,它将 ns1
命名空间中的所有标识符引入到 main
函数的作用域中,因此你可以直接使用 v_tomato
而不需要在前面加上 ns::
。
代码分析(不同于上个代码)
对于本例,代码的输出结果依然是10,我们来分析一下。main函数内的int v_tomato = 10,这是一个放在定义表的局部变量,而对于ns命名空间的int v_tomato = 10是放在可见表中的。当出现v_tomato = 20这个变量,编译器需要确定究竟是这两个同名变量中的哪一个,编译器会先在定义表中寻找,此时他找到了就不会查看可见表,因此v_tomato = 20修改的是main函数内定义的局部变量而不是ns命名空间的同名变量。这也就是最后我们的输出结果依然是v_tomato = 10的原因,因为它实际上并没有修改我们ns命名空间中的这个变量。
名字空间指令和名字空间声明的差别
使用名字空间指令会让所指定命名空间的内容在当前作用域中可见,其中定义的变量会被放入可见表中,而使用名字空间声明会让所指定命名空间中的内容引入当前作用域(相当于定义),其中定义的变量放入定义表中
代码演示
//名字空间指令 和 名字空间声明 的差别
#include<iostream>
using namespace std;
namespace ns1{
int g_money = 10;
int g_other = 10;
}
namespace ns2{
int g_money = 10;
int g_other = 10;
}
int main(){
using namespace ns1;//ns1中的所有内容都出现在可见表中
using ns2::g_other;//只有ns2::g_other出现在定义表中
g_money = 100;//修改的是ns1中的g_money
cout << "ns1::g_money = " <<ns1::g_money <<
", ns2::g_money =" << ns2::g_money <<endl;
g_other = 100;//修改的是ns2中的g_other
cout << "ns1::g_other =" << ns1::g_other <<
", ns2::g_other =" << ns2::g_other <<endl;
return 0;
}
命名空间的嵌套
在C++中,嵌套命名空间的同名变量不冲突,因为每个变量在其所属的命名空间中都有唯一的作用域。变量的名称相同,但由于它们位于不同的命名空间中,编译器可以通过命名空间限定符(如ns1::ns2::g_money
)来区分它们。因此,它们不会冲突。
//命名空间的嵌套
#include<iostream>
using namespace std;
namespace ns1{
int g_money = 10;
namespace ns2{
int g_money = 20;
namespace ns3{
int g_money = 30;
namespace ns4{
int g_money = 40;
}
}
}
}
int main(){
//名字空间别名可以简化书写
namespace ns_forth = ns1::ns2::ns3::ns4;
//cout << ns1::ns2::ns3::ns4::g_money << endl;
cout << ns_forth::g_money << endl;
}
扩展
我们说使用名字空间声明引入的变量相当于定义在main函数中的变量,那他们有什么区别?
在C++中,namespace
中的变量(如ns::v_tomato
)通常具有进程级生命周期
1. 进程级生命周期的定义
- 进程级生命周期的变量从程序开始执行(通常是在程序的初始化阶段)到程序结束时一直存在。
- 这些变量通常分配在全局/静态数据区中,生命周期贯穿整个程序的运行过程。
2. namespace
中的变量的生命周期
namespace ns { int v_tomato = 10; }
:- 这里的
v_tomato
是在命名空间ns
中定义的全局变量。 - 虽然它被封装在
namespace
中,但它的本质仍是一个全局变量。
- 这里的
特点:
- 存储位置:
v_tomato
会存储在全局/静态数据区中,而不是栈或堆中。这意味着它在程序的整个生命周期内都存在。 - 生命周期:从程序启动时开始(即在
main()
函数执行之前),直到程序终止时结束。因此,它具有进程级的生命周期。 - 作用范围:虽然
v_tomato
的作用范围被限制在命名空间ns
中,但它仍然是一个全局变量。这意味着在程序的任何地方,只要通过ns::v_tomato
访问,它都可以被引用和修改。
3.示例分析
#include <iostream>
namespace ns {
int v_tomato = 10; // 进程级生命周期的变量
}
int main() {
std::cout << "v_tomato = " << ns::v_tomato << std::endl; // 输出 10
ns::v_tomato = 20;
std::cout << "v_tomato = " << ns::v_tomato << std::endl; // 输出 20
return 0;
}
在这个示例中:
v_tomato
在程序开始时被初始化为10,并且在整个程序的生命周期中都存在。- 你可以在
main()
函数中(或程序的其他地方)访问和修改它的值。 - 当程序结束时,
v_tomato
才会被销毁。
4. 总结
namespace
中的变量v_tomato
是具有进程级生命周期的全局变量。- 它从程序启动时初始化,并在程序结束时销毁。
- 尽管被封装在
namespace
中,它的生命周期和其他全局变量一样,不受namespace
本身的影响。