文章目录
- Basic
-
-
-
-
- 1、std::是什么
- [2、`&` (取地址符)和 `*` 的含义](#2、
&(取地址符)和*的含义) - 3、C++中的遍历
- 4、C++中数组和动态数组vector
- [5、map & set 常用操作](#5、map & set 常用操作)
- 6、static
- 7、nullptr现代空指针
- 8、ecplicit禁止隐式转换
- 9、构造函数的初始化&调用
- 10、const
- 11、virtual虚拟函数和override重写
- [12、`->` 和 `.` 的区别](#12、
->和.的区别) - 13、constexpr编译期计算
- 14、你还不知道using的几种用法吗
- [15、enum class(强类型枚举)](#15、enum class(强类型枚举))
-
-
-
Basic
1、std::是什么
shell
auto d = std::string("world");
std::cout << d << " ";
std 是 standard(标准) 的缩写,它是 C++ 标准库的 命名空间(namespace) 。如果不想每次都写 std::,可以在文件开头写using namespace std;,之后就可以直接写cout、string、vector了。但在大型项目中不推荐这样做,因为不同库可能有同名函数,会产生命名冲突。
2、& (取地址符)和 * 的含义
shell
int x = 10;
const int* p = &x; // p 指向 x,p 记住了 x 的地址
// *p = 20; // ❌ 不行!const 说"指针指向的内容不能改"
x = 30; // ✅ 可以,直接改 x 本身没问题
std::cout << *p << "\n"; // 输出 30,因为 p 指向 x,x 已经变成了 30
// &x → 0x01 (x 的地址)
// p → 0x01 (p 里存的值,就是 x 的地址)
// *p → 10 (顺着地址找到里面存的值)
| 符号 | 什么时候 | 含义 | 口语理解 |
|---|---|---|---|
&x |
表达式中 | 取 x 的地址 | "x 住哪儿?" |
int* p |
声明时 | p 是指针 | "p 是个专门存地址的变量" |
*p |
表达式中 | 取 p 指向的值 | "顺着地址看看里面是谁" |
其他,注意在遍历的时候,如果想对原数值进行修改一定要遍历读取引用,否则是复制一份的数据修改不了原来数组
shell
std::vector<int> scores {12,23,45,123,345};
for(int& score : scores){
score++;
}
//score原数组中的值
//&score原数组中的值的地址
3、C++中的遍历
使用 :
shell
#include <vector>
auto vec = std::vector{1,2,3};
// 写法一:传统 for(和 JS/C/Java 一样)
for (int i = 0; i < vec.size(); i++) {
std::cout << vec[i] << " ";
}
// 写法二:范围 for(更简洁,推荐)
for (auto x : vec) {
std::cout << x << " ";
}
4、C++中数组和动态数组vector
shell
int vec = std::vector{1,2,3}; //动态数组,自动扩容不固定大小
int arr[] = {1,2,3} ; //常规数组
数组常用操作
shell
auto vec = std::vector{1,2,3};
vec.push_back(4); //末尾加
vec.size(); //长度计算
vec[0]; //取值
vec.pop_back(); //删除末尾
vec.empty(); //判断是否为空
5、map & set 常用操作
- map集合
shell
std::map<string,int> ages = {{'jack',18},{'eric',12}}; //声明
ages['jack']; //18
ages['vicky'] = 18;
ages.size(); //3
ages.count('bob'); //false,count是判断是否存在
for(auto [name,age] : ages){
std::cout << name << "is" << "age" << "\n";
}
- set不重复几何
shell
std::set<int> nums = {1,2,3,4,5};
nums.insert(9); //插入
nums.erase(2); //擦除
nums.count(5); //是否存在
numns.size(); //大小
6、static
static声明的变量只初始化一次,并且在函数调用执行完成之后不会销毁
shell
void counter (){
static int count = 0;
count++;
std::cout << count << "\n";
}
int main(){
counter(); //1
counter(); //2
counter(); //3
}
7、nullptr现代空指针
比 NULL 更安全、更清晰,避免类型混淆。
NULL其实是 C 语言的宏,本质就是整数0。nullptr有自己专属的类型std::nullptr_t,只能赋值给指针,不会被当成整数。nullptr 的类型就是指针。
shell
int* p = nullptr; // 空指针,安全
if (p == nullptr) { // 判断指针是否为空
std::cout << "p 是空指针\n";
}
// 也可以简写为:
if (!p) { // 等价写法
std::cout << "p 是空指针\n";
}
8、ecplicit禁止隐式转换
ecplicit Distance(double m) : meters(m) {}
shell
#include <iostream>
class Distance {
private:
double meters;
public:
// explicit Distance(double m) : meters(m) {} // 加explicit更安全
Distance(double m) : meters(m) {} // 不加会隐式转换
double get() const { return meters; }
};
void print(double d) {
std::cout << "距离: " << d << " 米\n";
}
int main() {
Distance d = 500; // 如果构造函数不是explicit,这里允许
print(d); // 隐式把 Distance → double
// print(500); // 如果加了explicit,这行会报错
}
9、构造函数的初始化&调用
这真的,,,前端看到震惊写法(孤陋寡闻了,,,
shell
#include <iostream>
class Distance {
private:
double meters;
public:
Distance(double m) : meters(m) {} // 不加会隐式转换
double get() const { return meters; }
};
int main() {
Distance d = 500; // 如果构造函数不是explicit,这里允许
print(d); // 隐式把 Distance → double
// print(500); // 如果加了explicit,这行会报错
}
- 构造函数定义:和类名同名的函数,就是构造函数
- 写法、功能分析:Distance(double m) : meters(m) {}
shell
Distance(double m) : meters(m) {} //构造函数,功能同下函数体内赋值
Distance(double m) {
meters = m;
}
//WHY?为什么多此一举这样写?因为有些情况下,函数体内赋值做不到,必须用初始化列表
//如下:
class Example {
const int id; // const 成员,必须在创建时就赋值
const std::string& name; // 引用成员,必须在创建时就绑定
// ✅ 初始化列表------创建时直接赋值
Example(int i, std::string& n) : id(i), name(n) {}
// ❌ 函数体内赋值------做不到!
Example(int i, std::string& n) {
id = i; // 报错!const 不能赋值
name = n; // 报错!引用不能重新绑定
}
};
- Distance d = 500 为什么可以?
shell
Distance d = 500; //自动翻译成下面
Distance d = Distance(500); //同Distance d{500};
- 多个变量值怎么初始化构造函数
shell
class Box {
double length, width, height;
public:
Box(double l, double w, double h) : length(l), width(w), height(h) {}
};
//初始化
// 写法一:直接初始化(最常用)
Box b(1.0, 2.0, 3.0);
// 写法二:列表初始化(C++11,推荐)
Box b{1.0, 2.0, 3.0};
// 写法三:先构造再赋值
Box b = Box(1.0, 2.0, 3.0);
// 写法四:列表初始化 + auto(C++17,最简洁)
auto b = Box{1.0, 2.0, 3.0};
10、const
-
const跟在函数后面,意思是:"这个函数承诺不修改对象内部的任何数据"shellclass Dog : public Animal { std::string name = "旺财"; void speak() const { // ✅ 可以读数据 std::cout << name << " 汪汪汪!\n"; // ❌ 不能改数据,编译器会报错 // name = "小白"; } void rename(std::string n) { // 没有 const name = n; // ✅ 可以改,因为这个函数没承诺"只读" } }; -
const 对象只能调用 const 函数
shellconst Dog myDog; // const 对象 myDog.speak(); // ✅ 可以,speak() 有 const,承诺不改 myDog.rename("小白"); // ❌ 报错!rename() 没 const,const 对象不能调用
11、virtual虚拟函数和override重写
- 有了
virtual,编译器会去看实际创建的对象是什么,然后调用对应的函数。
shell
class Animal {
public:
virtual void speak() { std::cout << "某种动物叫声\n"; }
};
class Dog : public Animal {
public:
void speak() override { std::cout << "汪汪汪!\n"; }
};
Animal* pet = new Dog();
pet->speak(); // 输出:汪汪汪!
// ✅ 程序运行时发现实际对象是 Dog,调用 Dog 的 speak
- 其他
| 问题 | 答案 |
|---|---|
| 为什么叫"虚"函数? | 因为调用哪个版本的函数,要等到运行时才确定,"不确定"所以叫"虚" |
| 只有虚函数能重写吗? | 是的,只有父类的 virtual 函数才能被子类 override |
virtual 的作用? |
让编译器在运行时根据实际对象类型调用正确的函数(多态) |
override 的作用? |
告诉编译器"我要重写父类的虚函数",写错了编译器会报错 |
12、-> 和 . 的区别
pet->speak();等同于pet.speak();吗?
取决于 pet 是指针 还是对象:
| pet 的类型 | 调用方式 | 示例 |
|---|---|---|
指针(Animal* pet) |
用 -> |
pet->speak(); |
对象(Animal pet) |
用 . |
pet.speak(); |
并且->是简写
shell
pet->speak();
// 等价于
(*pet).speak();
// 意思是:先通过地址找到对象(*pet),再调用方法(.speak())
13、constexpr编译期计算
constexpr = "这个值/计算在编译时就能搞定"。好处是更快、更安全。但不能用于有 I/O、动态内存等运行时才能完成的操作。
shell
// ✅ 可以加 constexpr
constexpr int add(int a, int b) {
return a + b; // 简单的计算,没问题
}
// ❌ 不能加 constexpr
constexpr void print_hello() {
std::cout << "hello\n"; // ❌ 有 I/O 操作,编译时做不了
}
// ❌ 不能加 constexpr
constexpr int get_random() {
return rand(); // ❌ 每次结果不同,编译时确定不了
}
- 限制
| 能做的 | 不能做的 |
|---|---|
| 简单的数学计算 | std::cout 等 I/O 操作 |
条件判断 if |
调用非 constexpr 的函数 |
| 递归(C++14 起) | 使用 new(动态内存) |
| 返回常量 | 修改外部状态 |
constexpr变量 vsconst变量
const |
constexpr |
|
|---|---|---|
| 含义 | 运行时不能改 | 编译时就能确定 |
| 何时确定值 | 可以是运行时 | 必须编译时 |
shell
const int x = get_input(); // ✅ 运行时才确定,但之后不能改
constexpr int y = get_input(); // ❌ 编译时确定不了,报错!
constexpr int z = 100; // ✅ 编译时就知道是 100
14、你还不知道using的几种用法吗
- 用法一:引入命名空间
cpp
using namespace std; // 引入整个 std 命名空间
// 之后可以直接写 cout,不用写 std::cout
- 用法二:引入命名空间中的某一个成员
cpp
using std::cout; // 只引入 cout,不引入其他的using std::endl; // 只引入 endl
cout << "hello" << endl; // ✅ 可以直接用// cin >> x; // ❌ 不行,没引入 cin
比
using namespace std;更安全,因为只引入了你需要的东西,不会产生命名冲突。
- 用法三:给类型起别名(类型别名)
cpp
using StringVec = std::vector<std::string>;
// 新名字↑ = 原来的类型
意思是:以后 StringVec 就等价于 std::vector<std::string>。
cpp
StringVec v2 {"苹果", "香蕉"};
// 等价于
std::vector<std::string> v2 {"苹果", "香蕉"};
15、enum class(强类型枚举)
shell
#include <iostream>
enum class Color { Red, Green, Blue }; // 强类型,不会隐式转int
int main() {
Color c = Color::Green;
// if (c == 1) {} // 错误!不能直接跟int比
if (c == Color::Green) {
std::cout << "是绿色\n";
}
// 想转int必须显式转换
std::cout << "底层值: " << static_cast<int>(c) << "\n";
}