前景:在学习数据结构,特别音视频的实践编码中,发现对C++基本知识点的掌握很有必要,以及Swift是由C++写的。
开发工具:pc+windows+Visual Studio(区别于vscode,本人使用的vs2019)
简述:C++是完全兼容C语言的语法,源文件扩展名是cpp(C Plus Plus),是一门面向对象编程的编译型语言。
知识点:
1,函数重载原则:函数名相同,函数参数、类型、顺序不同,但不影响编译。本质上是因为C++编译器默认会将函数名(也是一个符号名)进行基于顺序、参数、类型不同而采取不同的改编修饰,进而会生成多个不同的函数名。
2,默认参数原则:
- 在设置默认参数时,只能从右往左开始。函数参数也可以设置默认值。
- 如果函数同时有声明、实现,默认参数只能放在函数声明中。
- 假如同时实现默认参数和重载参数,会导致二义性,编译会出错,如下代码:
void test(int x, int y = 1) {
}
void test(int x) {
}
int main() {
//编译会报错
test(1);
return 0;
}
3,extern “C”
:音视频编译时,我们会需要使用此来导入ffmpeg的头文件。作用是被extern “C”
修饰的代码会按照C语言的方式去编译,如果函数在定义和声明时,想被此修饰,在声明时使用即可。有时在编码C语言代码时,直接使用extern “C”
,这样直接可以被C++调用。
4,内联函数:debug模式下,查看汇编仍然会看到call+函数符号名。修改为release模式,查看汇编,相比对会看到优化。(可以参考禁用“优化”选项和修改“内联函数扩展”为任何适用项)。会对被修饰项进行语法检测,多了函数特性。并非对修饰的所有函数都会进行优化,例如并不会对递归函数进行优化。
5,const关键字:
- 被修饰的变量不可更改,也即是const修饰右侧的第一个变量不能更改。在修饰定义变量时需要立即赋值。
- 修饰类或结构体时,被修饰的对象以及对象的成员变量都不能被更改。
代码演示如下:
const int *x = 0; //const右侧的(*x)是第一个变量,指针x所指向的整型变量数据, *x所指的变量值不能被修改, x指针变量可以被修改
int * const x = 0; //const右侧的(x)是第一个变量,指针变狼; x指针变量不能被修改,但是*x指向的变量值可以被修改。
6,引用:
-
在C语言中,使用指针(Pointer)可以间接获取、修改某个变量的值;在C++中,使用引用(Reference)可以起到跟指针类似的功能。在定义的时候,必须被初始化,一旦指向变量,就不能被修改。基本数据类型、枚举、结构体、类、指针、数组等,都可以有引用。本质是指针,只不过,编译器弱化了功能。
-
引用可以被const修饰,这样就无法通过引用修改数据了,可以称为常引用(const必须写在&符号的左边,才能算是常引用)。当常引用指向了不同类型的数据时,会产生临时变量,即引用指向的并不是初始化时的那个变量
-
比指针更安全,在应用上,例如swap交换值。
int x = 10;
int &y = x; //这种方式就叫做引用,y是x的别名,打印地址发现,x和y是同一个地址,不能被修改。
//数组的引用
int array[] = {1,2,3};
int (&ref1)[3] = array;
/*array 数组名是数组的地址其实也是数组的第一个元素的地址,是一个常量地址,是无法修改的,可以看作是指向数组首元素的指针(int *类型),常引用才能引用一个常量,所以用const 修饰。这种写法相当于ref2作为指向int *类型的array指针的别名。
*/
int *const &ref2 = array;
7,类class与结构体struct:
- 两者均可以定一个类,class的默认成员权限是public,struct的默认成员权限是private。
- 堆空间对象申请和释放原则:malloc-free,new-delete,new [] - delete []。
- 通过malloc分配的对象不会调用构造函数。如果自定义了构造函数,除了全局区,其他内存空间的成员变量默认都不会被初始化,需要开发人员手动初始化
- 命名空间可以用来避免命名冲突。
- 默认情况下的初始化,成员变量的初始化如下图:
struct Person {
int age;
}
//全局区,并且成员变量会被初始化为0。
Person global_p;
int main() {
//栈空间,成员变量不会被初始化
Person p;
//堆空间,成员变量不会被初始化
Person *p0 = new Person;
//堆空间 成员变量初始化为0
Person *p1 = new Person();
}
- 成员访问权限、继承方式有3种: (1)public:公共的,任何地方都可以访问(struct默认)(2)protected:子类内部、当前类内部可以访问 (3)private:私有的,只有当前类内部可以访问(class默认)
- 初始化列表:只能用在构造函数中,初始化变量,但是初始化顺序只跟成员变量的声明顺序有关。
struct Person {
int k_age;
int k_score;
Person(int age, int score) : k_score(score), k_age(age) {
}
}
//此对象的k_age和k_score的值??
Person(99,60);
- 父类指针可以指向子类对象,是安全的,开发中经常用到(继承方式必须是public)
- 虚函数:被virtual修饰的成员函数;只要在父类中声明为虚函数,子类中重写的函数也自动变成虚函数(子类中可省略virtual)。通过virtual修饰的函数,在多态应用中,会根据指针指向的真正对象来调用函数,而不是根据指针类型。虚函数的实现原理是虚表,这个虚表里面存储着最终需要调用的虚函数地址,这个虚表也叫虚函数表。在多态的使用中,父类call函数时,会根据所指对象的内存进行寻址,会找到虚函数表,然后找到所指对象的函数地址,而不是指针变量本身的修饰类的函数地址。
- 纯虚函数:没有函数体且初始化为0的虚函数,用来定义接口规范。
- 抽象类:含有纯虚函数的类,不可以实例化(不可以创建对象);抽象类也可以包含非纯虚函数、成员变量;如果父类是抽象类,子类没有完全重写纯虚函数,那么这个子类依然是抽象类。
class Man {
public:
//虚函数
virtual void test() {
cout << "Man::test" << endl;
}
//如果存在父类指针指向子类对象的情况,应该将析构函数声明为虚函数(虚析构函数)
//delete父类指针时,才会调用子类的析构函数,保证析构的完整性
virtual ~Man() {
cout << "Man::~Man" << endl;
}
}
class Person: public Man {
public:
void test() {
}
~Person() {
}
}
//抽象类
class Animal {
//纯虚函数
virtual void run() = 0;
}
- 多继承:会产生菱形继承,导致子类继承的父类的成员变量重复以及访问基类变量时会导致二义性,可通过sizeof(#)确认对象内存大小。解决方式:采用虚继承的方式。代码演示如下:
class A {
string a;
}
/*虚继承:会创建一个虚表指针,父类的成员变量会放在对象内存的后面,区别于常规继承(父类成员变量会在对象的前面)。A作为虚基类。虚表指针所指对象的第一个元素数据是相对于定义的那个类来说,例如D对象内存布局的两个虚表指针,也是相对于B或C类而言
虚表指针所指向的地址空间包含着两个数据:
第一个是虚表指针与本类起始位置的偏移量;第二个是虚基类第一个成员变量与本类起始的偏移量。
*/
class B: virtual public A {
string b;
}
class C: virtual public A {
string c;
}
/*为解决访问D创建的对象访问虚基类成员变量a的问题,在定义B和C类都需要声明virtual。
D创建的对象内存布局,会存在两个虚表指针,同时会把B和C所包含的虚基成的变量合并放到对象空间的后面。
*/
class D: public B, public C {
string d;
}
int main() {
D d;
d.a = ""; //如果不采取虚继承,编译时会报错提示
return 0;
}
-单例模式:
class ShareManager {
private:
ShareManager() {};
~ShareManager() {};
static ShareManager *_instance;
public:
static ShareManager *shareInstance() {
if(_instance == NULL) {
_instance = new ShareManager();
}
return _instance;
}
static void deleteInstance() {
if(_instance != NULL) {
delete _instance;
_instance = NULL;
}
}
}
- 引用类型成员变量必须初始化。
class Person {
int age;
//引用类型变量
int &m_age = age;
public:
Person(int &tempAge) : m_age(tempAge){}
//拷贝构造函数:拷贝构造函数的格式是固定的,接收一个const引用作为参数
Person(const Person &p) {
this.age = p.age
}
}
- 关键字explicit可以禁止掉隐式构造。
- 友元:友元函数和友元类。如果将一函数(非成员函数)声明为类的友元函数,那么此函数就能直接访问此类对象的所有成员。如果将类A声明为类C的友元类,那么类A的所有成员函数都能直接访问类C对象的所有成员。
class Point {
friend Point add(const Point &, const Point &);
friend class Math;
private:
int m_x;
int m_y;
public:
Point() {}
Point(int x, int y) : m_x(x), m_y(y) {}
};
Point add(const Point &p1, const Point &p2) {
return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);
}
class Math {
void test() {
Point p;
p.m_y = 10;
p.m_x = 10;
}
}
- 模板(template): 泛型,是一种将类型参数化以达到代码复用的技术,C++中使用模板来实现泛型。模板没有被使用时,是不会被实例化出来的。一般将模板的声明和实现统一放到一个.hpp文件中。
//应用于函数模板
template <class T> void compare(T &v1, T &v2);
//应用于类
template <typename Item>
class Set {
Item *items;
int k_size;
int k_capacity;
public:
Set(int capaticy = 0) {
k_capacity = (capaticy > 0) ? capaticy : 10;
//申请堆空间
items = new Item[k_capacity];
}
void add(Item value) {
if (k_size == k_capacity) {
//扩容操作
}
items[k_size ++] = value;
}
~Set() {
if (items == NULL) {
return;
}
delete[] items;
}
};
Set<int> s(2);
- 智能指针:
//系统
//强引用:会对一个对象产生强引用,当有一个新的shared_ptr指向对象时,对象的强引用计数就会+1;当有一个shared_ptr销毁时(比如作用域结束),对象的强引用计数就会-1;当一个对象的强引用计数为0时(没有任何shared_ptr指向对象时),对象就会自动销毁(析构);每个对象都有个与之对应的强引用计数,记录着当前对象被多少个shared_ptr强引用着;可以通过shared_ptr的use_count函数获得强引用计数。
shared_ptr<Person> p1;
//弱引用,解决循环引用
weak_ptr;
//可以确保同一时间只有1个指针指向对象
unique_ptr;
//简单实现
template <class T>
class SmartPointer {
T *pointer;
public:
SmartPointer(T *p): pointer(p) {}
~SmartPointer() {
if (pointer == nullptr) {
return;
}
delete pointer;
}
T* operator -> () {
return pointer;
}
};
意识拓展:swift的inout关键字的本质其实就是C++的引用操作;函数表的派发机制与C++虚表指针的关系(ps,个人意识,期待大神分享阐述其中微妙)。