Untitled
Overview
- 课程介绍与评分体系 (Course Overview & Grading)
- C++ 中的变量与基本类型 (Variables & Types)
- 封装 (Encapsulation) 与 类的定义 (Class Structure) —— 结合
sphere.h/sphere.cpp - 命名空间 (Namespaces) —— 结合代码中的
cs225 - 构造函数 (Constructors) —— 默认构造函数 vs 自定义构造函数
- 指针入门 (Pointers Introduction)
C++ 中的变量与基本类型 (Variables & Types)
变量的四大属性
“Every variable is defined by four properties”
- Name (名称): 变量的标识符(例如
myFavoriteInt,s) - Type (类型): 它是什么样的数据(例如
int,Sphere),类型决定了它占用多少内存以及如何解释这块内存 - Value (值): 存储的具体内容(例如
42, 半径为1.0) - Location (地址/内存位置): 它在计算机内存中的具体位置
变量的两大分类
C++ 是强类型 (Strongly Typed) 的语言,变量分为两类 :
- 基本类型 (Primitive/Fundamental Types):
- 语言内置的,直接支持的类型
- 例子:
int myFavoriteInt;(整数)char grade = 'A';(字符)double gamma = 0.653;(双精度浮点数)
- 用户定义类型 (User-Defined Types / Class Types):
- 创建新的结构来存储数据
- 例子:
Sphere myFavoriteSphere;(球体对象)Cube rubix;(立方体)Grade courseGrade;(成绩对象)
封装 (Encapsulation) 与类的定义 (Class Structure)
封装
Encapsulation is a separation of interface from implementation.
封装带来的两大好处
- 隐藏复杂性 (Hides complexity):用户只需要知道它能做什么(接口),而不需要知道它是怎么做的(实现)
- 数据保护 (Allows private data to be private):防止外部代码随意篡改对象的内部状态
C++ 的实现方式:
C++ 在语言层面拥抱了封装,将接口 (Interface) 定义在 .h 文件中,将实现 (Implementation) 写在 .cpp 文件中
sphere.h (接口文件/头文件)
sphere.h 讲清楚用户定义的Sphere 类长什么样
1 |
|
解析:
- Header Guards (
#ifndef,#define,#endif): 这是一个标准的 C++ 惯例,防止同一个头文件被多次#include,避免编译错误 class Sphere { ... };: 定义一个新的类型Sphere,类定义的最后必须有一个分号;public:列出的函数是公开的,任何人(包括main.cpp)都可以调用这些函数,比如s.getRadius(),构成了接口private:列出的变量是私有的只有Sphere类内部的函数可以访问,外部代码(如main.cpp)如果试图直接访问s.r_,编译器会报错,这实现了数据保护- 这里的成员变量名为
r_在 C++ 中,习惯给私有成员变量加下划线后缀(或前缀),以便与普通变量区分
- 这里的成员变量名为
sphere.cpp (实现文件)
1 |
|
解析:
#include "sphere.h":.cpp文件必须知道它要实现的类长什么样,所以必须包含头文件Sphere::(Scope Resolution Operator):- 它告诉编译器下面定义的这个
getRadius函数,不是一个普通的全局函数,而是属于Sphere类的成员函数 - 正因为有了
Sphere::,在这个函数内部,才能直接使用r_这个私有变量
- 它告诉编译器下面定义的这个
命名空间 (Namespaces)
为什么要用命名空间?
世界上可能有很多其他的 Sphere 类,如何区分它们
- 命名空间就像是文件夹或者人的姓氏
- CS225 课程里的 Sphere 是
cs225::Sphere(cs225 家族的 Sphere) - 标准库里的
vector是std::vector(std 家族的 vector)
代码中的实现
sphere.h
1 | namespace cs225 { // 打开命名空间 |
这就好比把 Sphere 类定义在了一个叫 cs225 的包裹里
sphere.cpp 实现函数时,也必须包裹在同样的命名空间里,或者明确指出是谁:
1 | namespace cs225 { |
如何使用?(main.cpp)
方式 1:全名调用 (Explicitly)
这是最严谨的写法
1 | int main() { |
方式 2:使用 using 指令 (Simplify the Syntax)
如果在 main.cpp 里一直写 cs225:: 会很麻烦,告诉编译器:如果在当前文件里找不到某个名字,就去 cs225 或者 std 命名空间里找找看
1 |
|
构造函数 (Constructors)
什么是构造函数
- 构造函数是当创建一个类的新对象时,被调用的第一个函数,它的主要任务是:初始化对象的状态(给变量赋初值)
- 它的名字和类名完全相同(例如
Sphere) - 它没有返回值类型(连
void都不写)
三种构造函数的情况
场景 1:自动默认构造函数 (Automatic Default Constructor)
如果 sphere.h 里完全没有写任何构造函数,C++ 编译器会有一个自动默认构造函数
- 它不需要参数
- 它不做任何实质性的初始化工作(对于基本类型如
double,它的值可能是内存里的垃圾值,这很危险) - 只有当没定义任何构造函数时,C++ 才会有这个 constructor
场景 2:自定义默认构造函数 (Custom Default Constructor)
在 sphere.cpp 中明确定义了一个不带参数的构造函数:
1 | // sphere.h |
当写 Sphere s; 时,这个函数会被调用此时 s 的半径就是 1.0,不再是随机垃圾值
场景 3:带参数的构造函数 (Custom Parameterized Constructor)
定义了允许用户指定半径的构造函数 :
1 | // sphere.h |
当写 Sphere s(5.0); 时,这个函数被调用,s 的半径就是 5.0
注意
假设只定义了带参数的构造函数 Sphere(double r),而删掉了不带参数的 Sphere()
在 main.cpp
1 | int main() { |
为什么会报错?
- 写了
Sphere s;,这意味着想调用不带参数的构造函数 - 但是在代码里删掉了
Sphere() - 编译器以为用户想要用默认构造函数,但是自己定义了一个
Sphere(double r)但是由于有定义的带参数的构造函数,就不存在自动默认构造函数了
结论:如果定义了带参数的构造函数,并且还希望用户能用 Sphere s; 这种方式创建对象,就必须显式地把 Sphere() 写出来
指针入门 (Pointers Introduction)
什么是指针
普通变量存储的是数据本身,而指针存储的是数据的内存地址 (Memory Address) ,可以想象成一个路标或门牌号,它指向真正存放数据的地方
语法符号
*(声明指针): 放在类型后面,表示这是一个指针变量Sphere s1;说明s1是一个球体对象Sphere *s2;说明s2是一个指向球体对象的指针
&(取地址符): 获取某个变量在内存中的地址s2 = &s1;-> 把s1的地址赋值给s2,现在s2指向了s1
->(箭头运算符): 指针的成员访问符
如何访问成员?(. Vs ->)
-
对象 (Object) -> 使用 点号 (
.)1
2Sphere s1;
s1.getRadius(); // 正确 -
指针 (Pointer) -> 使用 箭头 (
->)1
2
3Sphere s2 = &s1;
s2->getRadius(); // 正确
// s2.getRadius(); // 错误!s2 只是个地址,本身没有 radius
s2->getRadius()的意思是:顺着s2指向的地址找到那个对象,然后调用它的getRadius
不使用箭头 ->,用指针访问成员
先解引用 (Dereference): (*s2).getRadius()
*s2表示取出 s2 指向的那个对象(也就是s1)- 既然取出了对象,就可以用点号
.了 - 注意:括号
()不能省,因为.的优先级比*高
总结:s2->getRadius() 等价于 (s2).getRadius(),但前者更简洁,是 C++ 通用写法
Q&A
宏 (Macro) 与 头文件保护 (Header Guard)
- 什么叫 Macro (宏)
- 在 C++ 中,以 # 开头的指令(如
#include,#define)都不是给编译器看的,而是给预处理器 (Preprocessor) 看的,预处理器就像一个文本替换工具(类似 Word 里的查找替换),它在真正编译代码之前工作 #define SPHEREH的意思就是:在预处理器的记事本上,记下一个叫 SPHEREH 的标记(Flag)它不代表具体的值,它只是存在而已
- 在 C++ 中,以 # 开头的指令(如
- 每一个头文件都需要
#define吗- 几乎每个头文件都需要,是为了防止代码被重复粘贴导致的冲突
- 当写
#include "sphere.h"时,预处理器把 sphere.h 里的代码原封不动地复制粘贴到 main.cpp 里,它不管文件名叫什么 - 为什么要防重复: 假设有一个 math.h 引用了 sphere.h, main.cpp 同时也引用了 sphere.h ,如果不加保护,sphere.h 的代码会被复制粘贴两次,进入 main.cpp,编译器看到两个 class Sphere { … }; 定义时,就会报错:Sphere 类重定义!(Class redefinition error)
- Header Guard 的工作流程: 这三行代码是一个整体,缺一不可 :
1 |
|
- 作用域解析运算符
Sphere::Sphere()- :: (Scope Resolution Operator)
- sphere.h 是声明 (Declaration):像是菜单,告诉大家有这道菜
- sphere.cpp 是定义 (Definition):像是厨房,具体做这道菜
- 在 sphere.cpp 文件里,在写代码时,默认是在全局环境下的
1 | // 错误的写法 |
编译器会认为在写一个普通的全局函数 getRadius,这个函数不属于任何类,也就找不到 Sphere 类里的私有变量 r
所以,需要用 :: 来找到函数的来源:
`
1 | // 正确的写法 |
对于构造函数 Sphere::Sphere() 也是同理:
- 第一个 Sphere:类名(是哪个家族的)
- ::属于
- 第二个 Sphere:函数名(构造函数的名字和类名一样)
- Sphere::Sphere() ` 的意思是 —— 现在要定义属于 Sphere 类的那个名为 Sphere 的构造函数

