第一个 C++ 程序与规范
定义与作用
每个 C++ 程序都有一个入口函数 main,它是由操作系统调用的起点。C++ 程序结构、命名空间、头文件组织和编码规范共同构成了程序员的"基本功"——它们决定了代码的可读性、可维护性和跨平台兼容性。
核心原理
程序基本结构
头文件与源文件的职责分离
| 文件类型 | 包含内容 | 作用 |
|---|---|---|
.hpp / .h | 类声明、函数声明、宏定义、模板 | 接口定义,供多翻译单元共享 |
.cpp | 函数实现、全局变量定义、main | 实现细节,只编译一次 |
命名空间(namespace)
命名空间是 C++ 解决名称冲突的机制。标准库的所有标识符位于 std 命名空间中。
// 三种使用方式
#include <iostream>
// 方式 1: 完全限定名(推荐在头文件中使用)
std::cout << "Hello";
// 方式 2: using 声明(推荐在 .cpp 的函数作用域内使用)
using std::cout;
// 方式 3: using 指令(谨慎使用,绝不放在头文件全局作用域)
using namespace std;
main 函数的合法签名
int main() { } // 标准形式
int main(int argc, char* argv[]) { } // 带命令行参数
int main(int argc, char** argv) { } // 等价形式
auto main() -> int { } // C++11 后置返回类型
main函数可以省略return语句,编译器自动插入return 0;(C++ 标准规定)。
完整示例
示例一:学生成绩录入系统
场景说明:一个命令行程序,读取学生姓名和成绩,计算并输出汇总信息。演示基本程序结构、命名空间使用、头文件包含。
grade.hpp
#ifndef GRADE_HPP
#define GRADE_HPP
#include <string>
#include <vector>
namespace sdu { // 自定义命名空间,避免名称冲突
struct Student {
std::string name;
double score;
};
std::vector<Student> input_students();
void print_summary(const std::vector<Student>& students);
} // namespace sdu
#endif
grade.cpp
#include "grade.hpp"
#include <iostream>
#include <iomanip>
namespace sdu {
std::vector<Student> input_students() {
std::vector<Student> result;
int count;
std::cout << "请输入学生人数: ";
std::cin >> count;
for (int i = 0; i < count; ++i) {
Student s;
std::cout << "第 " << (i + 1) << " 位学生姓名: ";
std::cin >> s.name;
std::cout << "成绩: ";
std::cin >> s.score;
result.push_back(s);
}
return result;
}
void print_summary(const std::vector<Student>& students) {
if (students.empty()) {
std::cout << "无学生数据" << std::endl;
return;
}
double total = 0.0;
double max_score = students[0].score;
double min_score = students[0].score;
for (const auto& s : students) {
total += s.score;
if (s.score > max_score) max_score = s.score;
if (s.score < min_score) min_score = s.score;
}
double average = total / students.size();
std::cout << "\n===== 成绩汇总 =====" << std::endl;
std::cout << "学生人数: " << students.size() << std::endl;
std::cout << std::fixed << std::setprecision(2);
std::cout << "平均分: " << average << std::endl;
std::cout << "最高分: " << max_score << std::endl;
std::cout << "最低分: " << min_score << std::endl;
}
} // namespace sdu
main.cpp
#include "grade.hpp"
#include <iostream>
int main() {
std::cout << "学生成绩管理系统 v1.0" << std::endl;
std::cout << "===================" << std::endl;
auto students = sdu::input_students();
sdu::print_summary(students);
return 0;
}
编译命令:
g++ -std=c++11 -Wall -Wextra grade.cpp main.cpp -o grade_system
预期交互示例:
学生成绩管理系统 v1.0
===================
请输入学生人数: 3
第 1 位学生姓名: 张三
成绩: 88
第 2 位学生姓名: 李四
成绩: 95
第 3 位学生姓名: 王五
成绩: 72
===== 成绩汇总 =====
学生人数: 3
平均分: 85.00
最高分: 95.00
最低分: 72.00
逐段分析:
namespace sdu将自定义类型和函数隔离在独立命名空间中,避免与标准库或第三方库冲突- 头文件使用
#ifndef/#define/#endif包含守卫,防止重复包含 main函数中通过sdu::前缀调用自定义命名空间的函数const auto&在print_summary中避免不必要的拷贝- 类型
Student声明为struct(默认 public),适合纯数据聚合
示例二:预定义宏与跨平台编译
场景说明:编写一个能检测编译器、平台和 C++ 标准版本的程序,演示预定义宏的用法。
env_info.cpp
#include <iostream>
#include <string>
std::string compiler_name() {
#if defined(__clang__)
return "Clang " + std::to_string(__clang_major__) + "."
+ std::to_string(__clang_minor__);
#elif defined(__GNUC__)
return "GCC " + std::to_string(__GNUC__) + "."
+ std::to_string(__GNUC_MINOR__);
#elif defined(_MSC_VER)
return "MSVC " + std::to_string(_MSC_VER);
#else
return "Unknown Compiler";
#endif
}
std::string platform_name() {
#if defined(_WIN32) || defined(_WIN64)
return "Windows";
#elif defined(__linux__)
return "Linux";
#elif defined(__APPLE__)
return "macOS";
#else
return "Unknown Platform";
#endif
}
std::string cpp_standard() {
#if __cplusplus >= 202002L
return "C++20 或更新";
#elif __cplusplus >= 201703L
return "C++17";
#elif __cplusplus >= 201402L
return "C++14";
#elif __cplusplus >= 201103L
return "C++11";
#else
return "C++98/03";
#endif
}
int main() {
std::cout << "===== 编译环境信息 =====" << std::endl;
std::cout << "编译器: " << compiler_name() << std::endl;
std::cout << "平台: " << platform_name() << std::endl;
std::cout << "标准: " << cpp_standard() << std::endl;
std::cout << "\n===== 文件信息 =====" << std::endl;
std::cout << "源文件: " << __FILE__ << std::endl;
std::cout << "行号: " << __LINE__ << std::endl;
std::cout << "日期: " << __DATE__ << std::endl;
std::cout << "时间: " << __TIME__ << std::endl;
return 0;
}
编译命令:
g++ -std=c++17 env_info.cpp -o env_info
预期输出(GCC + Linux 环境示例):
===== 编译环境信息 =====
编译器: GCC 11.4
平台: Linux
标准: C++17
===== 文件信息 =====
源文件: env_info.cpp
行号: 50
日期: Jun 13 2026
时间: 14:30:22
逐段分析:
__cplusplus是 C++ 标准预定义宏,值对应标准版本号(C++11 = 201103L、C++17 = 201703L)- 条件编译
#if defined(...)根据编译器宏选择不同代码路径,实现跨平台兼容 __FILE__、__LINE__常用于调试日志输出__DATE__和__TIME__记录编译时间,用于版本追踪
易错场景与面试考点
易错场景
| 场景 | 错误表现 | 正确做法 |
|---|---|---|
在头文件全局作用域写 using namespace std; | 污染所有包含该头文件的翻译单元的命名空间 | 头文件中始终使用 std:: 完全限定名 |
main 函数返回 void | 某些编译器报错或警告 | main 必须返回 int |
| 头文件缺少包含守卫 | redefinition of 'class Foo' | 始终添加 #ifndef/#define/#endif |
| 自定义命名空间与 std 冲突 | 命名歧义 | 使用项目专属前缀如 ns_projectname |
在 .cpp 中直接写函数体而忘记 include 对应头文件 | 另一个 .cpp 找不到声明 | 保持头文件声明 + 源文件实现模式 |
常见面试问题
main函数的合法签名有哪些?可以返回void吗?——标准规定main必须返回int。void main()不符合标准,某些编译器可能拒绝。#include <header>和#include "header"的区别?——尖括号在系统/编译器头文件搜索路径中查找,双引号先在本文件所在目录查找,再按尖括号的方式查找。为什么要避免在头文件中使用
using namespace std;?——头文件会被多个翻译单元包含,using 指令将其命名空间导入作用域,导致潜在的名称冲突,且难以追踪。namespace与class中的static函数有何区别?——命名空间用于逻辑分组、避免名称冲突,是开放的可跨文件扩展;类中的 static 函数属于类作用域,具有封装和访问控制。如何在一个项目中组织自定义命名空间?——通常以项目名或模块名作为命名空间,如
namespace myproject::network,C++17 起支持嵌套命名空间简写。
小结
main函数是程序入口,必须返回int- 头文件负责声明,源文件负责实现,用包含守卫保护头文件
- 使用
namespace隔离标识符,头文件中永远不用using namespace std; __cplusplus、__FILE__、__LINE__等预定义宏用于诊断和跨平台兼容