C++基本知识(持续更新)

来源:互联网 发布:可以sql注入的网站 编辑:程序博客网 时间:2024/05/22 21:56
一、面向对象概念:

1、重载、隐藏、覆盖的区别?

重载规则:
–1.相同的作用域(不同类的同名函数不是重载)
–2.函数名相同。
–3.参数个数、类型、顺序、const限定的指针或引用、是否为常成员函数
–4.virtual及返回值等其他因素不能作为重载依据。

重载函数的选择顺序
先查找严格匹配的(优先选用非模板函数)
再通过内部类型转换查找可以匹配的
最后通过用户自定义的强制类型转换查找

隐藏规则:
1.派生类的函数跟基类的函数同名,其他不完全相同,此时不论有没有virtual关键字,基类函数将被隐藏。(注意有virtual仅返回值类型不同的情况将产生编译错误)
2.派生类的函数跟基类的函数同名,且其余参数完全一致但基类没有virtual关键字,此时基类函数也将被隐藏。

若想解除隐藏,则需要使用using XXX::XXX方式来进行声明,使其可见。
如果本地有定义,则使用本地的;如果本地没有定义,则使用using声明的。
不同于XXX::调用,这是直接用,没有选择。


若想解除隐藏,则需要使用using XXX::XXX方式来进行声明,使其可见。
如果本地有定义,则使用本地的;如果本地没有定义,则使用using声明的。
不同于XXX::调用,这是直接用,没有选择。

覆盖规则:

1.指派生类与基类的成员函数之间

2.基类函数必须有virtual关键字。

3.基类和派生类同名函数的原型完全相同(返回值、函数名、参数表、const)。

4.覆盖是通过虚函数表实现的,覆盖也成为重写。

基类和派生类同名函数的原型完全相同;

如果想使用基类成员函数,可以采用派生类对象.基类::函数或者派生类对象指针->基类::函数;

二、语法
  • 不能建立引用数组,引用不能为空,不能改变;只能引用变量,不能引用表达式;引用&符号挨着变量;
错误案例:
int &&r;             //引用的引用
int &*p;             //引用的指针
int &arr[3];         //引用数组
void &r;             //void类型的引用
int &r = NULL;       //空引用
int *&rn = &n; //ERROR,右值只能是指针变量,不能为表达式,引用要针对变量

  • 数组初始化越界自动检查;如char bob[3] =“abc”;   in C++,是不合法的;
  • Union, struct, enum 声明变量的时候不用加关键字;
  • const和non-const之间不允许隐式转换;需明确强制转换;
  • void *和其他之间不允许隐式转换;需明确强制转换;
  • 函数没有返回值时,用void进行说明;
  • 函数使用前必须声明;
  • 新增运算符 typeid,new,new[],delete,delete[],throw,::*,::->
             ::*为成员指针,如int X::*代表“X类内部指向int型变量的指针”类型
  • 如果定义一个不带namespace的函数,则为全局的,使用“::”来操作它。 //如::max(a, b);

           而平时如max等函数,在std namespace中都存在。//如std::max(a, b);
  • 单一对象可能拥有一个以上的地址。如”pbase“和”pdrived“两个指针,不要假设C++中如何内存布局,他们可能不同。

          如以下情况:

          1)多重继承;

          2)有虚函数的类;

  • “以对象调用成员函数”为静态绑定;而“以指针或引用调用成员函数”为动态绑定;--- 只有动态绑定时virtual采用意义。
  • C++裁定凡是独立(非附属)对象都必须有非零的大小。

    ---------- 如class a{}; sizeof(a)一般为1,而不是0.

    ---------- 单一继承情况下,如果class a被继承,作为基类其占用空间为0。遵循EBO empty base optimization原则。

    ---------- 如果多个空基类同时被继承,只有一个基类占用空间为0,其他基类占用空间都为1,和char类型类似。

                  子类实际的占用空间需要考虑对齐。

  • this指针默认为classtype * const this类型,说明this不可变;

  • 常量成员函数的const修饰是修饰this指针的,修饰后为const classtype * const this类型;如:常量成员函数为void print() const;
  • 全局函数没有this指针,自然不能在其后加const或virtual修饰;
  • 类的静态成员函数与静态数据成员都是属于类的,不是属于对象的,其不含有this指针,所以也就无法访问非静态数据成员;
  • 接口类:只含有纯虚函数的类称为接口类;
  •  多用const,enum,inline;
  • const ...iterator 表明迭代器不能改变;
  • const_iterator表明迭代器所指对象不能改变;
  • 请区分初始化和赋值。尤其在构造函数时,效率不同的。
  • 请在初值列列出所有成员变量;
  • 如果成员变量时const或reference,则一定要有初值;
  • 内置类型也优先使用初值列赋初值;
  • C++不保证不同文件内的non-local static对象的初始化顺序。所以用local static对象替换,即singleton模式。
  • 编译器可以自动生成缺省构造函数,析构函数,copy构造函数,copy assignment操作符。只有需要时,才会被自动生成;
  • 所有编译器产出的函数都是public的
  • 编译器默认产生的析构函数为non-virtual的!!!
  • 如果base class的析构函数为virtual,则编译器自动为其derived class生成的析构函数也为virtual的
  • 默认copy构造函数为浅copy
  • 如果class有reference成员或const成员,则无法编译出“默认copy assignment操作符”
  • 如果base class的copy assignment操作符声明为private,则编译器拒绝为其derived class生成一个copy assignment操作符
  • copy构造函数也是构造函数。如果自定义了,则编译器不会再自动生成默认构造函数;但如果自定义了构造函数,编译器还是可以自动生成copy构造函数
  • 为了阻止编译自动生成的copy构造函数和copy assignment操作符:
方式1:“声明”二者为private类型,只需要“声明”即可。如果外部有调用,则编译器会报错;如果内部成员函数或friend调用,则连接器会报错;
方式2:private方式继承Uncopyable类,必须private继承。这是在编译期就会被报错;
方法3:使用boost的private boost::noncopyable;
  • 只有当class内含至少一个virtual函数,才为它声明virtual析构函数。(分割问题, delete pbase,pbase指向derived class。)
  • 不要无端地使用virtual析构函数,这会使对象体积增加,移植性变差。
  • 不要企图继承一个标准容器或任何其他“带有non-virtual析构函数”的class!!!
  • 如果析构函数为纯虚的,则必须提供一份纯虚析构函数的定义。否则其派生类无法找到其析构函数!!! 为了让基类为接口类;
  • 如果普通成员函数为纯虚的,则基类不需要提供纯虚函数定义;
  • 并非所有class都是为了多态用途:如标准string,STL容器都不被设计为base class使用;如Uncopyable等被设计为base class,但不需要virtual析构函数。
  • operator=返回reference to *this;
  • 任何函数如果操作一个以上的对象,而其中多个对象是同一个对象是,其行为仍然正确。

          方法1:堆复制技术;

          方法2:copy-and-swap技术;

  • copy构造函数,copy assignment操作符都应该确保复制“对象内所有成员变量”及“所有base class成分“;

         1)copy构造函数: 使用初始化列表初始化基类。

         2)copy assignment操作符:使用baseclass::operation=(入参rhs);方式对base class成分进行赋值动作;

  • 资源获取时机就是初始化时机 Resource acquisition is Initialization;RAII
  • 为了防止内存泄露,使用基于对象的资源管理办法。把资源放进对象内,依赖C++的“析构函数自动调用机制”确保资源被释放。
  • 非heap-based资源需要建立自己的资源管理类;
  • 自定义的资源管理类需要仔细考虑copying构造函数的行为,分以下几种:

           1)禁止复制:私有继承Uncopyable类;

           2)引用计数法:使用shared_ptr,自定义删除器;

           3)深copy:

           4)唯一拥有权:如auto_ptr;

  • array可以考虑string和vector,以及boost::scoped_array,boost::shared_array类替换。
  • auto_ptr  ---    智能指针。

                             其“拷贝”行为比较特别,只保留一份。

                             不能用于STL。

                             不允许指定“删除器”

                             std::auto_ptr

                             在#include <memory> 中

    shared_ptr ---      引用计数型智能指针。可用于STL。缺点:无法打破环状引用。

                               允许指定“删除器”。

                               自动使用它的“每个指针专属的删除器”,消除了cross-dll问题。

                               std::tr1::shared_ptr

    (析构函数都是调用delete,而不是delete[])

  • 使用typedef后,要注意new/new []和delete/delete []的一致性;
  • 显示获取被管理对象比较安全,如类似智能指针的get成员函数功能;(知道如何定义类型转换函数么?哈哈)
  • 如智能指针,其构造函数是explicit的,所以不能出现默认转换:  如auto_ptr< aa > (new aa);  。
  • 以“独立语句”定义存储智能指针,以免因”异常“而内存泄露!如:作为入参传入智能指针。
  • 让接口容易被使用,不容易被误用。目标:如果客户使用某个接口而没有获取期望的行为,则这个代码不应该编译通过。
  • 尽量让types的行为和内置types的行为一致;
  • 设计class犹如设计type!!!有很多问题需要考虑!
  • 使用引用传递,而不是值传递:
         1、效率高;
         2、避免切割问题;就是将“子类”通过“值传递”传递给“基类”,导致被切割掉一部分。只有引用和指针才会执行virtual查找动作。
         3、内置类型,STL迭代器,函数对象往往使用值传递比较恰当。
  • 将成员变量都声明为private;为了便于变更等;protected和public都不可取!
  • 返回值有时候还是要返回“值”,“引用”解决不了问题。
  • 越多成员函数访问数据,则类成员变量的封装性就越低。
  • friend函数和成员函数操作空间一样。
  • 有时候,使用non-member,non-friend函数替换member函数。可增加封装性等。
  • 把“便利函数,类似于工具函数”放在多个头文件,但录属于同一个命名空间。利于扩展,增加封装性;
  • 如果需要为某个函数的所有参数进行类型转换,那么这个函数必须是个non-member。
  • 变量使用时再定义,定义时就赋值;
  • 尽量不用类型转换,尤其是dynamic_cast类型转换;用类型转换也用新式C++提供的类型转换;
  • 避免返回handle(指针,引用,迭代器)指向对象内部。
  • inline是个申请,可能被编译器忽略;
  • 如果是模板函数,一般在编译期完成具现化,但也有的在连接期完成具现化;
  • 而inline一般也是在编译期完成,但也有在连接期或运行期实现的;
  • 模板具现化与inline没有任何关系;
  • inline可能导致代码膨胀,cache命中率降低等后果;
  • class内部定义的friend函数也是inline的;
  • 除非模板的所有具现体都需要inline的,否则不要设置为inline模板;

  • 将文件编译的依赖降至最低:通过“声明依赖性”替换“定义依赖性”。 如不使用including头文件,因为头文件保护定义;相反,直接使用声明;
  • 程序库头文件应该“完全且仅有声明式”;这样可能需要“声明头文件”和“定义头文件”两个。

  • 首先查找local作用域;然后是子类的作用域;再然后查找父类的作用域;再然后查找父类的namespace;再然后找全局namespace;
  • “隐藏”和“覆盖”可能同时起作用,如父类中有两个重载的成员函数,这是子类中的成员函数则“隐藏”一个和“覆盖”一个。//1.使用using来解除“隐藏”。2.使用Base::func;

  • public继承 意味着 “is-a”关系。//因为“隐藏”可能导致“is-a”关系错误。所以需要使用using声明或转交函数来去除“隐藏”。
  • pure-virtual函数目的:继承接口。//只能通过 baseClass::pure-virtual-function();方式来调用它!!!
  • virtual意味“接口必须被继承”,缺省实现继承。
  • “virtual函数式动态绑定”,但其“参数是静态绑定”(C++为了运行效率)。--- 为了避免歧义,不要重新定义继承而来virtual函数的缺省参数值。否则,pbase等调用会使用base的缺省参数值,而不是derived的缺省参数值。)
  • non-virtual函数意味“接口和实现必须被继承”,不允许重新定义,如果重新定义,则出现“隐藏”,不符合设计原则。
  • 避免使用virtual函数的替代方案:1、NVI手法(模板设计模式);2、将virtual函数替换为“函数指针成员变量”;3、tr1::function???
  • 复合composition 意味着 “has-a”关系。在应用领域,意味着“has-a”关系;在实现领域,意味着implemented-in-terms-of关系。
  • private继承意味着implemented-in-terms-of关系,复合便于理解,大部分情况下用复合,而不用private继承; 
  • private继承:1、(唯一优点)能实现EBO,即空白基类最优化。
                                     2、子类访问基类的protected 成员,需要重载virtual函数时,也可以用。
  • 多重继承:1、virtual继承;但会增加大小、速度、初始化复杂度等成本。virtual base class不带任何数据,才是一个有实用价值的情况;

                                  2,、public继承某个interface class,private继承协助实现的class的两相结合。

  • 模板总是采用不同的方法使用类型参数,因此一般无法确认抛出异常的类型。所以不要让模板和异常规格同时使用。
  • 在函数调用中不允许传递一个临时对象到一个非const引用类型的参数里,但在异常中却被允许。
  • 不能重载&&、||,因为重载后函数调用法会替代短路求值法,会导致都执行。
  • operator++函数返回的是const对象,不能再调用operator++参数:能保证 i++++就会出错。
  • 虚函数不能inline。
  • 每一个重载的operator必须带有一个用户定义类型的参数。
  • C++禁止为非常量引用产生临时对象。即void f(string &str);  此时传入“abc”,则编译失败。
  • mutable声明变量,表示任何函数里面它们都能被修改,甚至在const成员函数里面。如mutable int a;
  • 不要对数组使用多态:语言规范中说通过一个基类指针来删除一个含有派生类对象的数组,结果将是不确定的。这实际意味着执行这样的代码肯定不会有什么好结果。多态和指针算法不能混合在一起来用,所以数组与多态也不能用在一起。
  • 绝大部分纯虚函数都没有实现,但纯虚析构函数是个特例。它们必须被实现,因为它们在派生类析构函数被调用时也将被调用。
  • 不要将extern 'C'看作是申明这个函数是用C语言写的,应该看作是申明在个函数应该被当作好象C写的一样而进行调用。
  • 因为需要初始化和析构静态对象,所以需要使用C++写main()函数。只要将C写的main()改名为realMain(),然后用C++版本的main()调用realMain()。
  • assert.h调试模式下,assert起作用,如果assert的表达式为false,则弹出断言窗口。出现coredump。
  • 最好杜绝对数组类型用typedefs。否则delete时容易引起混乱。
  • 赋值只在两个对象之间操作时有意义,而不是在一个对象和一块原始的比特之间。
  • 模板不能声明在函数内部。
  • 局部类不能绑定在模板的类型实参上。
  • 仿函数类和仿函数类模板不能被定义在函数内部,不管它实现起来有多方便。
  • std::ostringstream oss;  oss << "One hundred and one: " << 101;  std::string s = oss.str();

  • 尽量使用T t(u)类型,而不是T t=u类型。前者即可能调用直接初始化构造函数,也可能是copy构造函数;而后者只可能是copy构造函数;
  • 不要通过类型转换去掉常量属性,而应该使用mutable;如:const int a=10;int *pa = (int *)&a;将产生未定义的行为。因为编译器可能对a进行了优化。

  • 编译器在包含参数类型的名字空间中也会进行函数名字的匹配!!!
  • 类描述的是一组数据,以及操作这组数据的函数。所以同一个名字空间:成员函数,非成员函数都可以理解为类的一部分。但访问规则和查找规则,成员函数的优先级要高于非成员函数。
  • 默认继承是私有的;
  • namespace可以嵌套,但不能循环嵌套










原创粉丝点击