C++ Primer学习笔记

关键词:C++
作者:BIce 创建时间:2012-10-31 13:53:55

自从学过C++到现在也有五年了,其间虽然一直在用C++,但是却真的不是很了解C++C++ Primer一书也不是看过一次了,只不过没有真正静下心来看过,最近刚好找到了这么一个月的时间,在做一些事的同时抽出时间好好看了一下这本久负盛名的经典著作,故记录。

本次记录,主要以C++C语言的比较引入,按如下几个方面进行:1. C++ & STL; 2. C++中被隐藏起来的部分——C++默认帮我们做的; 3. 面向对象及复制控制; 4. C++的语法和各种关键字。

1.Standard Template Library

STLC++赋予我们的强大武器,在其中定义了很多我们进行开发中需要用到的:数据结构、数据类型、算法模板等等。

  • 数据结构,STL中覆盖了我们经常使用的所有数据结构,在下面简单记录
    • vector:顺序容器,是可动态申请空间的可变长数组,支持随机访问,只能在vector的尾部进行快速插入,首部进行快速删除
    • deque:顺序容器,双向的vector,队列两端的掺入和删除元素都非常快,支持随机访问
    • list:顺序容器,双向链表,任何位置的插入和删除操作都很快,不支持随机访问
    • string:顺序容器,与vector类似,连续存储,类似保存字符的vector
    • pair:关联容器中的存储类型,包含一对可以是不同类型的数据
    • map:关联容器,存储一个key-valuepair的集合,要求key必须定义小于(<)操作符,且不能有key重复的pair出现(允许重复key的类型为multimap)
    • set:关联容器,仅存储key的集合,对key要求与map一致(multiset)
    • bitset:用以处理bit集合的数据结构
  • 数据类型:由于C++标准中对于基本内置数据类型(如int, long等)的实际存储空间没有限制,只规定了内置类型最小的存储空间,这样带来的问题就是:由于编译器和实际机器的不同,这些内置数据类型可以表示的范围是不同的。而对于vector这种标准库,我们显然不能假定它最大或最小的存储范围,以任何一个内置类型来表示vector的大小都是不合适的,因此STL中给出了这些数据结构的一些相关类型,如:size_type类型可以表示大小,difference_type类型可以表示迭代器的差值,等等。(p272)
  • 算法模板(泛型算法):提到算法,STL中提供了各种各样的算法,(就像它包括最基本的排序sort和查找find),这些算法针对的一般都是STL中的数据结构,最一般的是针对容器类结构的算法(如sortfind)。
    • 泛型算法都是独立与容器的,即它们可以应用于各个类型的容器中。如何做到这一点呢?泛型算法大都是依赖于迭代器的,由迭代器将算法和容器绑定起来,算法并不执行容器提供的操作,而是依赖迭代器和迭代器的操作来实现。
    • 虽然说算法是独立于容器的,但这并不表明算法可以应用于所有的容器,它对迭代器,以及容器的元素类型是有一定要求的:比如find算法就要求元素至少要定义==运算符。
    • 提到迭代器,它与指针很类似,而C++中迭代器大概有五类:输入迭代器;输出迭代器;前向迭代器;双向迭代器;随机访问迭代器。不同的容器可以提供的迭代器是不同的,比如list就不能给出随机访问迭代器,但一个容器的迭代器可能满足上述多个迭代器要求。
    • 每个泛型算法都对迭代器有着不同的要求,如sort就需要随机访问迭代器,它可以用于排序vectordeque,但不可以用于list
    • 泛型算法的形参都遵循一定的格式,
      1. 以一个容器为输入的,有alg(beg,end,params)alg(beg,end,dest,params)两种,其中由begend来指出容器的访问范围,而dest则指出算法的输出目标,算法默认dest指出的容器大小与beg-end指定的范围一样大,如果给出一个未初始化或者大小不符的容器,可能导致运行时异常(可用插入迭代器解决)。
      2. 以两个容器为输入的,有alg(beg,end,beg2,params)alg(beg,end,beg2,end2,params)两种形式,如果未指定end2的话,算法默认beg2指定容器的范围至少与beg-end的范围一样大

2.C++中的各种默认行为

相对于C语言,C++给了我们强大的功能,但是这强大的功能背后隐藏着的是各种复杂的机制,也就是C++的语言特性,对于这些默认行为,在这里简单记录如下几个:声明与定义;初始化与赋值;指针与引用;隐式转换;函数重载匹配。而C++关于对象的默认行为,以及复制控制的内容,将在下一部分记录。

  • 变量声明与定义:C++中的声明和定义是区分开来的,像C++中一个完整的类要有.h类声明文件和.cpp(.cc)类定义一样,变量的声明和定义也是不同的。变量的定义(definition)用于对变量分配存储空间,对于一个变量来说,只能有一次。变量的声明(declaration)则是向程序表明变量的类型和名字,虽然可以用声明顺便定义变量,但也可以用extern关键字来声明一个变量而不是定义它。
  • 变量初始化与复制:C++中变量的初始化和复制完全是两个不一样的操作,初始化指创建变量并给它附上初值,而复制是擦除对象的当前值并用新值代替(此点在对象的复制控制时尤其重要)
  • 变量初始化原则:
    • 内置类型的初始化:如果是在函数体外(全局)进行定义,则内置类型的变量会自动被初始化成0;如果是在函数体内进行定义,内置类型将不会被初始化(可能为任何值,与分配的内存相关)
    • 类类型的初始化:不论在何处定义,都会被初始化,如果未给出初始化式,则会使用默认构造函数(未定义初始化式时调用的无参构造函数)进行初始化。
  • 变量作用域:每个变量都有自己的作用域,在离开作用域之时,其生命期结束,局部变量会被清理掉(典型的作用域是函数,无论出现什么情况,在函数中的局部变量在函数退出后都会被有效地清理,局部对象会被调用析构函数清除),但以new关键字定义的变量不能被自动清除,只能用delete关键字才能清理回收
  • 参数传递:C++中默认的函数参数传递都是传值型,不论对象还是内置类型(不像Java是对象传引用,内置类型传值),如果说参数是对象的话,C++会用实参作为参数调用对象的拷贝构造函数来创建一个新对象而不是传递原来的对象(在向容器中加入元素的时候也都是如此)。在C中,想要传递一个数据而不重新创造它的办法是传递一个指向此数据的指针,而C++中,为了避免复杂的指针操作,给出了引用的概念,通过声明引用,就可以在形实参结合时传递相应对象而不进行初始化新对象的动作了。
  • 引用:引用和指针的概念类似,不同的是引用一旦初始化就不能指向其他的对象了,引用相当于别名。说引用不能指向其他对象是基于C++语法决定的,考虑如下式子:
    • object obj= & a; obj=b;//其中a,b都是object类的实例对象
    • 在执行obj=b时,不会执行引用的赋值,而会调用a=操作符,以b对象对a进行复制。
  • 指针:无需多说,唯一要确认的是C++中没有多维数组,所谓多维数组,即是数组的数组罢了
  • 隐式转换:C++中最令人头疼的就是这些隐式转换了,为了方便开发者,C++中定义了一些隐式的转换,帮助我们简化代码,但是这样也给我们带来的不小的困惑。
    • 发生隐式转换的时机:(表达式中,函数调用中)
      1. 混合类型表达式,操作数被转换成相同的类型(转换成比较精确的那个类型),对于算术类转换,C++定义了转换的层次,最常用的是整形提升,如果存储空间小于intchar, signed char, unsigned char, short, unsigned short等可以被包含在int型的值,都会直接被转换成int,而int之上的提升是向上转换。
      2. 条件表达式中,各种值都会转变成bool
      3. 用一个表达式初始化或者赋值给某个变量,该表达式值被转换为该变量的类型
      4. 在处理函数调用语句时时,需确定调用的函数(重载时尤为明显),在确定的过程中则将实参进行隐式转换,找到最匹配的函数时,按转换后的参数对函数进行调用(在形参是对象类型时,还可以通过调用以相应实参类型为参数的构造函数来执行默认的隐式转换,可以通过explicit关键字来消除对象初始化的隐式转换)
    • 其他隐式转换的时机:
      1. 数组名一般会被转换成相应的指针
      2. 算数值和指针值都可以被转换成bool值(0false,其他值为true
      3. C++自动将枚举类型的成员转换成整型
      4. 需要用非const对象初始化const对象的引用时(或指针),会将非const对象转换为const对象。
  • 显式转换:C++中定义了四种显式转换
    • static_cast,任何隐式转换都可以由显式转换完成、
    • dynamic_castC++ RTTI特性的一部分,支持运行时识别指针或引用指向的对象,可以完成由父类指针或对象到子类指针或对象的转换
    • const_cast,可以转换掉表达式const的性质
    • reinterpret_cast,为操作数的位模式提供较低层次的重新解释,本质上依赖于机器,目前不是特别了解这个关键字
  • 函数重载:C语言没有函数重载(overloaded function)的机制,函数名必须是唯一的,而在C++中则不同,允许函数的重载,不同重载的区别是函数参数的个数和类型,不能只以返回类型来指定函数重载。(有const引用形参的函数与有非const引用形参的函数被认为是不同的重载,原因想想就知道了)
    • 重载函数确定:在进行函数调用时,需要将函数调用与重载函数集合中的一个函数相关联,它有三个步骤:
      1. 确定该函数调用所需考虑的重载函数集合,该集合中的函数被称为候选函数(同名的)
      2. 从候选函数中选择一个或多个函数作为可行函数,它们要满足:函数的形参个数与实际调用相同;每个实参的类型必须与对应形参类型匹配,或者可以被隐式转换成对应的形参类型
      3. 寻找最佳匹配:确定可行函数集中与该函数调用中实参有最佳匹配的一个,如果找到多个则有二义性,出错。
    • 最佳匹配的条件:只有一个函数满足下列条件则成功
      1. 函数实参的匹配都不劣于其他可行函数
      2. 至少有一个实参的匹配优于其他可行函数
    • 实参类型转换等级:为确定最佳匹配,编译器指定了实参类型到形参类型转换的等级,等级高的就比其他的更优,由高到低分为:精确匹配;类型提升;标准转换;类类型转换
  • 函数指针:指向特定函数类型的指针,函数类型由返回类型及形参表确定,与函数名无关,形式如下
    • bool (*pf)(const string &, const string &);//声明pf为一个函数指针

*pf外边的括号是必须的,因为如果不加上的话,它就会被认为是一个合理的函数声明,更好的定义为,typedef bool (*cmpFcn)(const string &, const string &);,定义cmpFcn为一个指向函数的指针类型

  • 函数指针只能通过同类型的函数或函数指针或者0值进行初始化或赋值
  • 直接引用函数名等小于在函数名上使用&操作符,而调用函数可以直接使用()操作符即可,也可以加上*操作符再调用()
  • 函数指针的类型不存在转换一说,必须精确匹配
  • 返回函数指针的函数

int ( * ff( int ) ) ( int *, int );//定义了一个函数参数为int,名字为ff,它将返回一个int (*) ( int * , int )的函数指针

 

  • 操作符重载:这是本部分最后一个内容了(呼呼……),总的来说C++的设计者想把它设计成为一个功能强大、又十分简洁的语言,这样做的好处就是C++中有着很多简洁的语法,关键字比C也基本没有扩充,代价就是有很多简洁代码的背后,有着我们并不知道的一系列操作正在进行,下面说一下操作符重载的问题:
    • C++允许类的设计者自由定义在自己类上运行的操作符(如+,-,*,/<<,>>,&,*,->,()等等)的各种操作,它们可以作为类成员函数出现(要求第一个参数是自己的this),也可以作为类的友元函数来出现(比如针对<<>>的重载,第一个操作符肯定是流对象,我们不可能修改标准库的代码,就只能作为友元函数来实现了)。
    • 赋值操作符=,作为复制控制的一部分,在下一个部分记录
    • 书中给出了一个智能指针的实例,实现方法就是重载智能指针的解引用操作符*和箭头操作符->
    • 算术操作符和关系操作符,一般定义为非成员函数,而对于<操作符和==操作符的定义一定要慎重,一般的如果ab两个对象,a<bb<a都不成立,那么就默认相等关系了
    • 自增自减操作符,前缀式正常,后缀式接受一个int实参(无意义,仅为区别)
    • 调用操作符(),定义了调用操作符的对象,被称为函数对象,行为类似函数,可以在对象的实例后调用()来执行某个具体操作,经常被用于泛型算法的实参。标准库也定义了一些函数对象(p452),标准库还定义了一组函数适配器来特化和扩展函数对象

3.面向对象及复制控制

C++中最大的改动就是引入了面向对象的机制,考虑了面向对象机制之后,第二部分所描述的问题才暴露出来,有的甚至更加复杂,本部分首先简单记录一下C++面向对象的基本,然后对复制控制进行记录,最后再次讨论操作符重载,主要是转换操作符重载的问题。

3.1  C++面向对象基本

C++中,定义一个类可以由classstruct两个关键字来指定,唯一的不同是class关键字指定的类默认的访问控制符(Access Label)是private的,而struct关键字指定的类默认是public的。(C++classstruct没有其他的区别了,struct定义类一样可以完成多态等任务)

  • Access LabelC++允许的成员访问控制符有三个,
    • public, 允许外部访问,一般作为类的接口出现
    • private, 不允许外部或自己的子类进行访问,作为隐藏的信息不对外开发,一般涉及细节
    • protected, 不允许外部访问,允许子类访问

另外,允许在类中使用friend关键字,指定友元函数或者友元类,使之可以像访问自身成员一样访问本类中的成员

  • 继承,面向对象的基石。C++中一个类可以作为派生类(Derived Class)藉由类派生列表继承与多个基类(Base Class),而由这些由继承关联的类,我们可以得到一个继承层次,派生类中拥有所有基类的完整部分(包括所有数据成员,但不一定可以访问)
  • 而在进行继承的时候,也可以指定三种继承,
    • public继承,基类的成员在派生类中延续自己的访问级别(protectedpublic不变,private无论如何不能访问),一般我们使用的继承都是public继承
    • private 继承,基类的protectedpublic在派生类中变为private
    • protected继承,基类的protectedpublic在派生类中变为protected
  • 虚函数,virtual关键字指定,面向对象的基石,多态的实现方式,基类希望派生类重新定义的函数,只能藉由引用和指针来完成,具体无须多说你懂的

3.2 C++类生命周期及复制控制

C++类的实例对象从创建到销毁,程序员都可以控制,但是如果放弃控制的话,C++会提供一些默认的行为帮助我们简化工作,这里主要记录此类默认行为。

  • 构造函数,负责对象的初始化工作
    • 默认构造函数,无参的(或者有默认实参的)构造函数,在初始化对象的时候,如果没有给出初始化式,而只有定义式的时候,编译器会调用这个无参的构造函数,完成对象的初始化工作
    • 合成默认构造函数,当类没有自己定义一个属于本身的构造函数之时,编译器会合成一个构造函数,完成对象的初始化工作,执行默认行为,它的执行行为大致如下:
      1. 如果类是派生类,会首先按照类派生列表的顺序,依次调用基类的默认构造函数,完成基类部分的初始化工作
      2. 完成基类的初始化部分之后,会按照类成员变量的声明顺序,依次初始化变量,具有类类型的成员使用自身的默认构造函数进行初始化,内置和复合类型的成员(指针和数组),只对定义在全局作用域的对象实例才初始化,否则不会进行初始化动作,这个规则与变量的初始化规则是一样的。(所以如果类包含内置或复合类型的成员,则不应该依赖与合成的默认构造函数
    • 构造函数初始化列表,每个构造函数几乎都应该使用初始化列表,原因在于:构造函数的执行分为两个阶段,一是初始化阶段,对成员进行初始化;二是函数体的计算阶段,也就是构造函数中语句的执行阶段。而如果使用初始化列表,构造函数会使用初始化列表的值对成员进行初始化;如果不用初始化列表,构造函数会先先对成员进行默认的初始化,然后在计算阶段再进行赋值的动作,这样不但会多进行一些操作,还对成员类型多了隐性的要求(如果是类类型则必须有默认构造函数,const或引用的成员则无法初始化,必须在初始化列表中才能进行初始化)。
    • 如果是派生类的构造函数,则最好是要在初始化列表中手动调用直接基类的构造函数来初始化基类部分
  • 析构函数,负责对象的销毁,以及资源的释放工作。在变量超出作用域时,会自动撤销;不过使用new操作符动态分配的对象只有使用delete操作符才能撤销,因此当对象的引用或指针超出作用域时,不会导致其指向对象的撤销,可能会导致内存泄漏。(撤销一个数组或者STL容器时,也会自动按逆序撤销容器内的元素)
    • 如果可能作为基类,那么析构函数应该是虚函数,此是为了保证在使用基类指针进行delete操作时,可以正确的调用派生类的析构函数,释放派生类的成员。但是其他复制控制函数(构造函数和赋值操作符重载)则不可以定义为虚函数,会产生一些麻烦,也没什么用处
    • 如果类需要定义析构函数,则类几乎一定需要其他复制控制函数(拷贝构造函数和赋值操作符重载)
    • 合成的析构函数:编译器总会为我们合成一个析构函数,无论我们给不给出,就算我们给出了自己的析构函数,合成的析构函数也会在执行完自定义的析构函数后执行,合成析构函数会按对象初始化时的逆序(声明类成员的逆序)撤销每个成员,针对类成员,合成析构函数会调用成员的析构函数来撤销对象(但是合成析构函数并不删除指针成员指向的对象)。
    • 和构造函数不同,派生类的析构函数中只负责自身的成员即可,也无需调用基类的析构函数
    • 析构函数必须保证不能抛出异常,否则会导致严重错误
  • 复制控制,在定义类对象时,需要隐式或者显式执行的,复制、复制和撤销对象的操作,这些行为由特殊的类成员函数:复制构造函数、赋值操作符和析构函数来完成。之前已经说过来析构函数,下面主要记录复制构造函数和=赋值操作符的重载。
    • 复制构造函数,有单个形参的构造函数,此形参一般为该类型的const引用。在定义一个新对象并用一个同类型的对象对其进行初始化时,将显示的使用复制构造函数。当将该类型对象传递给函数作为实参或者从函数返回该类型的对象时,会隐式调用复制构造函数。
      1. 合成的复制构造函数,如果没有定义复制构造函数,编译器会为我们合成一个,它的默认行为是,会执行逐个成员的初始化动作(仅仅简单的复制,如果有指针成员,则一般不能依赖于合成的复制构造函数,需要自己给出复制构造函数,指定指针成员的复制策略),将对象初始化为原对象的副本。(如果有数组成员,合成函数会复制数组,将复制数组的每一个元素)
    • 赋值操作符=的重载,在调用了对象赋值的动作时(也就是=操作符时),编译器会调用赋值操作符。
      1. 合成的赋值操作符,如果没有定义赋值操作符,编译器会为我们合成一个,它的默认行为是执行逐个成员的赋值,对于数组则给每个数组元素赋值(有指针成员的话,与复制构造函数一样,一般不能依赖默认的)
    • 一般来说,如果需要复制构造函数和赋值操作符二者其中的一个,也就一定需要另外一个。
  • newdelete的重载(具体见p633)

C++中,使用new操作符来为类对象分配内存,并构造一个对象(实际行为为:调用operate new的标准库函数,申请足够大的内存;然后调用构造函数初始化对象;最后返回对象的指针);

使用delete操作符调用析构函数撤销对象,并将内存返还给系统(实际行为:调用对象的析构函数;调用operate delete的标准库函数,释放对象占用的内存)

但有些时候默认的初始化行为可能不能满足程序的性能要求(利用内存池机制时),这个时候就需要我们手动申请和释放内存,并控制对象的初始化和撤销动作,这时我们就需要使用allocator类来重载以及operate newoperate delete了。

  • allocator类:STL中可以提供类型化内存分配及对象的构造与撤销的模板工具类,可以自由的申请构建N个某类对象的内存,并在内存之上调用构造函数和析构函数
  • operate newoperate delete的重载:如果类想自己管理申请的内存以及初始化类对象时的动作(比如要实现一个自身维护的内存池),就需要重载自身的operate newoperate delete来完成这项任务。(operate new中利用allocator查看内存池,分配内存,不执行初始化动作(由构造函数执行);operate delete中利用allocator维护内存池,回收内存,不执行撤销动作(由析构函数执行))

3.3 C++类对象的隐式转换

C++的类对象同样也会涉及到很多的隐式转换,一个C++类可能会被转换成任意的类型,而任意一个类型也可以转换成一个C++类,这个你信么?本来我是不信的,但是C++的设计者就是为我们提供了这样的实现办法。下面分两个部分分别说明由类型T到类对象,以及类对象到某类型T的转换规则定义方法。

  • 某类型T到类类型A的转换,在某个需要类型A的对象的地方,如果出现的却是类型T的变量(比如形实参结合之时),本来可能会导致语法错误的写法,但在转换构造函数的存在情况下,这种转换就会被编译器隐式执行,不但能通过编译器语法检查,还可以正常执行。
    • 转换构造函数:单个实参的构造函数,实参类型为任意类型T。有这个转换构造函数的存在,就可能存在从类型T到类类型的隐式转换。
    • 这种做法会带来一些简便的写法,不过一样会带来很多的麻烦,可能C++的设计者发现了这一点,遂定义了一个新的关键字explicit,将此关键字加到转换构造函数之前,就会阻止编译器执行默认的转换,本类可行的隐式转换就会被编译器报出语法错误了。
  • 类类型A到某类型T的转换,需要定义转换操作符,在定义了转换操作符的时候,在编译器需要类型T的值(int)时,如果A的对象出现了,会按照转换操作符自动执行从类型AT的转换。
    • 转换操作符,形式如下:operator type();
    • 一个表达式只能进行一次这种转换操作符的转换
    • 如果有多个转换操作符,也会出现重载匹配的问题,规则与其他函数重载类似,不过最好是通过显式的类型转换来指定具体的类型,完成操作

4.其他一些细节

C++是功能强大但又极其复杂的工具,看一本书真的不能学到全部,最多也还是皮毛,下面主要记录一下C++的一些细节问题,还有一些关键字的大概描述。

  • 虚继承:由于C++支持多重继承,这也就带来了一个菱形继承的问题,由于C++的派生类会拥有基类的对象,这样的话如果有菱形继承,一个派生类就会拥有两份上层基类的对象实例,这可能会出现问题,为了解决这个问题,C++提供了虚继承的机制,通过声明虚继承,可以保证一个上层基类只在其后续的派生类的对象空间中只有一个对象
    • 虚继承带来的一个问题是这个唯一的基类由谁来初始化的问题,C++规定由最低的派生类构造函数来初始化虚基类。由于一个类设计时不知道是否会被继承,为了兼容这种情况,需要在每一个虚继承的派生类构造函数中,都要定义好虚基类的构造方式。而在实际初始化一个对象之时,编译器会查找实际的类型,调用它的构造函数,忽略其父类定义的虚基类初始化定义
  • 异常处理:异常处理几乎是现在所有应用在大型工程里必不可少的一部分,C++也提供了完善的异常处理机制。在程序出现异常之时,会执行栈的展开动作,向上寻找处理的语句块,并在离开一个作用域时自动释放局部变量。但这样仍然可能出现资源的泄漏问题,因为某些资源或者动态创建的变量不能被自动销毁,这样对编写异常安全的代码带来了难度。解决办法是定义一个类来封装对资源的分配和释放(构造函数中初始化资源,析构函数中释放资源),这样做的话这个类的对象作为局部变量存在,就算是遇到异常,它也可以被自动销毁,调用析构函数释放资源。这一技术也被称为资源分配即初始化(RAII
    • auto_ptr类,STL中的RAII的实现,为动态分配的对象提供异常安全的支持模板工具。它可以管理从new返回的一个对象。当auto_ptr对象超出作用域时(或被撤销之时)会自动回收auto_ptr所拥有的对象空间。(但不能管理动态分配的数组,因为auto_ptr在赋值和复制的时候有特殊的行为,具有对原对象破坏性的操作,它也不能被放入容器中)
      1. 复制操作:用一个auto_ptr对象b初始化另一个auto_ptr对象a,会导致b将其原本拥有的对象管理权传递给a,而b则失去了管理权,变为一个不管理任何对象,没有绑定对象的auto_ptr
      2. 赋值操作:执行a=b,同样会导致b将对象的所有权传递给a,而b则变为未绑定的。
    • 异常说明:在函数参数列表后的throw(exception)语句,用以指定函数可以throw出的异常,不写的情况下指定函数可以抛出任何异常。写throw()说明函数不会抛出任何异常。在一般的情况下,函数的析构函数必须不能抛出任何异常
      1. 在涉及虚函数和函数指针的时候,函数的异常声明需要遵守一定层次,新声明的函数之异常声明一定不能放宽其基础声明的要求。
  • 模板编程,C++ STL的基础,大大增加C++代码的可重用性。具体语法不再叙述,说实话我现在也不是很了解。。。所以只简单记录下模板实例化的过程。
    • 模板是一个蓝图,而每次具体的使用,都会带来一个实例化的过程。类模板的实例化会生成一个独立的类类型供编译器使用;函数模板实例化时,编译器通过参数进行模板实参推断(template argument deduction)的工作,生成具体的可用函数。
  • C++的特殊关键字们

终于到了总结一下C++的特殊关键字的时候了,曾经我就被某外企的员工一顿问各种关键字,当时无奈太久没有复习C++,再加上当时也学的不好,就被鄙视了。恩下面就简单介绍下我了解的C++的特殊关键字们吧

  • const,此关键字功能巨大,可以用于定义变量(包括形参),定义指针(两处位置两个含义),可以用于定义函数,具体含义大家好好背吧,这里我就不写了。
  • mutable,与const相对,可用于定义在const函数中可修改的成员变量。
  • static,一样功能强大的关键字,可以用于定义类成员变量,类成员函数,也可以用在定义函数的局部变量处
  • explicit,使用在类转换构造函数的关键字,用以消除隐式转换,强制类用户进行显式调用
  • extern,用于声明变量,但不定义,指出变量在其他文件中定义
  • extern “C”,在调用C函数,而不是C++函数时,用于向编译器指出此函数是C函数,防止编译器按C++函数来处理本函数(在遇到函数重载时,C语言不支持重载,而C++支持,加上此指示能防止C++编译器对C函数进行不正确的编译)
  • register,声明寄存器变量,建议编译器将变量放入CPU寄存器中,以加速访问。
  • typeidC++ RTTI的另一部分,接受一个表达式,返回描述表达式类型的type_info的库类型对象引用
  • volatile,告诉编译器可以在程序的直接控制之外改变一个变量(如操作系统时钟),让编译器不能执行某些优化
  • const_castdynamic_caststatic_cast,以及reinterpret_cast,在上面说过。

留言功能已取消,如需沟通,请邮件联系博主sunswk@sina.com,谢谢:)