C++ 类
简单的说,C++ 的类(Class)就是一个“自定义的数据类型”。
如果说 int 是存整数的,double 是存小数的,那么 Class 就是让你创造一个能存“猫”、“汽车”或者“学生”这种复杂概念的容器。
它是 面向对象编程 (OOP) 的核心。为了理解,我们用一个图纸 vs 房子的比喻。
核心概念:图纸 vs 实体
-
类 (Class) = 图纸 (Blueprint)
-
它只是一张设计图,规定了房子有几扇窗、什么颜色、怎么盖。
-
它不占空间(在内存里不占地方,直到你用它造出对象)。
-
-
对象 (Object) = 房子 (Instance)
-
这是根据图纸造出来的真实的房子。
-
它占空间(占用内存)。
-
你可以用同一张图纸(类),造出无数个不同的房子(对象)。
-
类的组成部分
一个类通常包含两样东西:
-
属性 (Attributes / Member Variables):它是什么?(比如:颜色、重量、名字)。
-
行为 (Methods / Member Functions):它能做什么?(比如:跑、叫、计算)。
让我们来设计一个 “狗” (Dog) 的类。
#include <iostream>
#include <string>
using namespace std;
// 1. 定义类 (这是图纸)
class Dog {
public: // 访问权限:公开的 (任何人都能访问)
// --- 属性 (变量) ---
string name;
int age;
// --- 行为 (函数) ---
void bark() {
cout << name << " 正在汪汪叫!" << endl;
}
void introduce() {
cout << "我叫 " << name << ",今年 " << age << " 岁。" << endl;
}
}; // <--- 注意!类定义结束必须有分号 ;
int main() {
// 2. 创建对象 (根据图纸造出一条真狗)
Dog myDog;
// 3. 设置属性
myDog.name = "旺财";
myDog.age = 3;
// 4. 调用行为
myDog.introduce(); // 输出: 我叫 旺财,今年 3 岁。
myDog.bark(); // 输出: 旺财 正在汪汪叫!
// 可以造另一条狗
Dog yourDog;
yourDog.name = "小白";
yourDog.bark(); // 输出: 小白 正在汪汪叫!
return 0;
}
C++ 类的一个关键点:权限控制 (public vs private)
这是 C++ 和 Python 最大的区别之一。C++ 非常注重**“封装”**,也就是保护数据不被随意修改。
-
public(公有): 谁都可以从外部访问(比如上面的name)。 -
private(私有): 只有类自己内部的函数能访问,外部(main函数里)不能直接改。
为什么要有 private? 想象一下,如果把 age 设为 public,别人可能会写 myDog.age = -100;。这显然是不合法的。 如果是 private,我们可以写一个函数来控制:
class Dog {
private:
int age; // 私有的,外面看不见
public:
// 提供一个公开的接口来设置年龄
void setAge(int a) {
age = a; // 合法才修改
}
};
C++ 构造函数
构造函数
构造函数的作用是:**创建对象的那一瞬间,立刻将数据通过给定的数值进行初始化数据。 构造函数的 3 个铁律
-
名字必须和类名完全相同。
-
没有任何返回类型(连
void都不写)。 -
不用手动调用,对象创建时自动触发。
2 代码示例:如何写一个构造函数
我们修改一下刚才的 Dog 类:
#include <iostream>
#include <string>
using namespace std;
class Dog {
public:
string name;
int age;
// --- 这就是构造函数 ---
// 1. 名字叫 Dog (和类名一样)
// 2. 没有返回值
// 3. 接收参数用于初始化
Dog(string n, int a) {
name = n;
age = a;
cout << "构造函数被调用了!一只新狗诞生了!" << endl;
}
void introduce() {
cout << "我叫 " << name << ",今年 " << age << " 岁。" << endl;
}
};
int main() {
// 以前的写法:Dog myDog; (现在这样写会报错,因为我们规定了必须传参数)
// 现在的写法:在创建时直接传参
Dog myDog("旺财", 3);
myDog.introduce();
return 0;
}
运行结果:
构造函数被调用了!一只新狗诞生了!
我叫 旺财,今年 3 岁。
进阶:初始化列表 (Initialization List)
在 C++ 中,你会经常看到一种写法,不在函数体 {} 里赋值,而是在函数名后面加个冒号 :。这叫 初始化列表。
这是 C++ 程序员的“专业写法”,效率更高。
class Dog {
public:
string name;
int age;
// 推荐写法:初始化列表
// 意思是:用参数 n 初始化 name,用参数 a 初始化 age
Dog(string n, int a) : name(n), age(a) {
// 函数体可以是空的
}
};
一个常见的“坑”:默认构造函数
-
当你没写任何构造函数时:C++ 会好心地送你一个看不见的“默认构造函数”
Dog() {},让你能写Dog myDog;。 -
一旦你写了任何一个构造函数(比如上面的
Dog(string n, int a)):C++ 就认为“既然你自定义了,那我就不送了”。
后果: 如果你写了带参数的构造函数,就不能再简单地写 Dog myDog; 了,除非你自己再手动补写一个不带参数的构造函数:
class Dog {
public:
// 1. 带参数的构造函数
Dog(string n, int a) : name(n), age(a) {}
// 2. 手动补上无参数的构造函数 (重载)
Dog() {
name = "流浪狗";
age = 0;
}
};
// 现在下面两种写法都合法了:
Dog d1("旺财", 3); // 调用第 1 个
Dog d2; // 调用第 2 个
C++继承(Inheritance)
一句话理解:**继承就是:让一个类“拥有”另一个类的属性和功能。**生活类比:
- 动物(Animal)是一个大类
- 狗(Dog)是一种动物
- 猫(Cat)也是一种动物
那么:Dog 是 Animal 的一种在 C++ 里写成:
class Dog : public Animal
意思是:Dog 继承了 Animal。
继承示例
第一步:定义父类 Animal
#include <iostream>
#include <string>
using namespace std;
class Animal {
public:
string name;
Animal(string n) : name(n) {
cout << "Animal 构造函数被调用" << endl;
}
void eat() {
cout << name << " 正在吃东西。" << endl;
}
};
第二步:让 Dog 继承 Animal
class Dog : public Animal {
public:
int age;
// 注意这里
// 我们要调用父类的构造函数
Dog(string n, int a) : Animal(n), age(a) {
cout << "Dog 构造函数被调用" << endl;
}
void bark() {
cout << name << " 汪汪叫!" << endl;
}
};
第三步:测试
int main() {
Dog d("旺财", 3);
d.eat(); // 来自父类
d.bark(); // 自己的函数
return 0;
}
运行结果
Animal 构造函数被调用
Dog 构造函数被调用
旺财 正在吃东西。
旺财 汪汪叫!
关键理解点
1. 继承语法
class 子类 : public 父类
public 表示:父类 public 的成员,在子类里仍然是 public
2. 构造函数的调用顺序
当你创建:
Dog d("旺财", 3);
发生了什么?
-
先调用父类构造函数
-
再调用子类构造函数
顺序永远是:
父类 → 子类
3. 子类可以使用父类的成员
因为:
Dog : public Animal
所以:
d.eat();
是合法的。虽然 Dog 没写 eat(), 但它“继承”了。
继承的理解
我们画一个结构图:
Animal
├── name
└── eat()
Dog
├── 继承 name
├── 继承 eat()
└── 新增 age
└── 新增 bark()
Dog 就像:在 Animal 的基础上“扩展”功能。
C++ lambda
基础例子
假如我们要在主函数中实现两个整数相乘,代码如下
#include<iostream>
using namespace std;
int multipy(int x, int y){
return x*y;
}
int main(){
int x =3;
int y =2;
int m;
m = multipy(x,y);
cout<<"x*y= "<<m<<endl;
}
还有一种方式,
#include<iostream>
using namespace std;
int multipy(int x, int y);
int main(){
int x =3;
int y =2;
int m;
m = multipy(x,y);
cout<<"x*y= "<<m<<endl;
}
int multipy(int x, int y){
return x*y;
}
在main函数中使用multipy函数需要在外部定义。Lambda 表达式让你可以直接在代码中“原地”定义函数,特别适合用于回调、简单的逻辑封装以及配合 STL 算法(如排序、遍历)使用。
核心语法结构
Lambda 的基本样子如下:
[捕获列表] (参数列表) mutable -> 返回类型 {
函数体
};
-
[]捕获列表 (Capture Clause): 决定了 Lambda 内部如何访问外部(Lambda 之外)的变量。 -
()参数列表 (Parameters): 和普通函数一样,传入参数。 -
->返回类型 (Return Type): 一般可以省略,编译器会自动推导。 -
{}函数体: 代码逻辑。
#include<iostream>
using namespace std;
int main(){
int x =3;
int y =2;
int m;
auto multipy = [=]()
{
//x=10;
return x*y;
};
m = multipy();
cout<<"x*y= "<<m<<endl;
}
也可以像普通的函数一样,传递参数
#include<iostream>
using namespace std;
int main(){
int x =3;
int y =2;
int m;
auto multipy = [=](int z)
{
return x*y*z;
};
m = multipy(3);
cout<<"x*y*z= "<<m<<endl;
}
可以使用引用,修改使用的值
#include<iostream>
using namespace std;
int main(){
int x =3;
int y =2;
int m;
auto multipy = [&](int z)
{
x=2;
return x*y*z;
};
m = multipy(3);
cout<<"x*y*z= "<<m<<endl;
}
也可以针对特特定的参数进行设置
#include<iostream>
using namespace std;
int main(){
int x =3;
int y =2;
int m;
auto multipy = [=,&x](int z)
{
x=2;
//y=1;
return x*y*z;
};
m = multipy(3);
cout<<"x*y*z= "<<m<<endl;
}
| 语法 | 含义 | 例子 |
|---|---|---|
[] | 不捕获任何外部变量 | [](){ return 0; } |
[=] | 按值捕获所有外部变量 (只读副本) | [=](){ return x + y; } |
[&] | 按引用捕获所有外部变量 (可读写) | [&](){ x++; } |
[x] | 只按值捕获 x | [x](){ return x * 2; } |
[&x] | 只按引用捕获 x | [&x](){ x = 0; } |
[=, &y] | 默认按值捕获,但 y 按引用捕获 | [=, &y](){ y = x + 1; } |
mutable | 允许在 Lambda 内修改按值捕获的副本 | [x]() mutable { x++; } |