c++细节知识点整理

目录前端

一,友元ios

二,尽量使用常量程序员

(1),常量变量好处编程

(2),如何区别常量变量函数

(3),C ++中const的成员函数及变量布局

三,程序的二义性spa

解决方法:指针

四,划分全局名字空间code

五,纯虚函数与虚函数对象

虚函数 

纯虚函数

虚拟在函数中的使用限制

 

 

 

一,友元

  •        友元函数是指某些虽然不是类成员函数却可以访问类的全部成员的函数。类授予它的友元特别的访问权,这样该友元函数就能访问到类中的全部成员。

  •        友元类的全部成员函数都是另外一个类的友元函数,均可以访问另外一个类中的隐藏信息(包括私有成员和保护成员)。当但愿一个类能够存取另外一个类的私有成员时,能够将该类声明为另外一类的友元类。

  •        友元能够直接访问本类的私有成员,提升编程的灵活性和程序执行效率。

  1. 须要访问非公有成员的非成员函数只能是类的友元函数使用友元类时注意:
  •             (1)友元关系不能被继承。 
  •             (2)友元关系是单向的,不具备交换性。若类乙是类阿的友元,类阿不必定是类乙的友元,要看在类中是否有相应的声明。 
  •             (3)友元关系不具备传递性。若类乙是类阿的友元,类Ç是乙的友元,类Ç不必定是类阿的友元,一样要看类中是否有相应的申明。
  •           (4)当用到友元成员函数时,需注意友元声明和友元定义之间的相互依赖,在该例子中,类乙必须先定义,不然类甲就不能将一个乙的函数指定为友元。然而,只有在定义了类甲以后,才能定义类乙的该成员函数。更通常的讲,必须先定义包含成员函数的类,才能将成员函数设为友元。另外一方面,没必要预先声明类和非成员函数来将它们设为友元。 

二,尽量使用常量

(1),常量变量好处

  • 使用const的好处在于它容许指定一种语意上的约束 - 某种对象不能被修改 - 编译器具体来实施这种约束。

  • 经过常量,你能够通知编译器和其余程序员某个值要保持不变。

  • 借助编译器的帮助确保这种约束不被破坏。

(2),如何区别常量变量

//const,或两者同时指定为 const,还有,二者都不指定为 const:
char *p = "Hello";              // 非 const 指针,非 const 数据
const char *p  = "Hello";       // 非 const 指针,const 数据
char * const p= "Hello";        // const 指针,非 const 数据
const char * const p = "Hello"; // const 指针,const 数据

 常量语法并不是看起来那么变化无穷通常来讲,你能够在头脑里画一条垂直线穿过指针声明中的星号(*)位置,若是const的出如今线的左边,指针指向的数据为常量;若是常量出如今线的右边,指针自己为常量;若是const的在线的两边都出现,两者都是常量  。

(3),C ++中const的成员函数及变量

  • const的成员函数的目的固然是为了指明哪一个成员函数能够在const的对象上被调用。
  • 在C ++中,只有被声明为const的成员函数才能被一个const类对象调用,const对象只能访问const成员函数或者成员变量。
  • 若将成员成员函数声明为常量,则该函数不容许修改类的数据成员。
  • 常量的成员函数能够访问非对象的常量很是量的数据成员,常量的数据成员,也能够访问常量的对象内的全部数据成员。
  • 很是量的成员函数能够访问很是量的对象的很是量的数据成员,常量数据成员,但不能够访问常量对象的任意数据成员。
  • const的成员函数只是用于非静态成员函数,不能用于静态成员函数。

三,程序的二义性

C ++中存在着重载与继承,因此会存在多个函数名相同而参数列表不一样的函数,当编译器不知道调用哪个函数时,就会产生所谓的二义性。

  • 多继承(见条款43)充满了潜在二义性的可能。最常发生的一种状况是当一个派生类从多个基类继承了相同的成员名时。
class Base1 {
public:int doIt();
};
class Base2 {
public:
void doIt();
};
// Derived 没有声明
class Derived: public Base1,public Base2 {
// 一个叫作 doIt 的函数
...
};
Derived d;
d.doIt();
// 错误! — — 二义

解决方法:

  • 使用预解析符调用函数
    • d.Base1 :: doIt方法(); //正确,调用Base1 :: doIt
    • d.Base2 :: doIt方法(); //正确,调用Base2 :: doIt
  • 使用虚基类,虚继承

四,划分全局名字空间

            将定义的名字都放在这个单一的空间中,从而不可避免地致使名字冲突

namespace sdm {
const double BOOK_VERSION = 2.0;
class Handle { ... };
Handle& getHandle();
}
/*
用户因而能够经过三种方法来访问这一名字空间里的符号:
一、将名字空间中的全部符号所有引入到某一用户空间;
二、将部分符号引入到某一用户空间;
三、或经过修饰符显式地一次性使用某个符号:
*/

5、纯虚函数与虚函数

虚函数 

  • C++的虚函数主要做用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现。
  • 子类能够重写父类的虚函数实现子类的特殊化。
  • 当虚函数初始化为0时即为纯虚函数。
  • 虚函数必须是基类的非静态成员函数,其访问权限能够是protected或public
  • virtual 函数返回值类型 虚函数名 (形参表)
    {
        函数体
    }

     

纯虚函数

  • C++中包含纯虚函数的类被称为是“抽象类”。抽象类不能使用new出对象,只有实现了这个纯虚函数的子类才能new出对象。
  • C++中的纯虚函数更像是“只提供申明,没有实现”,是对子类的约束,是“接口继承”。
  • C++中的纯虚函数也是一种“运行时多态”。
  • class <类名>
    {
        virtual <类型><函数名>(形参表) = 0;
        ...
    }

     

virtual在函数中的使用限制

  • 普通函数不能是虚函数,也就是说这个函数必须是某一个类的成员函数,不能够是一个全局函数,不然会致使编译错误。
  • 静态成员函数不能是虚函数 static成员函数是和类同生共处的,他不属于任何对象,使用virtual也将致使错误。
  • 内联函数不能是虚函数若是修饰内联函数若是内联函数被虚修,计算机会忽略内联使它变成存在的虚函数。
  • 构造函数不能是虚函数,不然会出现编译错误。
  •  virtual - >分构函数实现删除父类指针调用子类的分构函数,不然可能会出现内存泄漏。

6、对象模型

在此模型下,nonstatic 数据成员被置于每个类对象中,而static数据成员被置于类对象以外。static与nonstatic函数也都放在类对象以外,而对于virtual 函数,则经过虚函数表+虚指针来支持

  1. 每一个类生成一个表格,称为虚表(virtual table,简称vtbl)。虚表中存放着一堆指针,这些指针指向该类每个虚函数。虚表中的函数地址将按声明时的顺序排列。
  2. 每一个类对象都拥有一个虚表指针(vptr),由编译器为其生成。虚表指针的设定与重置皆由类的复制控制(也便是构造函数、析构函数、赋值操做符)来完成。vptr的位置为编译器决定,传统上它被放在全部显示声明的成员以后,不过如今许多编译器把vptr放在一个类对象的最前端(也就是说对象的地址就是vptr的地址)。
  3. 虚函数表的前面设置了一个指向type_info的指针,用以支持RTTI(Run Time Type Identification,运行时类型识别)。RTTI是为多态而生成的信息,包括对象继承关系,对象自己的描述等,只有具备虚函数的对象在会生成。
对象内存布局​​

单继承(父类含虚函数)

 原则:对普通单继承而言

  1. 子类与父类拥有各自的一个虚函数表
  2. 若子类并没有overwrite父类虚函数,用父类虚函数
  3. 若子类重写(overwrite)了父类的虚函数,则子类虚函数将覆盖虚表中对应的父类虚函数
  4. 若子声明了本身新的虚函数,则该虚函数地址将扩充到虚函数表最后
#include <iostream>
using namespace std;
class Base
{
public:
    virtual void fun1(){ cout << "Base fun1" << endl; }
    virtual void fun2(){ cout << "Base fun2" << endl; }
private:
    int a;
};
class Derive :  public Base
{
public:
    void fun2(){ cout << "Derive fun2" << endl; }
    virtual void fun3(){}
private:
    int b;
};
int main()
{
    Base b;
    Derive d;
    Base *p = &d;
    p->fun1();
    p->fun2();
    system("pause");
    return 0;
}

通常多继承

这里讲的是不考虑菱形继承的多继承,由于菱形继承须要用到虚继承,放到以后考虑

原则:

  1. 若子类新增虚函数,放在声明的第一个父类的虚函数表中
  2. 若子类重写了父类的虚函数,全部父类的虚函数表都要改变:如fun1
  3. 内存布局中,父类按照其声明顺序排列