跳到主要内容C++ 基础语法与核心编程知识点总结 | 极客日志C++算法
C++ 基础语法与核心编程知识点总结
C++ 语言基础涵盖 sizeof 与 strlen 区别、指针与引用差异、数组名作用及结构体对齐规则。核心编程部分详解封装、继承、多态机制,包括构造函数调用规则、深浅拷贝处理、this 指针用法及虚函数实现动态多态。此外还介绍了 new/delete 与 malloc/free 内存管理差异,以及 STL 容器、算法、迭代器等组件的基本概念。
21772838012 浏览 一、C++ 基础
1. sizeof 和 strlen 的区别
strlen 是 C 语言中统计字符串长度的关键字,其统计遇到的第一个\0 前面的有效字符(不论显示还是隐式)。sizeof 统计占据的所有内存字节数,包含显示\0 和隐式\0。
2. 三目运算符
表达式 1?表达式 2:表达式 3;
3. 一维数组名作用
- 统计整个数组的字节数;
- 数组元素的首地址;
- 数组地址。
数组首地址与数组地址在值上是相等的,但是两者存在本质的区别,如下面代码中的解释说明:
int arr[3] = {10, 20, 30};
cout << sizeof(arr);
cout << sizeof(arr[0]);
cout << "&arr(整个数组地址):" << &arr << endl;
cout << "arr(首元素地址):" << arr << endl;
4. 二维数组名作用
- 统计整个数组的字节数;
- 数组名作为'指向一维数组的指针'(默认隐式转换,这里的维度看行数)。例如 int[2][3],它是'包含 2 个元素的数组',每个元素又是'包含 3 个 int 的数组';
arr会隐式转换为'指向其第一个一维子数组的指针',类型是 int (*)[3]。
- 整个二位数组的地址:&数组名
- 一维数组的数组名:数组名 + 下标
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
cout << sizeof(arr);
(*p)[] = arr;
cout << (*p)[];
cout << (*(p))[];
(*p_arr)[][] = &arr;
* p_elem = arr[];
cout << *p_elem;
int
3
0
+1
0
int
2
3
int
0
5. 函数的声明
6. 指针和引用的区别
一定切记指针本身就是变量,存储某个变量的地址,所以存在指向指针的指针;但是引用表示对某个变量起别名,内存不会再单独开辟一个空间,与引用的变量共享同一块内存空间。引用必须初始化,引用的实质相当于指向指针常量,指针的指向不会改变,但是值可以修改。
7. 空指针和野指针
- 空指针:主要用于对指针进行初始化,空指针指向的内存空间是不允许被访问的(内存编号 0 ~255 为系统占用内存,不允许用户访问);
- 野指针:简单理解野指针就是指向无效内存地址的指针变量,其产生原因如下:
- 指针被释放后未置空;
- 指针未进行初始化;
- 指针指向局部变量;
8. const 关键字
- 指针常量:const 修饰 p;
int* const ptr = &value;
- 常量指针:const 修饰*p;
const int *p = &a;
- class A { void func() const; };-->const 成员函数内部对于变量和值,对于变量不能修改(只读操作);对于函数只能调用同意类下的 const 成员函数;const 类只能调用它的 const 成员函数。
9. 指针和数组
- 指针数组:数组里面的每个元素都是指针;
int *ptr_arr[5];
- 数组指针:指针指向数组;
int (*arr_ptr)[5];
10. 指针和函数
- 函数指针:指向函数的指针;
int (*func_ptr)(int, int);
- 指针函数:函数的返回值是指针;
int *create_array(int size);
11. 结构体
- 结构体变量的定义方式:
- struct 结构体名 结构体变量;-->struct teacher t1;
- struct 结构体名 结构体变量 = {成员 1 的值,成员 2 的值};
- struct 结构体名 {} 结构体变量;
- 结构体作为函数参数
传递方式有两种:值传递/地址传递
特殊情况:结构体作为函数参数时,与 const 结合;(防止值被修改)
结构体实例化应用:当定义数据协议时,可以将数据定义为一个结构体去定义和解析数据。
结构体默认的访问和继承权限都是 public,而类全是 private;
结构体对齐面试常考知识点
CPU 访问内存时,并不是逐个字节读取,而是按字长(Word Size) 读取(比如 32 位 CPU 字长是 4 字节,64 位是 8 字节)。如果数据的起始地址是其自身大小的整数倍,CPU 可以一次读取完成,效率最高;否则就需要多次读取并拼接,效率降低。
结构体的总大小:必须是其最大成员类型大小的整数倍(不足则补填充字节)
成员对齐:每个成员的起始地址必须是其自身大小的整数倍(比如 char 占 1 字节,无限制;int 占 4 字节,起始地址需是 4 的倍数)。
写在前面:对于下面两个结构体,虽然结构体中的内容不同,但是由于结构体中的成员变量顺序不同,所以结构体大小也不同。Unoptimized 中 char 后直接 int,需要填充 3 字节;而 Optimized 中 char 后接 short,仅需填充 1 字节。
#include <stdio.h>
struct Unoptimized {
char a;
int b;
short c;
};
struct Optimized {
char a;
short c;
int b;
};
int main() {
printf("Unoptimized size: %zu bytes\n", sizeof(struct Unoptimized));
printf("Optimized size: %zu bytes\n", sizeof(struct Optimized));
return 0;
}
Unoptimized size: 12 bytes
Optimized size: 8 bytes
- struct Unoptimized:
- char a:占用地址 0(1 字节),地址 1-3 填充 3 字节(为了让 int b 的起始地址是 4 的倍数)。
- int b:占用地址 4-7(4 字节)。
- short c:占用地址 8-9(2 字节),地址 10-11 填充 2 字节(整体大小需是最大成员 4 字节的整数倍)。
- 总大小:1+3+4+2+2 = 12 字节。
- struct Optimized:
- char a:占用地址 0(1 字节),地址 1 填充 1 字节(让 short c 起始地址是 2 的倍数)。
- short c:占用地址 2-3(2 字节)。
- int b:占用地址 4-7(4 字节)。
- 总大小:1+1+2+4 = 8 字节(刚好是最大成员 4 字节的 2 倍)。
12. volatile 关键字
- 防止编译器优化,告诉代码从内存去读取值,而不是通过寄存器的缓存去读取值。
- 资源共享:中断与主程序/多线程之间共享数据。
13. static 关键字
- 静态变量:必须初始化;类内声明,类外初始化;作用周期贯穿整个程序,但作用范围只限局部范围内;属于类,而不是属于某个具体的对象;
- 静态函数:无需创建对象,可直接通过类名访问;属于类,而不是属于某个具体的对象;
14. define 与 const 区别
- define 简单理解只是无脑替换,不进行类型检查,define 定义的宏常量替换后不会占内存;
- const 会进行类型检查,const 定义的常量会占据内存;
15. new/delete 与 malloc/free 区别
new/delete 是 C++ 的运算符,malloc/free 是 C 语言的函数,二者都用于动态内存管理;
- new 按对象类型自动计算对应内存空间(如
new int分配 4 字节);malloc 需要指定字节数(如malloc(4));
- new直接返回对应类型指针(如
int*),无需强制类型转换;malloc 返回void*,必须手动强制类型转换。
16. define 与 typedef 区别
- define 只是简单的文本替换,不涉及任何语法检查,工作阶段处于编译之前;
- typedef 是关键字,其作用为起别名,单纯从这个角度来讲类似于引用,在编译阶段起作用;
二、C++ 核心编程(封装、继承、多态)
2.1 封装 - 类
1. 类和结构体区别
类默认访问和继承是 private,结构体默认访问和继承是 public。
2. 继承
- 公有继承:父类中的公有(public)和保护(protected)属性到子类中还是公有和保护属性;父类中的私有成员也会被子类继承,占用子类对象的内存空间,但子类完全无法直接访问这些私有成员。
- 保护继承:父类中的公有和保护属性到子类中变为保护属性;父类中的私有成员也会被子类继承,占用子类对象的内存空间,但子类完全无法直接访问这些私有成员。
- 私有继承:父类中的公有和保护属性到子类中变为私有属性;父类中的私有成员也会被子类继承,占用子类对象的内存空间,但子类完全无法直接访问这些私有成员。
3. 构造函数
- 主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用;
- 构造函数与类同名;
- 具体可分为无参构造、有参构造和拷贝构造;
4. 构造函数调用规则
默认情况下,c++ 编译器至少给一个类添加 3 个函数:
- 默认构造函数 (无参,函数体为空)
- 默认析构函数 (无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:如果用户定义有参构造函数,c++ 不在提供默认无参构造,但是会提供默认拷贝构造;如果用户定义拷贝构造函数,c++ 不会再提供其他构造函数;
5. 析构函数
主要作用在于对象销毁前系统自动调用,执行一些内存清理工作。构造函数和析构函数如下所示:
#include <iostream>
#include <cstring>
using namespace std;
class Person {
private:
string name;
int age;
public:
Person() { cout << "调用【无参构造函数】" << endl; name = "未知"; age = 0; }
Person(string n, int a) { cout << "调用【有参构造函数】" << endl; name = n; age = a; }
Person(const Person& p) { cout << "调用【拷贝构造函数】" << endl;
name = p.name; age = p.age;
}
~Person() { cout << "调用【析构函数】,销毁对象:" << name << endl; }
void showInfo() { cout << "姓名:" << name << ",年龄:" << age << endl; }
};
void testCopy(Person p) { p.showInfo(); }
int main() {
cout << "--- 创建无参对象 p1 ---" << endl;
Person p1; p1.showInfo();
cout << "\n--- 创建有参对象 p2 ---" << endl;
Person p2("张三", 20); p2.showInfo();
cout << "\n--- 用 p2 拷贝创建 p3 ---" << endl;
Person p3 = p2;
p3.showInfo();
cout << "\n--- 函数参数值传递调用拷贝构造 ---" << endl;
testCopy(p2);
cout << "\n--- 程序结束,对象开始销毁 ---" << endl;
return 0;
}
6. 深拷贝和浅拷贝
- 浅拷贝:一定切记是共享内存空间,对于像
int, float, char 这样的基本数据类型,浅拷贝会创建一个新的、独立的副本;对于像指针、引用这样的复杂类型,浅拷贝只会复制指针本身,而不会复制指针指向的内容。结果就是,新旧两个对象会共享同一块内存资源。
- 深拷贝:不共享内存空间,对于基本数据类型,和浅拷贝一样,创建新副本;对于指针、引用等复杂类型,深拷贝会不仅复制指针,还会为指针指向的内容分配新的内存,并将内容也复制过去。最终,新旧对象拥有各自独立的资源,互不干扰。
- 浅拷贝带来的问题:内存的重复释放问题,解决办法,采用深拷贝。
7. 静态成员
- 类内声明,类外初始化;
- 它属于整个类,而不属于某个具体的对象;
- 作用范围有限,但生命周期贯穿整个程序;
- 相当于局部全局变量;
- 可通过命名空间直接访问,不需要通过创建对象去访问;
- 它属于整个类,而不属于某个具体的对象;
- 只能访问静态成员,不能访问非静态成员;
8. 非常关键内容
在 c++ 中成员变量和成员函数是分开存储,只有非静态成员变量才会占据类对象的内存空间,静态成员变量和函数与常规成员函数不会占用类对象的内存空间。
9. this 指针(本质相当于指向类对象的指针常量)
写在前面:常见用法解决命名冲突、返回对象本身实现链式调用,本质是让成员函数'知道操作哪个对象'。
- 定义:
this指针是 C++ 编译器自动为每个非静态成员函数添加的一个隐藏的、常量指针(const指针),它指向调用该成员函数的那个对象本身。简单说:谁调用函数,this就指向谁。
- this 指针的用途:
- 当形参和成员变量同名时,可用 this 指针来区分,如代码 1;
- 在类的非静态成员函数中返回对象本身,可使用 return *this,如代码 2;
- 在成员函数中访问当前对象的地址 / 本身,如代码 3;
- this 的类型是
类名* const:指针本身不能被修改(不能让 this指向其他对象),但指向的对象内容可以改;(指针常量)
- 只有非静态成员函数才有
this指针(静态成员函数属于类,不依赖对象,因此没有 this)。
- 如果空指针调用'不访问成员变量'的成员函数,不会崩溃(因为函数代码在代码区,和对象无关);如果调用'访问成员变量'的成员函数,会崩溃(因为
this是 NULL,this->age等价于NULL->age,非法访问内存):
#include <iostream>
using namespace std;
class Person {
public:
int age;
void setAge(int age) {
this->age = age;
}
void showAge() {
cout << "年龄:" << age << endl;
}
};
int main() {
Person p1, p2;
p1.setAge(18);
p2.setAge(20);
p1.showAge();
p2.showAge();
return 0;
}
#include <iostream>
using namespace std;
class Person {
public:
int age;
Person& addAge(int num) {
this->age += num;
return *this;
}
};
int main() {
Person p;
p.age = 10;
p.addAge(5).addAge(3);
cout << p.age << endl;
return 0;
}
void Person::showAddr() {
cout << "当前对象的地址:" << this << endl;
cout << "当前对象的年龄:" << (*this).age << endl;
}
class Test {
public:
void func1() { cout << "无成员变量访问" << endl; }
void func2() { cout << this->age << endl; }
int age;
};
int main() {
Test* p = NULL;
p->func1();
return 0;
}
10. 友元
总结:只要看见 friend 关键字,那么这个函数就可以访问引入它的类的私有成员。
2.2 继承
1. 继承方式
public、protect、private(上面已经介绍过)
2. 继承中的构造和析构
子类继承父类时,继承中先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反。
3. 菱形继承
何为菱形继承,可以简单理解为,两个父类继承同一个爷爷,孙子类又继承两个父类,继承父类两份相同的数据,会导致资源浪费以及毫无意义,利用虚继承可以解决菱形继承问题,当两个派生类继承同一个基类时,采用虚继承。
2.3 多态
1. 分类
- 定义:编译器在编译阶段就确定要调用哪个函数,也叫'早绑定'。
- 实现方式:函数重载、运算符重载,代码 1;
- 定义:编译器在编译阶段无法确定调用哪个函数,直到程序运行时才确定,也叫'晚绑定'。
- 实现方式:虚函数(
virtual)+ 继承 + 父类指针 / 引用指向子类对象,代码 2;
#include <iostream>
using namespace std;
class Calculator {
public:
int add(int a, int b) { return a + b; }
int add(int a, int b, int c) { return a + b + c; }
double add(double a, double b) { return a + b; }
};
int main() {
Calculator calc;
cout << calc.add(1, 2) << endl;
cout << calc.add(1, 2, 3) << endl;
cout << calc.add(1.5, 2.5) << endl;
return 0;
}
#include <iostream>
using namespace std;
class Animal {
public:
virtual void makeSound() { cout << "未知动物的叫声" << endl; }
virtual ~Animal() {}
};
class Cat : public Animal {
public:
void makeSound() override { cout << "喵喵喵" << endl; }
};
class Dog : public Animal {
public:
void makeSound() override { cout << "汪汪汪" << endl; }
};
void animalCry(Animal &animal) {
animal.makeSound();
}
int main() {
Cat cat;
Dog dog;
animalCry(cat);
animalCry(dog);
Animal *p1 = &cat;
p1->makeSound();
Animal *p2 = &dog;
p2->makeSound();
return 0;
}
2. 虚函数和纯虚函数
- 虚函数:普通虚函数是用
virtual 关键字修饰的、有函数体的成员函数,目的是允许子类重写(override),从而实现动态多态。
- 纯虚函数:纯虚函数是用
virtual 修饰、没有函数体,且以= 0结尾的成员函数,目的是强制子类必须重写该函数,同时将包含它的类变为抽象类,抽象类不能实例化对象(无法创建 Animal a; 这样的对象)。
三、STL
STL (Standard Template Library, 标准模板库);从广义上分为容器(container)、算法(algorithm)、迭代器(iterator),容器和算法之间通过迭代器进行无缝连接。
STL 大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器。
- 容器:各种数据结构,如 vector、list、deque、set、map 等,用来存放数据。
- 算法:各种常用的算法,如 sort、find、copy、for_each 等。
- 迭代器:扮演了容器与算法之间的胶合剂。
- 仿函数:行为类似函数,可作为算法的某种策略。
- 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
- 空间配置器:负责空间的配置与管理。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online