在学习c++之前,我了解到它是一门较难掌握的,知识点繁多的语言,当我得知只安排1周不到的时间学完,我还是觉得不可思议,因为C语言的学习也安排了大概一个月。不过毕竟数据库也才安排1天半时间,也就没有什么好奇怪的了,但是我也做好了心理准备,这一周只是对C++做一个入门级别的学习,真正要掌握,乃至精通,还需后续努力。
下面罗列一下学习的概要:
一、基本概念
面向过程与面向对象的主要区别:
面向过程以怎么解决问题为核心,面向对象以谁来解决问题为核心。
引用,可以理解为变量的别名,如果函数入参是引用类型,那么函数调用时不会产生副本,调用中对入参值的更改会直接导致原始数据的更改。引用还可以作为返回值的类型,此时返回的是一个指向该返回值的隐形指针。
数据窄化:c++11中引入,就是当数据因为类型不一致而导致精度丢失时,用类似:int b{a} ,这样的花括号定义b,就会有警告。
获取指定下标处的数组元素的值,建议用str.at(i),可以在开发时立即发现数组下标越界的情况。若用str[i] 一旦越界,返回'\0'
内联函数:是直接把函数体在编译时放到主函数中,取代了C中的宏定义函数,声明的方法是在函数名称前面直接加 inline 关键字,内联函数消除了函数调用的开销,运行效率高,但编译时间长。一般将函数体代码行数少,调用频繁的声明为内联函数。成员函数默认就是内联函数。
函数重载:函数名相同,参数类型或个数不同的一组函数。不考虑返回值。注意:构造函数可以重载,但析构函数不能。函数重载和函数默认参数一起使用,可能会导致二义性。
哑元函数:函数参数只有类型,没有参数名,一般用于保存函数的前向兼容,或者区分重载的同名函数。
二、类和对象
栈内存对象:创建的对象在栈区,对象在所创建的代码段运行结束后释放
堆内存对象:创建的对象在堆区,需要用指针访问,手动释放。
构造函数:用于创建一个对象(先创建对象的属性),同时给该对象的属性赋初始值。若不手动指定,编译器默认给类添加一个无参构造函数。若指定了,则编译器不会添加此函数。带参构造函数可以用构造初始化列表来实现,效率比在构造函数体中赋值要高。
拷贝构造函数:是编译器在定义类时自动添加的,入参是本对象常量引用的,浅拷贝函数。
浅拷贝:当成员变量是指针类型时,只拷贝指针,不拷贝值,深拷贝就是拷贝值。
析构函数:用来释放在构造函数中申请的内存。析构函数不能有参数,所以不能被重载
当一个类被创建时,若不手动创建构造函数,编译器会同时往类中创建3个函数,无参构造函数,拷贝构造函数,析构函数
作用域限定符 的使用场合
1. 名字空间::方法名或数据类型或变量名
2. 当成员函数在类内声明,类外定义时,需要在类外函数名称前,返回值类型后,加上类名::
3. 当成员变量是静态的时,需要类内声明,类外初始化,要在数据类型后,变量名前加上类名::
4. 通过类名调用静态成员变量
5. 通过类名调用静态成员函数
6. 在A类中确定B类的某个成员函数是友元成员函数
7. 在派生类的成员函数中调用基类的成员函数。
8. 创建迭代器指针。
9. 在多重继承中出现二义性时,使用::区分调用的是哪个基类的成员
隐式调用构造函数:当构造函数的入参(不包括带默认值的)只有一个时,可以用赋值号直接创建对象。可以用explicite关键字屏蔽此构造函数。
this指针:指向调用者本身的,在成员函数中自带的指针。
1. 用来区分成员变量和局部变量,
2. 用来支持链式调用。
3. 结合多态进行参数传递。
static关键字:当成员变量被static修饰时,成员变量属于类级别,通常需要类内声明,类外初始化。该成员变量的内存空间被所有本类的对象共用,且不会随对象销毁,甚至在对象创建前就已经创建了,可以通过类名直接调用。
当成员函数被static修饰时,成员函数属于类级别,没有this指针,所以函数内不能访问非静态的成员
const关键字:被const修饰的变量,在运行时才会成为常量,编译时const不生效。
当const修饰成员函数时,该函数可以访问非const的成员变量,但不能修改其值,只能调用常成员函数。
当const修饰成员变量时,该成员变量只能在定义时或构造函数中(编译时)被赋初始值,之后(运行时)不能更改。
当const修饰对象时,该对象的属性值不能更改,该对象只能调用常成员函数。
三、友元和运算符重载
友元是让类外的函数访问类内的成员,提高了程序的运行效率和灵活性,但破坏了对象的封装性和隐藏性。
友元函数:声明在类内的非成员函数,可以访问类的所有成员。不受private约束,没有this指针,所以要把对象的引用作为入参。
友元类:若类B是类A的友元类,则B可以访问A的所有成员,友元关系不能被继承,不具备交换性,不具有传递性
友元成员函数:若B中的某个成员函数是A的友元成员函数,那么只有B中的这个成员函数可以访问A的所有成员
运算符的重载:运算符可以看做一个函数,然后像函数重载一样,给运算符在操作基本数据类型之外,添加还能操作对象的功能。
运算符重载可以通过友元函数或成员函数来实现,后者少一个入参。
四、模板与容器
模板:是面向对象中多态的一种体现,就是让类或者函数支持一种通用的数据类型。模板分为函数模板和类模板
泛型编程:编写忽略类型的,可以重复使用的算法,泛型编程的算法可以套用在不同数据类型中,才有函数模板和类模板的方式,提高代码的重用率。
容器:批量存储数据的集合,容器内的对象自动释放和申请内存,无需new和delete,全是栈内存,容器分为顺序容器和关联容器,顺序容器有string,array,vector, list, deque,关联容器有map和multimap
迭代器:一种用于遍历容器的特殊指针,遍历效率最高
五、继承
继承:体现代码的复用性。派生类是基类的具体化,基类是派生类的抽象。
构造函数和析构函数不能被继承。
若派生类中没有声明构造函数,那么编译器会自动添加一个无参构造函数,并尝试用它调用基类的无参构造函数。此时若基类没有无参构造函数,则派生类必须让自己的构造函数来调用基类的构造函数,有三种方法:
- 透传构造:派生类的构造函数直接调用基类的某一个构造函数
- 委托构造:派生类的构造函数A调用自己的其他构造函数B,再让B调用基类的构造函数
- 继承构造:c++11引入,编译器给派生类自动生成和基类的构造函数相同结构和数目的构造函数,并透传构造
单一继承:一个派生类只有一个基类,多重继承,一个派生类继承于多个基类。
多重继承的二义性:
- 当两个直接基类出现重名成员时,所以作用域限定符区分哪个基类
- 菱形继承:派生类的直接基类都是同一个基类的派生类。此时可以用虚继承。
权限修饰符
当修饰成员时:
当修饰继承本身时:
公有继承:基类成员的权限在派生类中不变
保护继承:基类公有成员的权限在派生类中变为保护
私有继承:基类公有成员和保护成员的权限在派生类中变为私有
六、多态
多态:体现代码的灵活性,使得基类的引用能引用派生类的对象,或基类的指针能指向派生类对象。
多态实现的前提是:公有继承,函数覆盖,基类引用派生类对象或基类指针指向派生类对象
函数覆盖:区别于函数隐藏,需要给被覆盖的函数添加virtual关键字,使其变为虚函数,虚函数具有传递性,只有成员函数或析构函数可以是虚函数。
虚析构函数:当基类的析构函数设置成虚函数后,销毁基类时,会先调用派生类的析构函数,避免内存泄露。
七、其他
纯虚函数:只有函数声明,没有定义的虚函数
抽象类:如果一个类中有纯虚函数,那么这个类是抽象类。抽象类是用来为派生类提供算法框架,不能用来生成对象。派生类必须全部实现(覆盖)抽象类的所有纯虚函数,才能实例化。
智能指针:不需要手动释放内存,分为如下几类
auto_ptr(c98引入,已废弃):当执行拷贝构造函数或赋值运算符操作时,原智能指针对象所持有的堆内存对象的管理全会转移给新的智能指针对象,原指针的值为nullptr,此时再访问原智能指针,就会有问题。
unique_ptr:屏蔽了管理权的转移,除非用move()方法
shared_ptr:采用引用计数来管理堆内存对象的持有者。当增加持有者时,引用计数+1,反之-1,当引用计数=0时,释放该对象。
weak_ptr:配合共享指针使用,不控制资源对象生命周期,不影响引用计数,只提供对资源的访问,
类型推导:当变量的类型为auto时,会自动推导该变量的数据类型,但不支持数组,也不能作为函数入参的数据类型。