目录
1、指针与引用的区别
2.const关键字
3.重载和重写(覆盖)的区别
4.new和malloc的区别(new封装了malloc)
5.static和const的区别
6. c++三大特性
7.虚函数
8.纯虚函数
9.虚继承
10. 智能指针
11. 内存泄漏
12.c++的内存分布
13.STL介绍
1、指针与引用的区别
指针存放的是某个对象的地址,指针本身也有自己的地址,而引用只是一个变量的别名,对引用的改变会直接影响原始值。指针可以重新赋值指向其他的地址,而引用一但初始化就不能改变其目标。指针可以有空值,而引用必须在声明的时候初始化,且不能为空。引用不占用额外的内存,但指针会额外占用,用于存放自己的地址。
2.const关键字
被他修饰的值不能改变,必须在定义的时候赋初值。
常量指针和指针常量
数据类型 const* 指针变量 是常量指针的形式,其指向的地址的值不能改变。
数据类型 *const 指针变量 是指针常量的形式,其指向的地址不能改变。
3.重载和重写(覆盖)的区别
重写一般用于子类继承父类时,重写父类的虚函数方法。override关键字是为了确保父子类的虚函数参数一致,否则弹出错误。
重写方法的参数列表,返回值要与被重写的一致。
静态方法不能被重写为非静态。被重写的方法不能为私有方法。
重载存在于一个类中,方法名称相同,而参数形式不同。
4.new和malloc的区别(new封装了malloc)
new分配内存失败时,会抛出异常,而malloc则是返回NULL。
new申请内存分配时,无需指定大小,而malloc则需要显式的指出。
new/delete支持重载,而malloc/free则不支持。
new/delete会调用对象的构造函数和析构函数,而malloc不会
new从自由存储区上分配内存,malloc从堆上分配
5.static和const的区别
const修饰的变量,超出其作用域后会被释放,同时在定义时必须初始化,之后无法更改。而static在函数执行之后不会立即释放。static修饰成员函数称为静态成员函数,不依赖于任何实例,可以通过类名直接调用。因为其没有this指针,不能访问非静态成员变量或函数。同时不能声明为virtual,因为虚函数的调用是通过对象的虚函数表来实现的(里面存的指向各个虚函数的指针),而静态成员函数是与类相关联,而不是与类的对象相关联的。
- static用于声明静态变量和静态函数,它们在内存中只有一份拷贝,与任何对象实例无关。
6. c++三大特性
在类的外部,只能通过对象访问public属性的成员。
继承:可以使用现有类的所有功能,并在无需重新编写原有类的情况下对这些功能进行扩充。
封装:把客观事物抽象成类,并对信息进行分享和隐藏。
多态:允许子类类型的指针赋值给父类类型的指针。重载实现编译多态,虚函数实现运行时多态。实现多态有两种方式,覆盖:子类重新定义父类的虚函数的做法。重载:多个同名函数,参数表不同。
class Base {
public:
virtual void foo() {
cout << "Base::foo()" << endl;
}
};
class Derived : public Base {
public:
void foo() override { // 重写了基类的虚函数foo()
cout << "Derived::foo()" << endl;
}
};
Base* ptr = new Derived();
ptr->foo(); // 会调用Derived::foo()
7.虚函数
当基类希望派生类定义适合自己的版本,就将这些函数声明成虚函数。虚函数是动态绑定的,基类的指针指向谁的类,查询谁的类的虚函数表,这个机制保证派生类的虚函数被调用到。
虚函数实现多态:调用函数的对象必须是指针或者引用,同时被调用的函数必须是虚函数,且完成了虚函数的重写。
构造函数不能是虚函数,因为构造函数在对象被创建时调用,虚函数需要根据对象的动态类型来确定调用哪个函数版本,对象都没被创建完成,又怎么知道他的类型那。
析构函数可以是虚函数,当这个类作为基类实现多态时,基类指针指向派生类的对象,这时为了析构派生类对象,基类的析构函数必须是虚函数。
内联函数不能为虚函数,因为inline在编译时并不知道对象的类型,而虚函数则需要知道对象的类型才知道调用哪个虚函数,所以没法在编译时进行内联函数展开。这里再讲一下inline函数,他一般写在函数定义和声明的前面,是为了解决一些频繁调用的小函数而大量消耗栈内存的问题。一般用于小的频繁调用的函数体。inline函数只是对编译器的一个建议,如果它感觉函数太大了,也可以不作为内联函数使用。
inline int add(int x, int y); //声明时
inline int add(int x, int y) {
return x + y;
}//定义时
静态函数也不能成为虚函数,因为静态成员没有this指针,而虚函数一定要通过对象来调用,有隐藏的this指针。
8.纯虚函数
纯虚函数是在基类中声明的,没有具体实现的虚函数,它只提供接口,需要派生类自己实现。使用纯虚函数的类是抽象类,不能实例化。所以派生类必须重写纯虚函数,不然他也是抽象类,不能实例化。
class AbstractBase {
public:
virtual void pureVirtualFunc() = 0; // 纯虚函数声明
};
class Derived : public AbstractBase {
public:
void pureVirtualFunc() override {
// 派生类提供的纯虚函数实现
}
};
AbstractBase* ptr = new Derived();
ptr->pureVirtualFunc(); // 调用派生类对象的纯虚函数实现
delete ptr; // 删除派生类对象
9.虚继承
虚继承是为了解决多继承时的菱形继承问题,防止出现二义性。
//间接基类A
class A{
protected:
int m_a;
};
//直接基类B
class B: virtual public A{ //虚继承
protected:
int m_b;
};
//直接基类C
class C: virtual public A{ //虚继承
protected:
int m_c;
};
//派生类D
class D: public B, public C{
public:
void seta(int a){ m_a = a; } //正确
void setb(int b){ m_b = b; } //正确
void setc(int c){ m_c = c; } //正确
void setd(int d){ m_d = d; } //正确
private:
int m_d;
};
int main(){
D d;
return 0;
}
10. 智能指针
智能指针是为了解决动态分配内存导致内存泄漏和多次释放同一内存所提出的。放在<memory>头文件。包括共享指针,独占指针,弱指针
shared_ptr:是一种引用计数的智能指针,其通过一个计数器来追踪有多少个智能指针共享同一资源,当为计数为0时,释放资源。也就是当没有智能指针指向这个资源时,这个资源会被释放。
#include <memory>
std::shared_ptr<int> ptr1 = std::make_shared<int>(42); //第三种构造方式
std::shared_ptr<int> ptr2 = ptr1; // 共享所有权
'''
这两种不常用
int* a = new int(4);
std::shared_ptr<int> ptr3(a); //第一种构造方式
std::shared_ptr<int> ptr1(new int); //第二种构造方式
'''
只有ptr1和ptr2都被销毁时,才释放int型资源。
unique_ptr:是一种独占所有权的智能指针,不能与其他共享管理权,也就是不支持普通的拷贝和赋值,当其销毁时,它管理的资源也会销毁。
#include <memory>
std::unique_ptr<int> ptr = std::make_unique<int>(42);
weak_ptr :是为了配合shared_ptr而引入的,像旁观者那样观察资源的使用情况,但它没有共享资源,他的构造不会引起指针引用计数增加。
11. 内存泄漏
堆内存泄露:是指程序中使用malloc和new等从堆中分配一块内存,用完之后忘记用free或delete删除。
系统资源泄露:指程序使用系统分配的资源,但并没有调用相应的函数释放掉,导致系统资源浪费。
没有将基类的析构函数定义为虚函数:当基类的指针指向子类的对象时,如果基类的析构函数不是虚函数,那么子类的析构函数将不会被调用,造成内存泄漏。
如何防止内存泄漏?将内存的分配封装在类中,构造函数分配内存,析构函数释放内存。使用智能指针。
12.c++的内存分布
栈区存放的局部变量和函数的参数值。从高地址到低地址。
堆区存放的动态申请的内存。
全局区存放的全局变量和静态变量。在程序编译时内存就已经分配好了
13.STL介绍
容器:各种数据结构,如pair,vector,list,deque,set,map等。
适配器:queue和stack,他们的底层都是deque实现的。
pair:里面的元素调用a->first, a->second,插入pair时,insert({first, second})或insert(pair<类型,类型>(数据1,数据2));
vector:在堆中分配了一段连续的内存空间。他的扩容是每次翻倍,容量不够就翻倍。capacity永远大于size。底层实现是数组。
操作:push_back,emplace_back,pop_back, clear,
insert(position, value)
:在指定位置插入一个元素。erase(position)
:移除指定位置的元素。erase(first, last)
:移除指定范围内的元素
list:内存空间是不连续,通过指针来进行数据的访问。通常用来随即插入删除操作。是双向链表。
deque:双端队列,由于需要内部跳转,所以随机访问没有vector快。由于deque的迭代器要比vector的复杂,对deque排序时,可以先把deque复制到vector里再排序,再放回deque。操作:push_back,pop_back,push_front,pop_front
stack和queue被称为适配器,底层是deque。操作:push,pop,empty,size。此外队列还有front,back,栈还有top
map和set:底层采用红黑树实现,查找的复杂度为log(n).
unordered_map和unordered_set:底层实现是哈希表,查找复杂度为1