转载 :c++编程基础知识点

c++编程基础知识点:

变量声明和定义区别?

  • 声明仅仅是把变量的声明的位置及类型提供给编译器,并不分配内存空间;定义要在定义的地方为其分配存储空间。
  • 相同变量可以再多处声明(外部变量extern),但只能在一处定义

“零值比较”?

  • bool类型:if(flag)
  • int类型:if(flag == 0)
  • 指针类型:if(flag == null)
  • float类型:if((flag >= -0.000001) && (flag <= 0. 000001))

strlen和sizeof区别?

  • sizeof是运算符,并不是函数,结果在编译时得到而非运行中获得;strlen是字符处理的库函数。
  • sizeof参数可以是任何数据的类型或者数据(sizeof参数不退化);strlen的参数只能是字符指针且结尾是’\0’的字符串。
  • 因为sizeof值在编译时确定,所以不能用来得到动态分配(运行时分配)存储空间的大小

同一不同对象可以互相赋值吗?

  • 可以,但含有指针成员时需要注意。对比类的对象赋值时深拷贝和浅拷贝。

结构体内存对齐问题?

  • 结构体内成员按照声明顺序存储,第一个成员地址和整个结构体地址相同。
  • 未特殊说明时,按结构体中size最大的成员对齐(若有double成员),按8字节对齐。

static作用是什么?在C和C++中有何区别?

  • static可以修饰局部变量(静态局部变量)、全局变量(静态全局变量)和函数,被修饰的变量存储位置在静态区。对于静态局部变量,相对于一般局部变量其生命周期长,直到程序运行结束而非函数调用结束,且只在第一次被调用时定义
  • 对于静态全局变量,相对于全局变量其可见范围被缩小,只能在本文件中可见;
  • 修饰函数时作用和修饰全局变量相同,都是为了限定访问域。
    C++的static除了上述两种用途,还可以修饰类成员(静态成员变量和静态成员函数),静态成员变量和静态成员函数不属于任何一个对象,是所有类实例所共有
    static的数据记忆性可以满足函数在不同调用期的通信,也可以满足同一个类的多个实例间的通信。
    未初始化时,static变量默认值为0。

struct和class的区别?

  • struct的默认访问权限是public;class的默认访问权限是private。
  • struct默认继承权限是public,class默认继承权限为private。

malloc和new的区别?

  • malloc和free是标准库函数,支持覆盖;new和delete是运算符,并且支持重载
  • malloc仅仅分配内存空间,free仅仅回收空间,不具备调用构造函数和析构函数功能,用malloc分配空间存储类的对象存在风险;
  • new和delete除了分配回收功能外,还会调用构造函数和析构函数。malloc和free返回的是void类型指针(必须进行类型转换),new和delete返回的是具体类型指针。
  • new运算符实现机制就是在底层调用malloc函数,同时也会调用构造函数;delete运算符会调用free函数以及析构函数

指针和引用区别?

  • 引用只是别名,不占用具体存储空间,只有声明没有定义;指针是具体变量,需要占用存储空间。
  • 引用在声明时必须初始化为另一变量,一旦出现必须为typename refname &varname形式;指针声明和定义可以分开,可以先只声明指针变量而不初始化,等用到时再指向具体变量。
  • 引用一旦初始化之后就不可以再改变(变量可以被引用为多次,但引用只能作为一个变量引用);指针变量可以重新指向别的变量。不存在指向空值的引用,必须有具体实体;但是存在指向空值的指针。

宏定义和函数有何区别?

  • 宏在编译时完成替换,之后被替换的文本参与编译,相当于直接插入了代码,运行时不存在函数调用,执行起来更快;函数调用在运行时需要跳转到具体调用函数。
  • 宏函数属于在结构中插入代码,没有返回值;函数调用具有返回值。
  • 宏函数参数没有类型,不进行类型检查;函数参数具有类型,需要检查类型。
  • 宏函数不要在最后加分号。

宏定义和const区别?

  • 宏替换发生在编译阶段之前,属于文本插入替换;const作用发生于编译过程中。
  • 宏不检查类型;const会检查数据类型。
  • 宏定义的数据没有分配内存空间,只是插入替换掉;const定义的变量只是值不能改变,但要分配内存空间。

宏定义和typedef区别?

  • 宏主要用于定义常量及书写复杂的内容;typedef主要用于定义类型别名。
  • 宏替换发生在编译阶段之前,属于文本插入替换;typedef是编译的一部分。
  • 宏不检查类型;typedef会检查数据类型。
  • 宏不是语句,不在在最后加分号;typedef是语句,要加分号标识结束。
  • 注意对指针的操作,typedef char * p_char和#define p_char char *区别巨大。

宏定义和内联函数(inline)区别?

  • 在使用时,宏只做简单字符串替换(编译前)。而内联函数可以进行参数类型检查(编译时),且具有返回值。
  • 内联函数本身是函数,强调函数特性,具有重载等功能。
  • 内联函数可以作为某个类的成员函数,这样可以使用类的保护成员和私有成员。而当一个表达式涉及到类保护成员或私有成员时,宏就不能实现了。

条件编译#ifdef, #else, #endif作用?

  • 可以通过加#define,并通过#ifdef来判断,将某些具体模块包括进要编译的内容。
  • 用于子程序前加#define DEBUG用于程序调试。
  • 应对硬件的设置(机器类型等)。
  • 条件编译功能if也可实现,但条件编译可以减少被编译语句,从而减少目标程序大小。

区别以下几种变量?

const int a;
int const a;
const int *a;
int *const a;
  • int const a和const int a均表示定义常量类型a。
  • const int *a,其中a为指向int型变量的指针,const在 * 左侧,表示a指向不可变常量。(看成const (*a),对引用加const)
  • int *const a,依旧是指针类型,表示a为指向整型数据的常指针。(看成const(a),对指针const)

volatile有什么作用?

  • volatile定义变量的值是易变的,每次用到这个变量的值的时候都要去重新读取这个变量的值,而不是读寄存器内的备份。
  • 多线程中被几个任务共享的变量需要定义为volatile类型。

什么是常引用?

  • 常引用可以理解为常量指针,形式为const typename & refname = varname。
  • 常引用下,原变量值不会被别名所修改。
    • 原变量的值可以通过原名修改。
  • 常引用通常用作只读变量别名或是形参传递。

区别以下指针类型?

int *p[10]
int (*p)[10]
int *p(int)
int (*p)(int)
  • int *p[10]表示指针数组,强调数组概念,是一个数组变量,数组大小为10,数组内每个元素都是指向int类型的指针变量。
  • int (*p)[10]表示数组指针,强调是指针,只有一个变量,是指针类型,不过指向的是一个int类型的数组,这个数组大小是10。
  • int *p(int)是函数声明,函数名是p,参数是int类型的,返回值是int *类型的。
  • int (*p)(int)是函数指针,强调是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。

常量指针和指针常量区别?

  • 常量指针是一个指针,读成常量的指针,指向一个只读变量。如int const *p或const int *p。
  • 指针常量是一个不能给改变指向的指针。如int *const p。

a和&a有什么区别?

假设数组int a[10];
int (p)[10] = &a;
a是数组名,是数组首元素地址,+1表示地址值加上一个int类型的大小,如果a的值是0x00000001,加1操作后变为0x00000005。
(a + 1) = a[1]。
&a是数组的指针,其类型为int (*)[10](就是前面提到的数组指针),其加1时,系统会认为是数组首地址加上整个数组的偏移(10个int型变量),值为数组a尾元素后一个元素的地址。
若(int *)p ,此时输出 *p时,其值为a[0]的值,因为被转为int *类型,解引用时按照int类型大小来读取。

数组名和指针(这里为指向数组首元素的指针)区别?

  • 二者均可通过增减偏移量来访问数组中的元素。
  • 数组名不是真正意义上的指针,可以理解为常指针,所以数组名没有自增、自减等操作。
  • 当数组名当做形参传递给调用函数后,就失去了原有特性,退化成一般指针,多了自增、自减操作,但sizeof运算符不能再得到原数组的大小了。

野指针是什么?

  • 叫空悬指针,不是指向null的指针,是指向垃圾内存的指针。
  • 产生原因及解决办法:
    指针变量未及时初始化 => 定义指针变量及时初始化,要么置空。
    指针free或delete之后没有及时置空 => 释放操作后立即置空。

堆和栈的区别?

  • 申请方式不同。
    • 栈由系统自动分配。
    • 堆由程序员手动分配。
  • 申请大小限制不同。
    • 栈顶和栈底是之前预设好的,大小固定,可以通过ulimit -a查看,由ulimit -s修改。
    • 堆向高地址扩展,是不连续的内存区域,大小可以灵活调整。
  • 申请效率不同。
    • 栈由系统分配,速度快,不会有碎片。
    • 堆由程序员分配,速度慢,且会有碎片。

delete和delete[]区别?

  • delete只会调用一次析构函数。
  • delete[]会调用数组中每个元素的析构函数。

面向对象基础

面向对象三大特性?

封装性:数据和代码捆绑在一起,避免外界干扰和不确定性访问。
继承性:让某种类型对象获得另一个类型对象的属性和方法。
多态性:同一事物表现出不同事物的能力,即向不同对象发送同一消息,不同的对象在接收时会产生不同的行为(重载实现编译时多态,虚函数实现运行时多态)。

public/protected/private的区别?

public的变量和函数在类的内部外部都可以访问。
protected的变量和函数只能在类的内部和其派生类中访问。
private修饰的元素只能在类内访问。

对象存储空间?

非静态成员的数据类型大小之和。
编译器加入的额外成员变量(如指向虚函数表的指针)。
为了边缘对齐优化加入的padding。

C++空类有哪些成员函数?

首先,空类大小为1字节。
默认函数有:

  • 构造函数
  • 析构函数
  • 拷贝构造函数
  • 赋值运算符

构造函数能否为虚函数,析构函数呢?

  • 析构函数:

    • 析构函数可以为虚函数,并且一般情况下基类析构函数要定义为虚函数。
    • 只有在基类析构函数定义为虚函数时,调用操作符delete销毁指向对象的基类指针时,才能准确调用派生类的析构函数(从该级向上按序调用虚函数),才能准确销毁数据。
    • 析构函数可以是纯虚函数,含有纯虚函数的类是抽象类,此时不能被实例化。但派生类中可以根据自身需求重新改写基类中的纯虚函数。
  • 构造函数:

    • 构造函数不能定义为虚函数。在构造函数中可以调用虚函数,不过此时调用的是正在构造的类中的虚函数,而不是子类的虚函数,因为此时子类尚未构造好。

构造函数调用顺序,析构函数呢?

  • 调用所有虚基类的构造函数,顺序为从左到右,从最深到最浅
  • 基类的构造函数:如果有多个基类,先调用纵向上最上层基类构造函数,如果横向继承了多个类,调用顺序为派生表从左到右顺序。
  • 如果该对象需要虚函数指针(vptr),则该指针会被设置从而指向对应的虚函数表(vtbl)。
  • 成员类对象的构造函数:如果类的变量中包含其他类(类的组合),需要在调用本类构造函数前先调用成员类对象的构造函数,调用顺序遵照在类中被声明的顺序。
  • 派生类的构造函数。
  • 析构函数与之相反。

拷贝构造函数中深拷贝和浅拷贝区别?

  • 深拷贝时,当被拷贝对象存在动态分配的存储空间时,需要先动态申请一块存储空间,然后逐字节拷贝内容。
  • 浅拷贝仅仅是拷贝指针字面值。
    当使用浅拷贝时,如果原来的对象调用析构函数释放掉指针所指向的数据,则会产生空悬指针。因为所指向的内存空间已经被释放了。

拷贝构造函数和赋值运算符重载的区别?

  • 拷贝构造函数是函数,赋值运算符是运算符重载。
  • 拷贝构造函数会生成新的类对象,赋值运算符不能。
  • 拷贝构造函数是直接构造一个新的类对象,所以在初始化对象前不需要检查源对象和新建对象是否相同;赋值运算符需要上述操作并提供两套不同的复制策略,另外赋值运算符中如果原来的对象有内存分配则需要先把内存释放掉。
  • 形参传递是调用拷贝构造函数(调用的被赋值对象的拷贝构造函数),但并不是所有出现"="的地方都是使用赋值运算符,如下:
  Student s;
  Student s1 = s;    // 调用拷贝构造函数
  Student s2;
  s2 = s;    // 赋值运算符操作

注:类中有指针变量时要重写析构函数、拷贝构造函数和赋值运算符

虚函数和纯虚函数区别?

  • 虚函数是为了实现动态编联产生的,目的是通过基类类型的指针指向不同对象时,自动调用相应的、和基类同名的函数(使用同一种调用形式,既能调用派生类又能调用基类的同名函数)。虚函数需要在基类中加上virtual修饰符修饰,因为virtual会被隐式继承,所以子类中相同函数都是虚函数。当一个成员函数被声明为虚函数之后,其派生类中同名函数自动成为虚函数,在派生类中重新定义此函数时要求函数名、返回值类型、参数个数和类型全部与基类函数相同。
  • 纯虚函数只是相当于一个接口名,但含有纯虚函数的类不能够实例化。

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

  • 覆盖是派生类中重新定义的函数,其函数名、参数列表(个数、类型和顺序)、返回值类型和父类完全相同,只有函数体有区别。派生类虽然继承了基类的同名函数,但用派生类对象调用该函数时会根据对象类型调用相应的函数。覆盖只能发生在类的成员函数中。
  • 隐藏是指派生类函数屏蔽了与其同名的函数,这里仅要求基类和派生类函数同名即可。其他状态同覆盖。可以说隐藏比覆盖涵盖的范围更宽泛,毕竟参数不加限定。
  • 重载是具有相同函数名但参数列表不同(个数、类型或顺序)的两个函数(不关心返回值),当调用函数时根据传递的参数列表来确定具体调用哪个函数。重载可以是同一个类的成员函数也可以是类外函数。

在main执行之前执行的代码可能是什么?

  • 全局对象的构造函数。

哪几种情况必须用到初始化成员列表?

  • 初始化一个const成员。
  • 初始化一个reference成员。
  • 调用一个基类的构造函数,而该函数有一组参数。
  • 调用一个数据成员对象的构造函数,而该函数有一组参数。

什么是虚指针?

  • 虚指针或虚函数指针是虚函数的实现细节。
  • 虚指针指向虚表结构。

重载和函数模板的区别?

  • 重载需要多个函数,这些函数彼此之间函数名相同,但参数列表中参数数量和类型不同。在区分各个重载函数时我们并不关心函数体。
  • 模板函数是一个通用函数,函数的类型和形参不直接指定而用虚拟类型来代表。但只适用于参个数相同而类型不同的函数。

this指针是什么?

  • this指针是类的指针,指向对象的首地址。
  • this指针只能在成员函数中使用,在全局函数、静态成员函数中都不能用this。
  • this指针只有在成员函数中才有定义,且存储位置会因编译器不同有不同存储位置。

类模板是什么?

  • 用于解决多个功能相同、数据类型不同的类需要重复定义的问题。
  • 在建立类时候使用template及任意类型标识符T,之后在建立类对象时,会指定实际的类型,这样才会是一个实际的对象。
  • 类模板是对一批仅数据成员类型不同的类的抽象,只要为这一批类创建一个类模板,即给出一套程序代码,就可以用来生成具体的类。

构造函数和析构函数调用时机?

  • 全局范围中的对象:构造函数在所有函数调用之前执行,在主函数执行完调用析构函数。
  • 局部自动对象:建立对象时调用构造函数,离开作用域时调用析构函数。
  • 动态分配的对象:建立对象时调用构造函数,调用释放时调用析构函数。
  • 静态局部变量对象:建立时调用一次构造函数,主函数结束时调用析构函数。

文章转载自:https://github.com/linw7/Skill-Tree/blob/master/编程语言C++.md#stl
感谢作者。

猜你喜欢

转载自blog.csdn.net/weixin_39116058/article/details/88649617