cpp 编译
使用 g++ 进行编译。
跟 c 唯一的区别就是从 gcc 变成了 g++。
用 gcc 编译 cpp 程序可能会出问题,无法编译成功。
g++ 与 gcc 的区别
| 编译器 | 主要用途 | 默认行为 |
|---|---|---|
gcc | 编译 C 代码 | 链接 C 标准库 |
g++ | 编译 C++ 代码 | 链接 C++ 标准库 |
这两个底层都是GNU 编译器集合的前端,共享大部分代码,就是链接的标准库不同。
gcc 连接 c 标准库。
g++ 连接 cpp 标准库。
class
类就是蓝图。
访问控制,字段与方法
cpp 中的 class 根据访问权限来写数据,其实感觉还挺清爽的,很有区分度:
class Student{
private:
std::string name;
int score
int id;
public:
bool isAlive;
int getId(){
return id;
}
}构造函数与析构函数
构造函数
用来创建对象的时候,进行初始化。
写法:
class Student{
private:
std::string name;
int age;
public:
Student(std::string pName, int pAge){
//这里的p代表的是parameter参数,用来与字段区分
name = pName;
age = pAge;
}
}更简单与推荐的写法:
public:
Student(std::string pName, int pAge): name(pName), age(pAge){}前者与后者在外部表现虽然一样,但是底层完全不同,后者是初始化,前者是默认初始化完了再去赋值。
所以后者更加高效比起前者,推荐写后者,虽说与 java 不太一样。
cpp 中也有 this 指针,但是同样不推荐使用,性能比起初始化要差。
析构函数
对象销毁的时候自动调用析构函数。
前面加个 ~
class Student{
public:
~Student(){
}
}类内定义与类外定义
类内定义就是写类的时候直接写在 .cpp 中,定义也就是在类的内部直接写了。
这个其实不好,因为如果类多了,类直接的交互也多了,就非常麻烦,你总是要向前定义,比如你创建了一个 Monster 类,需要将 Player 类作为对象,为了程序能够进行不报错,就得把 Player 写在 Monster 前面,但是 Player 类也需要将 Monster 类作为对象,那就没法写了。
更推荐类外定义,也就是将类写在 .h 或者 .hpp 里面,类内也都是一些函数原型,而不是函数定义。
真的定义在 .cpp 文件中,Player::display(){} 进行定义。
令人烦恼的解析
无参数创建一个对象是:
Game game;
// 或者cpp风格
Game game{};
// 而不是Game game() 这是声明一个函数,创建一个game函数,返回Game类型,无参数vector
vector 数组是一个对象,而不像是 c 语言中的数组一样只是一个指针。
cpp 中的三种参数传递
传引用
void popOne(vector<int> &nums) { // 引用,修改原对象
nums.pop_back();
}
// 调用
popOne(nums); // 直接传递传指针
void popOne(vector<int> *nums) { // 指针,需要解引用
if (nums != nullptr) {
nums->pop_back();
}
}
// 调用
popOne(&nums); // 需要取地址传值(这个是错误的)
void popOne(vector<int> nums) { // 值传递,创建副本
nums.pop_back(); // 只修改副本,不影响原对象!
}
// 调用
popOne(nums); // 原nums不会被修改规则
// 声明时有 & → 调用时不要 &
void func(Type& param); // 调用: func(variable)
// 声明时有 * → 调用时要 &
void func(Type* param); // 调用: func(&variable)cpp 中的 lambda 表达式
[](参数){函数体}为什么使用 Lambda
| 优点 | 说明 |
|---|---|
| 简洁 | 不需要单独定义函数 |
| 封装 | 逻辑紧挨着使用的地方 |
| 灵活 | 可以捕获外部变量 |
| 现代 | C++11+ 的标准写法 |
cpp 版本
C++ 标准发布周期:
- C++98 - 1998年(第一个国际标准)
- C++03 - 2003年(小修订)
- C++11 - 2011年(重大更新,现代C++的开始)
- C++14 - 2014年(小改进)
- C++17 - 2017年(重要更新)
- C++20 - 2020年(重大更新)
- C++23 - 2023年(最新版本)
最多人用的版本
c++17。
这个也是当前的主流。
lazyvim 自带的 clangd 默认最新启用到 c++17,用 c++20 的一些函数与语法会报错。
auto
c++11 引入的一个类型推导关键字,让编译器自动推断变量类型。
类似于 java 中的 var。
好处是更简洁,不用写冗长的类型名。
如果容器类型改变,也不需要修改迭代器的类型了。
性能
编译时,auto 会被替换为具体类型,生成的机器码完全一样。
所以性能与直接写类型名没有任何区别,不影响运行性能,可能编译时会慢一丢丢。
cpp 中的继承与多态
继承
class Base {
public:
virtual void attack() { cout << "Base attack" << endl; }
};
class Derived : public Base { // 公有继承
public:
void attack() override { cout << "Derived attack" << endl; }
};- 用
virtual声明虚函数 - 用
override明确重写
多态
Base* ptr = new Derived();
ptr->attack(); // 输出 "Derived attack" - 多态通过基类指针/引用调用实现多态
迭代器
迭代器是泛化的指针。
提供了类似指针的接口,但是功能更加强大。
迭代器是指针的抽象。
cpp 中的 false 值
- 布尔类型,false
- 空指针,nullptr
- 整数,0
- 空指针常量,NULL,这个是 C 风格的,不推荐
- 浮点数,0.0
- 空字符,
'\0' - 空字符串,这个要注意,因为指针非空也会执行
除此之外的其他值就都是 true 了
cpp 中的 stack
stack.pop() 没有返回值。
这是为了异常安全的考虑。
pop() 返回被移除的元素,需要在返回前进行拷贝,如果拷贝过程中发生异常,元素既被移出栈又无法正确返回,就会导致数据丢失
- Java:
pop()有返回值 - Python:
pop()有返回值 - C#:
Pop()有返回值 - C++:
pop()无返回值
cpp 中的循环依赖
指的是两个或多个模块互相引用对方,也就是:
// 文件 A.h
#include "B.h"
class A {
B* b; // A 依赖 B
};
// 文件 B.h
#include "A.h"
class B {
A* a; // B 依赖 A → 循环依赖!
};会导致编译错误。
解决方法
使用前向声明,只声明一个 class B,来替代头文件。
cpp 中的函数修饰符位置
class Comparison {
public:
// 函数"类型"修饰符 - 在前面
// 影像函数的性质
virtual void func1(); // 虚函数
static void func2(); // 静态函数
inline void func3(); // 内联函数
// 函数"行为"修饰符 - 在后面
// 是函数的行为约束
void func4() const; // 常量成员函数
void func5() override; // 重写确认
void func6() final; // 禁止重写
void func7() = 0; // 纯虚函数
};虚函数与纯虚函数
| 特性 | 虚函数 | 纯虚函数 |
|---|---|---|
| 语法 | virtual void func(); | virtual void func() = 0; |
| 实现 | 有默认实现 | 没有实现 |
| 类性质 | 可以是具体类 | 使类成为抽象类 |
| 实例化 | 可以实例化 | 不能实例化 |
| 重写要求 | 派生类可选重写 | 派生类必须重写 |
虚函数
#include <iostream>
class Animal {
public:
virtual void makeSound() { // 虚函数 - 有默认实现
std::cout << "Animal makes a sound" << std::endl;
}
};
class Dog : public Animal {
public:
void makeSound() override { // 可以选择重写
std::cout << "Woof! Woof!" << std::endl;
}
};
class Cat : public Animal {
// 没有重写 makeSound(),使用基类的默认实现
};
int main() {
Animal animal; // 可以实例化
Dog dog;
Cat cat;
animal.makeSound(); // 输出: Animal makes a sound
dog.makeSound(); // 输出: Woof! Woof!
cat.makeSound(); // 输出: Animal makes a sound
}纯虚函数
#include <iostream>
class Shape { // 抽象类
public:
virtual double area() const = 0; // 纯虚函数
virtual void draw() const = 0; // 纯虚函数
// 可以有非虚函数
void printInfo() const {
std::cout << "Area: " << area() << std::endl;
}
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
// 必须实现所有纯虚函数
double area() const override {
return 3.14159 * radius * radius;
}
void draw() const override {
std::cout << "Drawing Circle" << std::endl;
}
};
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() const override {
return width * height;
}
void draw() const override {
std::cout << "Drawing Rectangle" << std::endl;
}
};
int main() {
// Shape shape; // 错误!不能实例化抽象类
Circle circle(5.0);
Rectangle rect(4.0, 6.0);
circle.printInfo(); // 输出: Area: 78.5397
rect.printInfo(); // 输出: Area: 24
Shape* shapes[] = {&circle, &rect};
for (auto shape : shapes) {
shape->draw(); // 多态调用
}
}h 与 hpp 头文件辨析
.h 更通用,.hpp 明确声明这是一个 cpp 项目。
两者选其一即可,但是不要混用。
调用 cpp 最大值
使用 limits 头文件。
#include <limits>
// 整数类型最大值
int max_int = std::numeric_limits<int>::max();
long max_long = std::numeric_limits<long>::max();
// 浮点数类型最大值
float max_float = std::numeric_limits<float>::max();
double max_double = std::numeric_limits<double>::max();
// 最小值
int min_int = std::numeric_limits<int>::min();cpp 中 struct 与 class 的区别
主要区别在默认的访问控制和默认的继承方式。除此之外,它们在功能上是完全相同的。
默认访问权限
- struct: 默认成员是
public - class: 默认成员是
private
默认继承方式
- struct: 默认是
public继承 - class: 默认是
private继承
虽说功能相同,但是两者有使用惯例:
- struct:用于包含数据的简单数据结构
- class: 用于具有复杂行为和封装的对象