Skip to content

C++ 基础:从编译运行到函数与对象 ​

先运行一个不含自定义类的程序,再学习值、引用、函数和对象生存期。本文使用 C++11 及以上的基础写法,示例只依赖标准库;继承、虚函数和模板另作后续学习。

1. 编译并运行 ​

将下面代码保存为 hello.cpp:

cpp
#include <iostream>

int main() {
    std::cout << "Hello, C++!\n";
    return 0;
}

#include 引入头文件中的声明,std:: 指明标准库命名空间,main 是可执行程序的入口。<< 把内容写入输出流,\n 换行,return 0 表示正常退出。

sh
g++ -std=c++11 -Wall -Wextra hello.cpp -o hello
./hello

预期输出 Hello, C++!。Windows 使用 GCC 时可输出 hello.exe,再执行 ./hello.exe。工程通常经过编译与链接,目标文件的扩展名随工具链变化;不要把 .obj、.exe 当成所有平台的固定规则。

2. 值、初始化与流程控制 ​

将 main 的函数体替换为:

cpp
int completed = 0;
const int goal = 3;
for (int day = 0; day < goal; ++day) {
    completed += 1;
}
if (completed == goal) {
    std::cout << "done: " << completed << "\n";
}

输出 done: 3。int 保存整数,const 不允许通过这个名字修改对象;先初始化再读取局部变量。for 依次执行初始化、条件判断和步进,if 按条件选择分支。普通语句用分号结束;花括号组成作用域。

类型转换不等于把类型简单排成“低级到高级”。需要取整时可写 static_cast<int>(2.8),得到 2;整数转换前仍要考虑目标范围。const_cast 不属于初学者绕过只读约束的常规工具,对本来定义为 const 的对象强行写入会导致未定义行为。

3. 函数、传值与引用 ​

cpp
#include <iostream>

int add(int value, int delta = 1) {
    return value + delta;
}

void add_in_place(int& value, int delta) {
    value += delta;
}

int main() {
    int count = 1;
    std::cout << add(count) << " " << count << "\n";
    add_in_place(count, 2);
    std::cout << count << "\n";
}

输出两行 2 1、3。传值参数接收独立的值;int& 是原对象的别名,通过引用修改会影响调用方。引用初始化后不能改为另一个对象的别名,也不能返回已经销毁的局部对象的引用。

默认参数在声明处提供;这个普通参数列表中,有默认值的参数放在右侧。add(count) 使用默认增量,add(count, 3) 显式指定。函数重载按参数列表区分,不能只靠不同返回类型区分。inline 不保证调用被展开,不应为了“更快”给所有函数加上它。

4. 字符串、容器与只读参数 ​

cpp
#include <iostream>
#include <string>
#include <vector>

int total(const std::vector<int>& scores) {
    int result = 0;
    for (int score : scores) {
        result += score;
    }
    return result;
}

int main() {
    std::vector<int> scores{80, 90, 100};
    const std::string name = "Ada";
    std::cout << name << ": " << total(scores) << "\n";
    if (name.find('z') == std::string::npos) {
        std::cout << "not found\n";
    }
}

输出 Ada: 270、not found。std::vector<int> 管理一组整数,std::string 管理文本字节;它们分别需要 <vector> 和 <string>。const T& 避免复制并限制通过该引用修改对象。标准字符串不自动把 UTF-8 下标变成汉字序号。

size() 查看大小,empty() 检查是否为空,find() 找不到时返回 std::string::npos,不能依赖有符号 -1 的写法。容器会负责自己的资源管理;排序、树和其他算法应到数据结构与算法专题学习,这里只讲如何存取业务数据。

5. 类、构造与成员访问 ​

在变量、函数和引用都清楚之后,再运行对象示例:

cpp
#include <iostream>

class Counter {
public:
    explicit Counter(int start) : value_(start) {}
    void add(int delta) { value_ += delta; }
    int value() const { return value_; }
private:
    int value_;
};

int main() {
    Counter original(1);
    Counter copied = original;
    Counter& alias = original;
    alias.add(2);
    std::cout << original.value() << " " << copied.value() << "\n";
}

输出 3 1。类把状态和方法放在一起,构造函数与类同名;初始化列表 value_(start) 初始化成员。explicit 限制不希望出现的隐式转换。这里的类只含一个整数,默认复制会得到独立的整数成员;不要把这个结论套到自己管理裸指针资源的类上。

写法作用
public对外提供可访问的接口
private限制成员访问,类外通过公开方法使用对象
protected在继承关系中按规则允许派生类访问,初学例子不需要它
int value() const只读成员函数,可通过只读对象调用
object.method()用对象或引用调用成员
pointer->method()用有效对象指针调用成员,不能对空指针或悬空指针使用

类内写成员函数定义最便于当前例子阅读;类外定义需要 Counter::方法名。struct 默认公开、class 默认私有,两者均可定义成员函数。友元是显式授予某个函数或类访问权限,不应当成自动提速或绕过设计边界的手段。

6. 对象生存期与资源管理 ​

当前例子的自动对象在离开作用域时销毁;类的析构函数使用 ~类名(),负责在对象销毁时执行清理。成员对象也有各自的析构过程,不能把“默认析构函数什么也不做”理解成容器资源不会释放。

初学工程优先用 std::string、std::vector 和明确的作用域管理资源。手动 new/delete 是另外的所有权责任:普通对象与数组的释放方式不同,不能混用 delete 和 delete[];也不能多次释放,或在释放后继续解引用。随后再学习智能指针和 RAII,不必先写裸指针链表才能理解类。

static 成员与对象实例字段不同,可能形成共享状态;命名空间作用域、局部作用域和链接也不是同一个概念。把对象状态作为参数传递,通常比引入全局可变变量更便于推理和测试。

7. 练习与后续顺序 ​

  1. 给 add 传入第二个参数,解释原来的 count 为何不变。
  2. 给成绩列表增加一个元素,检查总和。
  3. 给 Counter 增加 reset(),通过公开方法清零。
  4. 写一个接收 const Counter& 的输出函数,说明为何它只能调用只读成员函数。

先能独立完成这些例子,再学习智能指针与 RAII、异常、继承与虚函数、模板和标准库算法。设计模式属于解决设计问题的后续内容,见设计模式专题,不充当语言语法前置。

参考解法与预期(先完成练习)
  1. 在函数示例中用 add(count, 3) 替换 add(count),第一行为 4 1,第二行仍为 3:传值参数没有修改原对象。
  2. 将成绩改为 {80, 90, 100, 70},输出 Ada: 340,第二行仍为 not found。
  3. 在 Counter 的 public 区加入 void reset() { value_ = 0; };在原对象示例输出之后调用 original.reset(),再打印两者,预期 0 1。
  4. 将以下函数放在 Counter 定义之后、main 之前,调用 show(original):
cpp
void show(const Counter& counter) {
    std::cout << counter.value() << "\n";
}

value() const 可由只读引用调用;add 和 reset 不能通过该引用调用。这里的只读约束不禁止其他可写别名修改对象。

本文示例未在本轮编译或执行,所列输出为静态推演预期。

原课程的配套代码可作补充;编译标准和代码版本应分别确认。规则核对:引用、默认参数、初始化、析构。

继续阅读