This is my study note of C++.

1.可变参数宏

__VA_ARGS__为可变参数宏,C99中加入的宏。

1
#define varPrint(fmt, ...) printf(fmt, __VA_ARGS__)

2.常见宏定义

  • __DATE__当前日期, “MMM DD YYYY” 格式
  • __TIME __ 当前时间, “HH:MM:SS” 格式
  • __FILE __ 这会包含当前文件名,一个字符串常量
  • __LINE __ 这会包含当前行号,一个十进制常量
  • __FUNCTION __ 程序预编译时预编译器将用所在的函数名,返回值是字符串
1
#define varPrint(...) printf(__VA_ARGS__, "%d -%s %s",__LINE__, __DATE__, __TIME__)

3.内存分配

  • 从静态存储区域分配
    1
    内存在程序 编译 时就已经分配好,这块内存在程序的整 个运行期间都存在 ,速度快、不容易出错 。
  • 在栈上分配
    1
    在执行函数时, 函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释放。
  • 从堆上分配
    1
    即动态内存分配。程序在运行的时候用 malloc 或 new 申请任意大小的内存,程序员自己负责在何时用 free 或delete 释放内存。
    编译时内存分为5大存储区:堆区、栈区、全局区、文字常量区、程序代码区。

4.new和delete的区别

  • malloc分配的空间称为内存在堆区,new分配的空间称为自由存储区。
  • new存在重载,malloc不存在重载。
  • new不需要指定分配空间的大小,malloc需要。
  • new返回某种类型的指针,失败时会抛出异常,malloc返回的是void指针,失败时会返回NULL,因此分配后需要判断其状态。

5.include

  • #include 使用<>时,只搜寻系统目录,不查找本地目录
  • 使用“”时,查找本地目录,找不到后才会寻找系统目录。

6.const相对于define的优点

  • const具有数据类型,define只是字符替换,因此const更安全
  • const可用于一些debug调试

7.typedef和define的区别

  • typedef用于定义一些数据类型的别名,define一般用于定义常量和经常使用的宏
  • 执行的时间不同,typedf发生在编译的过程,define发生在预编译的过程,将宏替换到对应位置,typedef会有类型检查,define没有

8.指针

  • 指针常量

    1
    修饰的是常量,指针指向不可改变,但是在指针指向的值可以改变
  • 常量指针

    1
    const修饰的是指针,指针指向可以改变,但是指针指向的值不能改变
  • 指针数组:int *a[10];

    1
    是一个数组,a[ ]里面存的是地址数组指针:int (*a)[10];
  • 数组指针:int (*a)[10];

    1
    是一个指针,指向整个数组;
  • 指针和引用的区别

    1
    2
    3
    1.指针需要分配空间,引用只是一个别名,能创建指针的指针,不能创建引用的引用,引用需要初始化而指针不需要。
    2.引用++--的含义和指针不同
    3.指针引用可以修改,但是引用无法改变引用的值
  • 为什么会产生野指针

    1
    2
    3
    1.指针创建时未初始化
    2.指针释放后没有指向NULL
    3.指针超出作用域
  • 什么是迷途指针

    1
    指针释放内存后,还是指向对应地址,这样的指针叫做迷途指针。
  • 什么是数据对齐原则

    1
    指数据所在的内存地址必须是该数据长度的整数倍。

9.智能指针

智能指针的本质就是类模板,在构造的时候赋予使用的内存空间,并在作用域中持续存在,当智能指针对象使用完后,对象就会自动调用析构函数去释放该指针所指向的空间。

  • auto_ptr智能指针

    1
    它是c++98提供的智能指针,它对拷贝构造和复制重载的实现是通过原对象拷贝给新对象,原对象就会被设置为nullptr实现,它的缺点也很明显,使用原来的对象时程序会崩溃。
  • unique_ptr智能指针

  • share_ptr智能指针

    1
    同样也是C++11提供的智能指针,它引入引用计数器的想法,它记录一个资源被几个指针共享,当指针销毁时,引用计数器减一,只有当引用计数器为0时,才可释放该空间。

    当链表中构建share_ptr智能指针时,需要采用weak_ptr而不是share_ptr,weak_ptr不会导致指针间相互指向导致计数器发生改变。

  • 定制删除器

    1
    2
    3
    4
    5
    6
    7
    如果当share_ptr的指针指向数组、文件时,我们若是采用默认的delete函数释放空间则会报错,因此我们需要定制删除器

    bool del(int *p)
    {
    delete [] p;
    }
    shared_ptr<int> shared(new int[100],del);

10.线程锁

  • lock_guard
    1
    声明一个局部的lock_guard对象,在其构造函数中进行加锁,在其析构函数中进行解锁。最终的结果就是:创建即加锁,作用域结束自动解锁。从而使用lock_guard()就可以替代lock()与unlock()。
  • unique_lock
    1
    使用lock_guard后不能手动lock()与手动unlock();使用unique_lock后可以手动lock()与手动unlock()。

11.虚函数

  • 虚函数就是在基类中被关键字 virtual 说明,并在派生类中重新定义的函数
  • 虚函数的作用允许在派生类中重载基类同名函数
  • 虚函数的定义是在基类中进行的,基类为那些定义为虚函数的声明中加上关键字virtual
  • 纯虚函数,在基类中只申明,不实现
  • 其函数的原型与基类中的函数原型(即包括函数类型、函数名、参数个数、参数类型的顺序)都必须完全相同。
  • 虚函数表
    1
    虚函数表就可以理解为一个数组,每个单元用来存放虚函数的地址。当class中有虚函数时,那么他所创建的对象将会多出一个指针,这也是为什么类中有虚函数比类中没有虚函数在进行对象所占字节的测试时,会多出4个字节,而这多出的四个字节其实就是vptr(虚指针)
  • 如何检查虚函数是否继承错误
    1
    2
    3
    4
    1.override
    明确表明这个派生类的虚函数是重写基类的,如果派生类的虚函数和基类虚函数签名不一致,则会抛出错误。
    2.final
    如果不需要某个类被继承,或是不需要某类虚函数被重写,则将其用final申明,如果子类中被重写则会抛出错误。
  • 析构和构造函数是否能做虚函数
    1
    2
    首先,析构函数需要做为虚函数。如果析构函数不是虚函数,删除基类指针时,只会执行基类的析构而不会执行子类的析构,这样就会导致子类析构不完全,造成内存泄漏,因此析构一般需要设置为虚函数。
    对于构造函数,首先认识到构造函数做为虚函数是没有任何意义的,因为设置虚函数就是为了可以通过父类指针或者引用去访问这个函数,但是构造函数在创建时自动执行,且无法访问,同时如果要访问虚函数,需要访问虚函数表,需要类完成初始化,因此必须在构造之后,所以构造函数无法定义为虚函数。

12、struct和class的区别

  • 默认继承权限
    1
    如果没有明确指明的话,class的类按照private继承处理,struct的类按照public继承处理。
  • 成员默认访问权限
    1
    class成员默认的访问权限为private,struct成员默认的访问权限为public。

13、inline和virtual的区别

1
inline是一种用于实现的关键字,而不是用于声明的关键字,virtual用于声明该函数为虚函数。

14.为什么有了malloc/free还需要new/delete

1
这是因为对于内部非数据类型的对象而言,光是malloc无法满足动态对象的需求,对象在创建的时候要自动构造,在消亡之前要进行析构,由于malloc/free是库函数不是运算符,因此不在编译器控制的范围内,无法将构造和析构强加在malloc上。

15.vector[]和vector.at()的区别

  • []不做边界检查,当越界时不会抛出异常,而是返回一个错误的引用,当使用这个引用时,则会抛出错误。
  • .at()做边界检查,当越界时会抛出异常。

16.setw(n)

1
n表示宽度,后面紧跟的输出字段长度小于n时,字段前用空格补齐。

17.64位、32位下数据类型字节的区别

类型 32位 64位
char 1 1
int 4 大多数4,少部分8
long 4 8
float 4 4
double 8 8
指针 4 8

18.指针打开文件的权限模式

操作 作用
ios::in 为输入打开文件
ios::out 为输出而打开文件
ios::ate 文件尾追加文件(会发生覆盖)
ios::app 文件尾附加文件(不会发生覆盖)
ios::trunc 如果文件已经存在则先删除文件
ios::binary 二进制方式

19.枚举常量

  • 如果给定枚举名字,未给定名字对应值,枚举默认从0开始,往后逐个加1
  • 如果给定某个枚举名字和枚举值,那么往后的枚举值都从该值开始加1

20.strlen和sizeof的区别

  • strlen长度不包括’\n’,sizeof包括
  • sizeof在编译时计算好了,strlen在运行时计算
  • sizeof传参数可以是各种类型,strlen的参数必须是字符型指针

21.memset、memcpy、strcpy的区别

  • memcpy是内存拷贝函数,可以拷贝任何的数据类型,memcpy(b,a,sizeof(b))
  • strcpy只能拷贝字符串,遇到”\0”结束拷贝
  • memset是对一段内存空间全部设置为某个字符,例如char a[100];memset(a,””,sizeof(a))

22.struct和union的区别

  • union所有成员共享一块内存,该内存大小由成员变量中长度最大的一个决定,而struct中变量都是独立的
  • union分配的内存是连续的,struct不能保证分配的内存是连续的

23.static修饰

  • 静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响。
  • static数据存储在全局数据区
  • 静态成员函数没有this指针,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,但是非静态成员函数可以访问静态成员函数和静态数据成员

24.堆栈溢出的原因

  • 局部数组过大
  • 递归层次过多
  • 数组越界
  • 对于堆的溢出则是没有及时释放资源

25.声明和定义的区别

声明只是告诉编译器,有这个变量,但没有为其分配内存,定义则是为其分配内存。

26.头文件的作用

  • 保存程序的声明
  • 通过头文件来调用函数
  • 当接口被实现或者使用时,其方式若是与头文件不同则会抛出错误,减少程序员调试的时间

27.可以定义自己的NULL嘛?例如NULL值为1、2、3

1
如果包含了相应的标准头文件引入NULL的话,那么在程序中重新定义NULL为不同的内容是非法的。

28.指针和数组初始化的区别

1
2
char a[] =“string literal”;
char *p = “string literal”;
  • 用作数组初始值时(第一种情况),它指明该数组字符的初始值,可以通过改变数组元素修改字符串。
  • 其他情况下,会转换成一个无名的静态字符数组,可能存储在只读内存中,这就导致数据只能读不能写(第二种情况),只能修改指向,不能修改内容。

29.内联函数与宏的区别

  • 内联函数在编译时展开,直接嵌入到目标代码中
  • 宏在预编译时展开,只是简单的文本替换

30.面向对象的三个基本特征,并简而言之

  • 封装——将客观事物抽象为类
  • 继承——子类继承父类的方法和属性
  • 多态——允许子类类型的指针赋值给父类指针

31.指针函数和函数指针的区别

  • 指针函数
    1
    2
    void* foo();
    // 返回值为指针的函数
  • 函数指针
    1
    2
    int (*bar)(int ,int);
    // 申明一个指向函数的指针,该函数传入两个int型参数,返回一个int型参数