C++ 基础:从编译运行到函数与对象
先运行一个不含自定义类的程序,再学习值、引用、函数和对象生存期。本文使用 C++11 及以上的基础写法,示例只依赖标准库;继承、虚函数和模板另作后续学习。
1. 编译并运行
将下面代码保存为 hello.cpp:
#include <iostream>
int main() {
std::cout << "Hello, C++!\n";
return 0;
}#include 引入头文件中的声明,std:: 指明标准库命名空间,main 是可执行程序的入口。<< 把内容写入输出流,\n 换行,return 0 表示正常退出。
g++ -std=c++11 -Wall -Wextra hello.cpp -o hello
./hello预期输出 Hello, C++!。Windows 使用 GCC 时可输出 hello.exe,再执行 ./hello.exe。工程通常经过编译与链接,目标文件的扩展名随工具链变化;不要把 .obj、.exe 当成所有平台的固定规则。
2. 值、初始化与流程控制
将 main 的函数体替换为:
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. 函数、传值与引用
#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. 字符串、容器与只读参数
#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. 类、构造与成员访问
在变量、函数和引用都清楚之后,再运行对象示例:
#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. 练习与后续顺序
- 给
add传入第二个参数,解释原来的count为何不变。 - 给成绩列表增加一个元素,检查总和。
- 给
Counter增加reset(),通过公开方法清零。 - 写一个接收
const Counter&的输出函数,说明为何它只能调用只读成员函数。
先能独立完成这些例子,再学习智能指针与 RAII、异常、继承与虚函数、模板和标准库算法。设计模式属于解决设计问题的后续内容,见设计模式专题,不充当语言语法前置。
参考解法与预期(先完成练习)
- 在函数示例中用
add(count, 3)替换add(count),第一行为4 1,第二行仍为3:传值参数没有修改原对象。 - 将成绩改为
{80, 90, 100, 70},输出Ada: 340,第二行仍为not found。 - 在
Counter的public区加入void reset() { value_ = 0; };在原对象示例输出之后调用original.reset(),再打印两者,预期0 1。 - 将以下函数放在
Counter定义之后、main之前,调用show(original):
void show(const Counter& counter) {
std::cout << counter.value() << "\n";
}value() const 可由只读引用调用;add 和 reset 不能通过该引用调用。这里的只读约束不禁止其他可写别名修改对象。
本文示例未在本轮编译或执行,所列输出为静态推演预期。
继续阅读
- 排错
- 开发实践
- 开发实践