C++进阶

C++进阶

指针与引用

  1. 指针是实体类型,编译器为其单独分配内存空间,而引用只是变量的别名,在语言上我们可以把它看做不是实体类型,不单独分配空间,对一个对象的引用,就是直接对这个对象的操作。

  2. sizeof的时候,指针得到的是指针本身的大小,引用得到的是变量的大小。

  3. 指针可以不初始化,可以为空,可以改变所指的对象,但引用必须初始化,且必须指向所引用的对象,不能更换目标。

  4. 函数参数选择引用型参数时,形参和实参是同一个对象,不存在对象复制,避免了开销,还可以在修改形参的同时修改实参。

  5. 如果为了避免函数对原来实参的意外修改,我们可以用const对引用加以修饰,传常应用,这样就不存在拷贝构造,可以提高C++程序的执行效率。还可以避免对象切割问题。

    对象分割分体讲的是,假设有两个类存在继承关系。
    class windowWithScrollBars :public Window {};
    void printNameAndDisplay(Window w)
    那么如果传参类型是window的传值的话,就会将子类的特性丢失。

  6. 应用,例如在重载++操作符的时候

    day &operator++(day &d)
    {
    	d = (day)(d +1);
    	return d;
    }
    

内存分配

malloc, free, new, delete的区别

  1. malloc是c++/c语言的标准库函数,而new和delete是C++的运算符

    extern void* malloc(unsigned int num_bytes);
    void free(void *FirstByte);
    
  2. malloc函数返回的是void*指针,必须检查指针是否为空,然后再强制转换成其他任何类型的指针。释放后要将指针转换成null,避免成为野指针。而new返回指定类型的指针,并且可以自动计算所需要的大小。

  3. malloc只分配内存,new不仅分配内存,还对内存中的对象进行初始化。free只释放内存,delete不仅释放内存,还会调用对象的析构函数,销毁对象。

  4. 对于非内部数据构造的对象时,只用malloc/free不能满足动态对象的要求,malloc是库函数而不是运算符,不在编译器权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free,因此C++语言需要一个能动态内存分配和初始化的运算符new,以及一个能够完成清理和释放内存工作,包含析构函数调用的操作符delete。由于内部数据类型没有构造和析构过程,对他们而言malloc/free和new/delete是没有什么差别的。

  5. malloc从堆里面获得内存,函数返回的指针指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表,当操作系统收到程序的申请时,就会遍历链表,然后寻找第一个空间大于所申请空间的堆节点,然后该节点从空闲节点链表中删除,并将该节点分配给程序。而new从自由存储区中获取内存,自有存储区是C++上的抽象概念,可以借助堆来实现。

  6. 释放new申请的数组必须加"[]"

    delete [] ptr;
    

C++内存布局

C++分为堆,栈,全局/静态存储区。字符串常量区和常变量区,和代码区。

sizeof与内存对齐

  1. 大端,小端与内存增长方式
    小端模式:低地址存低位,高地址存高位。
    大端模式:低地址存高位,高地址存低位。
    例如char temp[4] = {0, 1, 0, 0}中,如果int* ptr = (int*) temp;
    那么在小端系统中它代表的整数应当是256。
    一般在windows, linux等中都是采用小端模式,而Mac OS采用的是大端模式。

  2. sizeof
    sizeof是C/C++中提供的一个操作符,在跨平台中可以获得一些便利。比如在32位系统中的指针变量是4个字节的,在64位系统中的指针变量就是8个字节的。
    对在栈中或者静态分配的数组名使用sizeof操作符获取的是整个数组占用的字节数,对于数组指针或者动态分配的数组使用sizeof操作符都是得到指针所占用的字节数。

  3. 内存对齐
    字节对齐一般与编译器有关,但一般按照以下原则进行。

    1. 结构体每个成员相对首地址的偏移量都是成员大小的整数倍,必要的话会进行填充字节;
    2. 结构体的总大小为结构体最大成员大小的整数倍,不够的会进行填充字节。

    第一条原则呢是因为CPU处理器在提取内存的时候有一定的粒度,一般为4个字节,可以用预指令设置,如果它的首地址不是该成员大小的整数倍的话,就可能要多取一次,再进行裁剪工作,这样CPU多了很多处理工作。
    第二条原则主要是给第一条原则善后处理,因为如果没有第二条原则,只有第一条原则的话,后面再开个结构体数组,就很有可能那个结构体数组的起始位置受到影响。
    因为基础类型的数据大小无非就是1,2,4,6,16字节,若结构体的总大小是最大基础成员大小的整数倍,那么也就一定是其他任一基础成员大小的整数倍,那么每个结构体的startpos就一定是任一基础变量大小的整数倍,(其实这也是一条原则,由(1)(2)保证而来的),这样的话,两条规则结合在一起就保证了所有基础类型数据,非基础类型数据全部对齐。

    与结构体不同,对于栈的内存对齐不受结构体的限制,总是以4字节对齐为原则进行对齐。

C++初始化顺序

  1. 成员变量在使用初始化列表初始化时,与构造函数中初始化成员列表的顺序无关,只与定义成员变量的顺序有关。因为成员变量的初始化次序是根据变量在内存中次序有关,而内存中的排列顺序早在编译期就根据变量的定义次序决定了。这点在EffectiveC++中有详细介绍。
  2. 如果不使用初始化列表初始化,在构造函数内初始化时,此时与成员变量在构造函数中的位置有关。
  3. 注意:类成员在定义时,是不能初始化的
  4. 注意:类中const成员常量必须在构造函数初始化列表中初始化。
  5. 注意:类中static成员变量,必须在类外初始化。

变量初始化顺序应该是:

  1. 基类的静态变量或全局变量
  2. 派生类的静态变量或全局变量
  3. 基类的成员变量
  4. 派生类的成员变量

C++关键字

static

  1. 隐藏
    所有未加static前缀的全局变量和函数都具有全局可见性,其他的源文件也能访问。如果加了static,就会对其他源文件隐藏。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。对于函数来讲,static的作用仅限于隐藏。
  2. 保持变量内容的持久,只能初始化一次
    存储在静态数据区的变量会在程序刚开始云心的时候就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的,虽然这种用法不常见。
    如果作为static局部变量在函数定义,它的生存期为整个源程序,但是其作用于仍然与自动变量相同,只能在定义该变量的函数内使用该变量。退出该函数后,尽管该变量还继续存在,但不能使用它。
int fun(){
    static int count = 10;       //在第一次进入这个函数的时候,变量a被初始化为10!并接着自减1,以后每次进入该函数,a
    return count--;              //就不会被再次初始化了,仅进行自减1的操作;在static发明前,要达到同样的功能,则只能使用全局变量:    
    
}
  1. static变量的第三个作用是默认初始化为0,和全局变量一样。
  2. 在C++中的类成员声明static
    在类中声明static变量或者函数时,初始化时用作用域运算符来标明它所属的类。
    类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致了它仅能访问类的静态数据和静态成员函数。
    不能将静态成员函数定义为虚函数。
    static并没有增加程序的时空开销,还节省了子类的内存空间。
    初始化在类体外进行,而前面不加static,初始化时使用作用域运算符来标明它所属的类。
    <数据类型><类名>::<静态数据成员名>=<值>
    为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。

extern关键字

  1. 在C语言中,修饰符extern用在变量或者函数的声明前,用来说明“此变量/函数”是在别处定义的,要在此处引用。这样可以加快编译速度。
  2. extern “C”表明是C语言的接口函数,由于C和C++对函数命名的不一致,C++支持函数重载(会把参数放在函数名的后面),而C语言不支持,需要C++编译器编译的时候告知它,此处调用的是C中函数,需要编译器选用C语言的编译规则来编译和链接此处的函数。

const关键字

  1. 修饰变量,与define的区别
    可以定义为const常量
    便于进行类型检查, const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误。
    可以保护被修饰的东西,防止被意外修改。
    const定义的常规量在程序运行过程中只有一个拷贝,而#define定义的常量在内存中有多个拷贝
  2. 常量指针
    是指指针指向的内容是常量,可以有两种定义方式,一般写第一种。
const int * n;
int const * n;
  1. 指针常量
int *const n;

指针本身不能改变,但是地址中保存的数值是可以改变的。
指向常量的常指针

const int* const p;

在C++中,const内存效率更高,编译器通常将const变量保存在符号表中,而不会分配存储空间,这使得它成为一个编译期间的常量,没有存储和读取的操作。

inline

  1. define只是字符串替换,inline由编译器控制
  2. 内联函数在编译时展开,而宏是由预处理器对宏进行展开
  3. 内联函数会检查参数类型,而宏定义不检查参数,所以内联函数更安全。
  4. 宏不是函数,而inline函数是函数
  5. 宏定义的时候要小心处理宏参数

explicit

explicit修饰构造函数的时候,它不能用于隐式转换和复制初始化。

猜你喜欢

转载自blog.csdn.net/fairyloycine/article/details/88077154
今日推荐