最近几年用c和python比较多,上次用c++写程序已经是几年前的事情了。温故而知新,是时候重新学习下c++了。

C++可以分成四大块去理解:

  • C
    c++仍以C为基础,区块blocks、语句statements、预处理器preprocessor、内置数据类型、数组、指针都来自C。
  • Object-Oriented C++
    C++一开始被称为c with classes,它引入了classes(包括构造函数和析构函数)、封装、继承、多态、动态绑定virtual函数…等等
  • Template C++
    template的相关考虑和设计已经弥漫了整个C++,它带来了新的编程模式,就是所谓的模板元编程。
  • STL
    STL是一个template程序库,它封装了常见的数据结构和算法,提供了一些容器、迭代器、算法函数。

1. C VS C++(语法差异)

c++仍以C为基础,区块blocks、语句statements、预处理器preprocessor、内置数据类型、数组、指针都来自C。
c++中类c的基本语法我已经掌握了,主要复习一下c++独特的一些语法特性。

1.1 const限定符

const允许你指定一个语义约束(也就是指定一个"不该被改动"的对象),编译器会强制实施这个约束。
const多才多艺,可以修饰变量、指针、函数、对象。

char greeting_str[] = "hello";
char* p = greeting_str;
const char* p = greeting;
char* const p = greeting;
const char* const p = greeting;

虽然语法变化多端,但是并不莫测高深:

  • 如果const出现在星号左边,表示被指物是常量。
  • 如果const出现在星号右边,表示指针自身是常量。
  • 如果const出现在星号两边,表示被指物和指针都是常量。

如果被指物是常量,const写在类型前和类型后,都是一样的。

void f1(const Widget * pw);
void f2(Widged const *pw);

两种写法都有人用,习惯就好。

另外,const可以还修饰成员函数,这有两个目的:

  • 使class接口更清晰,可以得知哪个函数可以改动对象内容,哪个函数不行
  • 使得操作const对象成为可能

1.2 引用

引用“&”是C++新增的概念,注意这里的“&”不是取地址的语义。

int m;
int& n = m;

n既不是m的拷贝,也不是指向m的指针,其实n就是m自己。

引用的主要用途是修饰函数的形参和返回值。

C++语言中,函数的参数和返回值的传递方式有三种:值传递、指针传递和引用传递。引用既具有指针的效率,又具有变量使用的方便性和直观性。

引用和指针的区别:

  • 引用在创建的同时必须初始化,即引用到一个有效的对象;而指针在定义的时候不必初始化,可以在定义后面的任何地方重新赋值。
  • 不存在NULL引用,引用必须与合法的存储单元关联;而指针则可以是NULL。
  • 引用一旦被初始化为指向一个对象,它就不能被改变为对另一个对象的引用(即“从一而终、矢志不渝”);而指针在任何时候都可以改变为指向另一个对象。

实际上“引用”可以做的任何事情,“指针”都能够做,为什么还要“引用”呢?

答案是:“用适当的工具做恰如其分的工作”。引用体现了最小特权原则,即给予程序元素足以完成其功能的最小权限。指针能够毫无约束地操作内存中的任何东西,尽管功能强大,但是非常危险。

内存分配是操作系统的概念,内存方式有以下3种:
(1)从静态存储区域分配。内存在程序编译的时候就已经分配好了(即已经编址),这些内存在程序的整个运行期间都存在,如全局变量、static变量等。
(2)在堆栈上分配。在函数执行期间,函数内局部变量(包括形参)的存储单元都创建在堆栈上,函数结束时这些存储单元自动释放(堆栈清退)。堆栈内存分配运算内置于处理器的指令集中,效率很高,并且一般不存在失败的危险,但是分配的内存容量有限,可能出现堆栈溢出。
(3)从堆(heap)或自由存储空间上分配,亦称动态内存分配。程序在运行期间用malloc() 或new申请任意数量的内存,程序员自己掌握释放内存的恰当时机(使用free() 或delete)。动态内存的生存期由程序员决定,使用非常灵活,但也最容易产生问题。

1.3 malloc/free

从操作系统角度来看,进程分配内存有2种方式,分别由2个系统调用完成:brk和mmap(不考虑共享内存)。

  1. brk是将数据段(.data)的最高地址指针_edata往高地址推
  2. mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。

这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。

malloc/free的内部实现
malloc/free是标准库实现的函数,这两个函数底层是由brk、mmap、,munmap这些系统调用实现的。

  • malloc小于128k的内存,使用brk分配内存,将堆顶指针往高地址推
  • malloc大于128k的内存,使用mmap分配内存,在堆和栈之间找一块空闲内存分配

  • 进程调用free©以后,C对应的虚拟内存和物理内存一起释放。
  • 进程调用free(B)以后,B对应的虚拟内存和物理内存都没有释放,因为只有一个_edata指针,如果往回推,那么D这块内存怎么办呢?当然,B这块内存,是可以重用的,如果这个时候再来一个40K的请求,那么malloc很可能就把B这块内存返回回去了。
  • 进程调用free(D)以后,B和D连接起来,变成一块140K的空闲内存。当最高地址空间的空闲内存超过128K,于是内存紧缩。

    malloc 函数的实质是它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。
typedef struct s_block *t_block;
struct s_block {size_t size; /* 数据区大小 */
t_block next; /* 指向下个块的指针 */
int free; /* 是否是空闲块 */
int padding; /* 填充4字节,保证meta块长度为8的倍数 */
char data[1] /* 这是一个虚拟字段,表示数据块的第一个字节,长度不应计入meta */
};

为了完全地管理内存,我们需要能够追踪要分配和回收哪些内存。在对内存块进行了 free 调用之后,我们需要做的是诸如将它们标记为未被使用的等事情,并且,在调用 malloc 时,我们要能够定位未被使用的内存块。

指针p被free以后其地址仍然不变(不等于NULL),只是该地址对应的内存是垃圾——p成了“野指针”!如果此时不把p设置为NULL,会让人误以为p是个有效的指针。

如果程序较长,我们有时记不住p所指的内存是否已经被释放,在继续使用p之前,通常会用语句if (p != NULL) 进行防错处理。很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也可能不再指向合法的内存块。

1.4 malloc/free和new/delete的区别

malloc/free是C/C++语言的标准库函数,new/delete是C++的运算符。

由于malloc()/free() 是库函数而不是运算符,不在编译器控制权限之内,不能把调用构造函数和析构函数的任务强加给它们。

因此C++语言需要一个能够完成动态内存分配和初始化工作的运算符new,以及一个能够完成清理与释放内存工作的运算符delete。

1.5 c++智能指针

从较浅的层面看,智能指针是利用了一种叫做RAII(资源获取即初始化)的技术对普通的指针进行封装。智能指针本质上是个类,在对象初始化的时候获取内存的控制权,在析构的的时候自动释放内存,来正确的管理内存。

C++ 标准模板库 STL(Standard Template Library) 提供了四种智能指针,都可以用来进行半自动内存管理。

1.5.1 auto_ptr

C++11之前的智能指针是auto_ptr,一开始它的出现是为了解决指针没有释放导致的内存泄漏。比如忘了释放或者在释放之前,程序throw出错误,导致没有释放。所以auto_ptr在这个对象声明周期结束之后,自动调用其析构函数释放掉内存。

auto_ptr不够方便——没有移动语义,所以不能够作为函数的返回值和函数的参数,也不能在容器中保存autp_ptr,之后被废除了。

1.5.2 unique_ptr

C++11之后有了unique_ptr,它们支持移动语义的存在,这里调用的是移动构造函数。因为移动语义它可以接管原来对象的资源,同时让原来对象的资源置为空。

unique_ptr是个独占式的指针对象,在任何时间、资源只能被一个指针占有,当unique_ptr离开作用域,指针所包含的内容会被释放。

    unique_ptr<string> p1(new string("hi,world")); // 必须采用直接初始化的形式初始化unique_ptr<string> p2(p1); // ❌ 不支持拷贝unique_ptr<string> p3;p3 = p2; // ❌ 不支持赋值

unique_ptr不能拷贝,不能赋值,可以移动(p.release())

unique_ptr<Test> fun()
{return unique_ptr<Test>(new Test("789"));
}
int main()
{unique_ptr<Test> ptest(new Test("123"));unique_ptr<Test> ptest2(new Test("456"));ptest->print();ptest2 = std::move(ptest);//不能直接ptest2 = ptestif(ptest == NULL)cout<<"ptest = NULL\n";Test* p = ptest2.release();p->print();ptest.reset(p);ptest->print();return 0;
}

release()和reset() 这两个函数都是将指针的所有权从一个(非const)unique_ptr转移给另一个unique_ptr。
reset()还能好一点,可以释放内存,但是release()就不行了,release()必须有 接盘侠,接了要么可以自动负责释放,要么负责手动释放。

1.5.3 shared_ptr

shared_ptr是智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。例如,赋值时,计数将加1,而指针过期时,计数将减1,。当减为0时才调用delete。

int main()
{shared_ptr<Test> ptest(new Test("123"));shared_ptr<Test> ptest2(new Test("456"));cout<<ptest2->getStr()<<endl;cout<<ptest2.use_count()<<endl;ptest = ptest2;//"456"引用次数加1,“123”销毁ptest->print();cout<<ptest2.use_count()<<endl;//2cout<<ptest.use_count()<<endl;//2ptest.reset();ptest2.reset();//此时“456”销毁cout<<"done !\n";return 0;
}

1.5.4 weak_ptr

weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。

weak_ptr是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。

1.5.5 智能指针注意

  • 尽量用make_shared/make_unique,少用new
  • modern c++ 在resource acquisition的时候应该使用智能指针。
  • 不要用raw ptr + new 去获取资源。这样做有三个好处:(1)明确资源的ownership(2)避免忘记delete这种人类容易犯的错误(3)更好地handle exception。

2. c++面向对象

面向对象三大特性:

  • 封装性:数据和代码捆绑在一起,避免外界干扰和不确定性访问。封装可以使得代码模块化。
  • 代码重用:
    继承是一种类复用技术,表示类的“一般与特殊”关系。让某种类型对象获得另一个类型对象的属性和方法。
    组合也是一种类的复用技术,用于表示类的“整体与部分”关系,例如,主机、显示器、键盘、鼠标组合成一台计算机。
  • 多态性:同一事物表现出不同事物的能力,即向不同对象发送同一消息,不同的对象在接收时会产生不同的行为(重载实现编译时多态,虚函数实现运行时多态)。多态的目的则是为了接口重用

2.1 封装

C++对C的最根本的改变就是把函数放进了结构之中,从而产生了C++类。

类把数据和函数捆绑在一起,其中数据表示类的属性(数据成员),函数表示类的行为,也称为成员函数、方法或者服务。

C++提供了关键字public、private和protected用于声明哪些数据和函数是可以公开访问的、私用的或者是受保护(受限访问)的,这样就达到了信息隐藏的目的,即让类仅仅公开必须让外界知道的内容,而隐藏其他一切内容。

class WhoAmI {public:void GetMyName(void);  //名字是可以公开的
protected:void GetMyAsset(void);  // 财产是受保护的,只有我和继承者可以使用
private:void GetMyGuilty(void);  // 罪过是保密的,只有自己才能知道
}

2.2 代码重用

2.2.1 继承

继承达到了重用代码功能和提高执行效率的效果。

当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。

// 基类
class Shape
{public:void setWidth(int w){width = w;}void setHeight(int h){height = h;}protected:int width;int height;
};// 派生类
class Rectangle: public Shape
{public:int getArea(){ return (width * height); }
};int main(void)
{Rectangle Rect; Rect.setWidth(5);Rect.setHeight(7);cout << "Total area: " << Rect.getArea() << endl;return 0;
}

2.2.2 组合

组合(Composition)也是一种类的复用技术,用于表示类的“整体与部分”关系。事实上,我们在写代码的时候,钟情于组合而不是继承,有助于降低代码的耦合性。

例如,眼(Eye)、鼻(Nose)、口(Mouth)、耳(Ear)是头(Head)的一部分,所以类Head应该由类Eye、Nose、Mouth、Ear组合而成,不是派生而成。

class Eye{public:void Look(void);
};
class Nose{public:void Smell(void);
};
class Mouth{public:void Eat(void);
};
class Ear{public:void Listen(void);
};class Head{private:Eye my_eye;Nose my_nose;Mouth my_mouth;Ear my_ear;
public:void Look(void){ my_eye.Look();}void Smell(void){ my_nose.Look();}void Eat(void){ my_mouth.Look();}void Listen(void){ my_ear.Look();}
}

2.3 多态

2.3.1 多态的概念

多态性(polymorphism)可以简单地概括为“一个接口,多种方法”,它是面向对象编程领域的核心概念:

  • 封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。
  • 而多态的目的则是为了“接口重用”。“接口与实现分离”:不仅要把数据成员(信息)隐藏起来,而且还要把实现完全隐藏起来,只留一些接口给外部调用。即使将来实现改变了,接口仍然可以保持不变。
  • 运行时多态,也即,在运行时,不论传递过来的究竟是类的哪个对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。

2.3.2 c++多态使用(virtual函数)

在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。

class Person
{
public :       virtual void BuyTickets()      {           cout<<" 买票"<< endl;     }
protected :      string _name ;   // 姓名
};  class Student : public Person
{
public :  virtual void BuyTickets()  {           cout<<" 买票-半价 "<<endl ;  }
protected :  int _num ;   //学号
};  //void Func(Person* p)
void Func (Person& p)  // 声明基类类型的指针,指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法
{  p.BuyTickets ();
}  void Test ()  {  Person p ;  Student s ;  Func(p );  Func(s );
}

这就是多态最常见的用法:Func参数是基类类型的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。

如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。因为没有多态性,函数调用的地址将是固定的,因此将始终调用到同一个函数,这就无法实现“一个接口,多种方法”的目的了。

2.3.3 多态实现原理

C++支持两种多态性:编译时多态性,运行时多态性。

  • 编译时多态性(静态多态):通过重载函数实现:在编译时就可以确定函数地址。
  • 运行时多态性(动态多态):通过虚函数实现 :运行时在虚函数表中寻找调用函数的地址。

我们研究下运行时多态的原理:虚函数表。

  1. 用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。
  2. 存在虚函数的类都有一个虚函数表(虚表), 虚函数表存储了类的virtual成员函数指针。虚函数表是由编译器自动生成与维护的,当类中声明虚函数时,编译器会在类中生成一个虚函数表。
  3. 类的对象有一个指向虚表开始的虚指针(vptr),vptr一般作为类对象的第一个成员。

2.4 c++对象内存模型

2.4.1 对象数据和成员函数

  • 非静态数据成员被放在每一个对象体内作为对象专有的数据成员
  • 静态数据成员被提取出来放在程序的静态数据区内为该类所有对象共享,因此仅存在一份。
  • 静态和非静态成员函数最终都被提取出来放在程序的代码段中并为该类的所有对象共享,因此每一个成员函数也只存在一份代码实体。
  • 构成对象本身的只有数据,任何成员函数都不隶属于任何一个对象,非静态成员函数与对象的关系就是绑定,绑定的中介就是this指针。

2.4.2 继承和虚函数

增加了继承和虚函数的类的对象模型变得更加复杂

  • 派生类继承基类的非静态数据成员,并作为自己对象的专用数据成员
  • 派生类继承基类的非静态成员函数并可以像自己的成员函数一样访问。
  • 为每一个多态类创建一个虚函数指针数组vtable,该类的所有虚函数(继承自基类或者新增的)的地址都保存在这张表里。
  • 多态类的每一个对象(如果有)中安插一个指针成员vptr,其类型为指向函数指针的指针,它总是指向所属类的vtable
  • 如果基类已经插入了vptr,则派生类将继承和重用该vptr

3. C++泛型编程

泛型编程是一种新的编程思想,它基于模板技术,有效地将算法与数据结构分离,降低了模块间的耦合度。

3.1 泛型编程举例

所谓泛型,是指具有在多种数据类型上皆可操作的含意,在C++中实际上就是使用模板实现。

比如我们要比较两个数的大小,这两个数的类型可能是int,也可能是float,还有可能是double。

一般编程我们需要三个函数:

int max(int a, int b){retuan a > b?a:b;
}
float max(float a, float b){retuan a > b?a:b;
}
double max(double a, double b){retuan a > b?a:b;
}

使用泛型编程,只需要一个函数

template <class T>
T max(T a, T b){return a > b ? a: b;
}

3.2 函数模板与类模板

函数模板是一种抽象的函数定义。通过用户提供的具体参数,C++编译器在编译时刻能够将函数模板实例化,根据同一个模板创建出不同的具体函数。这些函数之间的不同之处主要在于函数内部一些数据类型的不同,而由模板创建的函数的使用方法与一般函数的使用方法相同。函数模板的定义格式如下。

函数模板max的实例化是由编译程序在处理函数调用时自动完成的。当调用max(1, 2)时自动生成实例max<int, int>,而调用max(1.1f, 2.2f)时自动生成实例max<float, float>。

template<TYPE_LIST, ARGLIST>Function_Definition;

类模板是一种更高层次的抽象的类定义,用于使用相同代码创建不同的类模板的定义。类模板的实例化必须由程序员在程序中显式地指定。

template <<模板参数表>>
class <类模板名>
{<类模板定义体>}
template<class T1, class T2>
class Point_T
{public:T1 a;T2 b;Point_T(T1 ta, T2 tb): a(ta), b(tb){};
};Point_T<int, int> int_pt(1, 2);
Point_T<float, float> float_pt(1.1, 2.2);

下面是一个使用模板实现一个Stack的例子:

template <class T>
class Stack
{public:Stack(int len=10);~Stack(){delete[] stackPtr;}int push(const T&);int pop(T&);int isEmpty(){return top == -1;}int isFull(){return top == size -1;}
private:int size;int top;T* stackPtr;
}

4. STL容器

STL(Standard Template Library)是C++标准库的最主要和最重要的组成部分。C++ 的库在标准化之前,仅包含一些I/O组件、string类以及C函数库等极少的内容,而且还不是模板化的。模板的概念被提出和实现以后,开发一些实用的泛型容器和泛型算法成为可能。C++ 标准化委员会在1993年接受了Alexander Stepanov的建议,给标准C++增加了STL。正如BjarneStroustrup所说的那样:“自从1991年以来,对C++ 最重要的改变不是语言本身的改变,而是增加了标准库。”

STL(标准模板库)由三大部分组成:容器、算法和迭代器。

4.1 容器

在C++ 中,我们使用信息隐藏和封装技术把数据隐藏在类内部不许外部直接操作,同时提供访问器(如get_xxx成员函数)和修改器(如set_xxx成员函数)。STL容器的设计原理如出一辙,只是它们在实现时考虑的问题更多、更复杂而已。

容器不仅把元素对象隐藏了起来,而且把元素对象的内存申请和释放操作也全部隐藏了起来(通过存储分配器),这就使程序员彻底摆脱了直接操纵底层内存指针的麻烦,也避免了危险。经过严格测试的容器类几乎不可能存在动态内存管理上的问题,因此可以放心使用。

常见的容器包括:

vector容器
deque双端数组
stack栈模型
queue队列模型
list链表模型
priotriy_queue优先级队列
set与multiset容器
map与multimap容器

4.2 迭代器

容器迭代器的作用类似于数据库中的游标(cursor),它屏蔽了底层存储空间的不连续性,在上层使容器元素维持一种“逻辑连续”的假象。

通过迭代器,我们可以很方便对容器中的元素进行遍历,以及操作容器。

【建议1】: 尽量使用迭代器类型,而不是显式地使用指针。例如,使用vector::iterator,而不是int *,虽然它们是等价的。
【建议2】: 只使用迭代器提供的标准操作,不要使用任何非标准操作,以避免STL版本更新的时候出现不兼容问题。
【建议3】: 当不会改动容器中元素值的时候,请使用const迭代器(const_iterator)。

4.3 泛型算法

STL定义了一套丰富的泛型算法,可施行于容器或其他序列上,它们不依赖具体容器的类型和元素的数据类型。

STL提供的泛型算法主要有如下几种:

  • 查找算法,如find()、search()、binary_search()、find_if() 等。
  • 排序算法,如sort()、merge() 等。✧
  • 数学计算,如accumulate()、inner_product()、partial_sum() 等。
  • 集合运算,如set_union()、set_intersection()、includes() 等。
  • 容器管理,如copy()、replace()、transform()、remove()、for_each() 等。
  • 统计运算,如max()、min()、count()、max_element() 等。
  • 堆管理,如make_heap()、push_heap()、pop_heap()、sort_heap()。
  • 比较运算,如equal() 等。

4.4 STL如何实现vector

vector内部是使用动态数组的方式实现的。如果动态数组的内存不够用,就要动态地重新分配,一般是当前大小的两倍,然后把原数组的内容拷贝过去。

所以,在一般情况下,其访问速度同一般数组,只有在重新分配发生时,其性能才会下降。

它的内部使用allocator类进行内存管理,程序员不需要自己操作内存。

template<class _Ty, class _A = allocator<_Ty>>
class vector{...
}

_Ty类型用于表示vector中存储的元素类型,_A默认为allocator<_Ty>类型。

这里需要说明的是allocator类,它是一种“内存配置器”,负责提供内存管理(可能包含内存分配、释放、自动回收等能力)相关的服务。于是对于程序员来说,就不用关心内存管理方面的问题了。

参考

C++多态的实现原理
C++ 多态
malloc与free的底层实现
linux-malloc底层实现原理
c++ 智能指针用法详解
《高质量程序设计指南》 林锐

重新学习c++--理解引用、智能指针、虚函数、模板、容器相关推荐

  1. C++“准”标准库Boost学习指南(1):智能指针Boost.smart_ptr

    我们学习C++都知道智能指针,例如STL中的std::auto_ptr,但是为什么要使用智能指针,使用它能带给我们什么好处呢? 最简单的使用智能指针可以不会因为忘记delete指针而造成内存泄露.还有 ...

  2. [C++11]弱引用智能指针weak_ptr初始化和相关的操作函数

    弱引用智能指针 std::weak_ptr 可以看做是 shared_ptr 的助手,它不管理 shared_ptr 内部的指针.std::weak_ptr 没有重载操作符 * 和 ->,因为它 ...

  3. C++学习之路: 智能指针入门

    引言: 编写智能指针的要点: a) 构造函数接收堆内存 b) 析构函数释放内存 c) 必要时要禁止值语义. d) 重载*与->两个操作符 1. 简易的智能指针 . 1 #ifndef START ...

  4. C++专题:异常处理与转换函数,智能指针,STL模板

    目录 异常处理 转换函数 智能指针 STL标准模板库 异常处理 什么是异常? 程序中常见的错误分为两大类:编译时错误和运行时错误.编译时的错误主要是语法错误,如关键字拼写错误.语句末尾缺分号.括号不匹 ...

  5. 9.C++弱引用智能指针weak_ptr的用处

    weak_ptr也是一个引用计数型智能指针,但是它不增加对象的引用计数,即弱引用.与之相对,shared_ptr是强引用,只要有一个指向对象的shared_ptr存在,该对象就不会析构,直到指向对象的 ...

  6. C++ 11 深度学习(六)智能指针综述

    以下三种智能指针均为类模板 1.shared_ptr  共享指针   ,多个指针指向同一个对象,最后一个指针被销毁时,这个对象会被释放. 2.week_ptr 是辅助shared_ptr工作的 3.u ...

  7. 《C++捷径教程》学习笔记【一】:虚函数

    声明虚函数的方法是在基类中的成员函数原型前加上关键字virtual.格式如下: class 类名{ -- virtual 类型 函数名(参数表): -- }: 当一个类的成员函数声明为虚函数后,这就意 ...

  8. C++学习12:C++多态、虚函数、虚析构函数、纯虚函数、抽象类

    一 多态概述 C++中的多态分为静态多态和动态多态.静态多态是函数重载,在编译阶段就能确定调用哪个函数.动态多态是由继承产生的,指同一个属性或行为在基类及其各派生类中具有不同的语义,不同的对象根据所接 ...

  9. 理解数据成员指针、函数成员指针

    转自:http://www.cnblogs.com/malecrab/p/5572119.html 1. 数据成员指针 对于普通指针变量来说,其值是它所指向的地址,0表示空指针. 而对于数据成员指针变 ...

最新文章

  1. PHP数据库连接池SQL Relay安装使用
  2. 自己试验写的一个文本转语音的一个小程序
  3. C++ 输入输出流 文本文件 二进制文件读写
  4. 角色与网站地图的结合
  5. python开发是不是苦累_Python 2.7 辛苦了,你好Python 3.7
  6. python STL分解
  7. python从2 1 2 2 2 63_Python从零开始第三章数据处理与分析python中的dplyr(2)
  8. H5中 video 使用border-radius失效解决方法
  9. 《飞猪规则》 第二章 自由行及跟团游类商品发布规范
  10. java StringBuilder用法,用逗号拼接字符串 zhaoqian,sunli,zhouwu
  11. 《数据库与信息管理课程设计》
  12. “秒杀系统“设计原理
  13. Python:NBA球员得分数据排行爬虫
  14. 【R语言爬虫】R语言提交post请求抓取盈盈理财数据
  15. [基础代码仓库]基于STM32C8T6的库函数ADC模拟+DMA多通道转运代码
  16. 小白都能看懂 XAMPP的下载安装配置详细教程(含拒绝访问坑)
  17. 数据网站 免费数据网站
  18. 逻辑代数的基本定律和运算规则
  19. hexo 博客next主题集成gitment或者gitalk评论系统
  20. 黑马程序员Java零基础视频教程(2022最新Java)B站视频学习笔记-Day14-面向对象进阶02

热门文章

  1. 疲劳驾驶样本集_欧洲要求,2022年开始新车必须配备DMS(防疲劳预警)系统
  2. 邮件 自动打印 linux,Linux打印文件和发送邮件
  3. torch.round()
  4. 北大mooc课程平台
  5. Aligned TripletLoss
  6. php csrf攻击教程,HTTP路由实例教程(三)—— CSRF攻击原理及其防护
  7. Eureka Server 集群
  8. 断点帧数测试软件,《幽灵行动:断点》PC版性能表现分析
  9. linux管理防火墙开放端口
  10. 中移4G模块-ML302-OpenCpu开发-(MQTT连接阿里云-订阅主题)