C++总结(3)

C++第三次总结,结合常见问题

sizeof和strlen区别

  • strlen计算字符串的具体长度,不包括字符串结束符,返回字符个数
  • sizeof计算什么后站的内存数(字节大小),不是实际长度
  • strlen是一个函数,sizeof是一个取字节的运算符
  • sizeof的返回值是字符个数*字符所占的字节数,字符实际长度小于定义的长度
  • sizeof可以用类型做参数,strlen只能用char*做参数,且只能用‘\0’结尾
  • 数组做sizeof的参数不退化,传递给strlen就退化成指针

简述strcpy、sprintf、memcpy区别

  • 操作对象不同

    strcpy的两个操作对象均为字符串

    sprintf的操作源对象可以是多种数据类型,目的操作对象是字符串

    memcpy的两个操作对象就是两个任意可操作的内存地址

  • 执行效率不同

    memcpy最高,strcpy次之,sprintf最差

  • 实现功能不同

    strcpy主要实现字符串变量间的拷贝

    sprintf主要实现其他数据类型到字符串的转化

    memcpy主要是内存块间的拷贝

编码实现某变量某位清零

1
#define BIT3 (0x1 << 3)
2
void set_bit3(void){
3
    a |= BIT3;
4
}
5
void set_bit3(void){
6
    a &= ~BIT3;
7
}

将引用作为函数参数有哪些特点

  • 传递引用给函数与传递指针给函数效果是一样的
  • 使用引用做参数,并没有在内存中产生实参副本,是直接对实参操作;而一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。
  • 使用指针作为函数参数虽然也能达到一样的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用*变量名的形式进行运算;另外,在主调函数处必须使用变量的地址做实参

分别写出bool,int,flost,指针类型的变量a与零的比较

1
bool: if(!a) or if(a)
2
int: if(a == 0)
3
float: const EXPRESSION EXP = 0.00001
4
if(a < EXP && a > -EXP)
5
pointer: if(a != NULL) or if(a == NULL)

局部变量全局变量的问题

  • 局部会屏蔽全局。要用全局变量,需要使用“::”,局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量
  • 如何引用一个已经定义过的全局变量,可以用引用头文件的方式,也可以用extern关键字,如果用引用头文件方式来引入某个在头文件中声明的全局变量,假定你将那个变量写错了,那么在编译期间会报错,如果你用extern方式引用,假定你犯了同样错误,会在链接期间报错
  • 全局变量定义在不同C文件中,需要以static形式来声明

数组和指针的区别

  • 数组在内存中时连续存放的,开辟一块连续的内存空间;数组作所占的空间:sizeof(数组名);数组大小:sizeof(数组名)/sizeof(元素类型)
  • 用运算符sizeof可以计算数组大小,sizeof(指针)得到的是指针变量的大小,而不是指向内存的大小
  • 向函数传递参数的时候,如果实参是一个数组,那用于接受的形参为对应的指针
  • 数组原地址固定,指针不固定

C++如何组织一个类被实例化?一般在什么时候将构造函数声明为private?

  • 将类定义为抽象基类或者将构造函数声明为private
  • 不允许类外部创建类对象,只能在类内部创建对象

如何禁止自动生成拷贝构造函数

  • 为了组织编译器默认生成拷贝构造函数和拷贝赋值函数,我们需要手动去重写这两个函数,某些情况下,为了避免调用拷贝构造函数和拷贝赋值函数,我们需要将他们设置为private,防止被调用
  • 类的成员函数和friend函数还是可以调用private函数,如果这个private函数只声明不定义,会产生一个连接错误
  • 针对上述两种情况,我们可以定义一个base类,在base类中将拷贝构造函数和拷贝赋值函数设置为private,那么派生类中编译器将不会自动生成这两个函数,且由于base类中该函数是私有的,因此,派生类将阻止编译器执行相关操作

assert与NDEBUGE

  • assert宏的原型定义在<assert.h>,其作用是如果它的条件返回错误,则终止程序执行,原型定义:

    1
    #include <assert.h>
    2
    void assert(int expression);

    assert的作用是先计算表达式expression,如果其值为假,那么它先向stderr打印一条出错信息,然后通过调用abort来终止程序运行

  • NDBEBUGE宏是Standard C中定义的宏,专门用来控制assert()的行为。如果定义了这个宏,则assert不会起作用

main函数有没有返回值

  • 程序运行过程入口点,main函数,返回值必须是int,这样返回值才能传递给程序激活者表示程序正常退出。main(int args, int char **args) 参数传递,参数处理一般会调用getopt()函数处理

写一个比较大小的模板函数

1
#include <ioatream>
2
using namespace std;
3
template<typename type1, typename type2>
4
type1 Max(type1 a, type2 b)
5
{
6
    return a > b ? a : b;
7
}

C++怎么实现一个函数先于main函数运行

  • 如果在main函数之前声明一个类的全局变量的对象,那么执行顺序先于main函数
  • 定义在main函数之前的全局对象、静态对象的构造函数在main函数之前执行
  • mian函数执行之前,主要是初始化系统相关资源
    • 设置栈指针
    • 初始化static静态和global全局变量,即data段的内容
    • 将未初始化部分的全局变量赋初值,即bss段内容
    • 全局对象初始化,在main之前调用构造函数
    • 将main函数的参数,argc,argv等传递给main函数,然后才开始执行main函数
  • main函数执行之后
    • 全局对象的析构函数会在main函数之后执行
    • 可以用_onexit注册一个函数,它会在main之后执行

虚函数和纯虚函数的区别在于

  • 纯虚函数只有定义没有实现,虚函数既有定义又有实现
  • 含有传虚函数的类不能定义对象,含有虚函数的类可以定义对象

智能指针怎么用,只能指针出现循环引用怎么解决

  • shar_ptr调用一个名为make_shared的标准库函数,每个shared_ptr都有一个关联计数器,通常称为引用计数,一旦一个shared_ptr的计数器变为零,他就会自动释放自己所管理的对象;shared_ptr的析构函数就会递减它所指向的对象的引用计数。如果引用计数变为零,shared_ptr的析构函数就会销毁对象,并释放它占用的内存
  • unique_ptr,某一时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁
  • weak_ptr,是一种不控制所指向对象生存期的智能指针,他指向一个由shared_ptr管理的对象,将一个weak_ptr绑定到一个shared_ptr不会改变引用计数,一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使有weak_ptr指向对象,对象还是会被释放
  • 弱指针用于专门解决循环引用问题

strcpy和strncpy函数的区别,那个函数更安全

  • 函数原型

    char * strcpy(char *strDest, const char * strSrc)

    char * strncpy(char *strDet, const char *strSrc, int pos)

  • strcpy函数:如果参数dest所指的内存空间不够大,可能会造成缓冲溢出的错误

    strncpy函数:用来复制源字符串的前n个字符,src和dest所指的内存区域不能重叠,且dest必须有足够长的空间放置n个字符

  • 如果目标长>指定长>源长,则源长全部拷贝到目标长,自动加上‘\0’

    如果指定长<源长,则将源长中按指定长度拷贝到目标字符串,不包括’\0’

    如果指定长>目标长,运行错误

为什么要用static_cast转换而不用c语言中的转换

  • 更加安全
  • 更清楚直接,可读性好

成员函数里memset(this, 0, sizeof(*this))会发生什么

  • 有时候类里面定义了很多int,char,struct等c语言里哪些类型的变量,该函数可以将整个对象的内存全部置为零
  • 类中含有虚函数时,会破坏虚函数表
  • 类中含有C++类型的对象时,会破坏对象的内存

回调函数的作用

  • 当发生某事件时,系统或其他函数将会自动调用你定义的一段函数
  • 回调函数就相当于一个中断处理函数,有系统在符合你设定的条件时自动调用。为此,你需要做三件事情:声明、定义、设置触发条件,就是在你的函数中吧你的回调函数名称转化为地址作为一个参数,以便系统调用
  • 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数
  • 因为可以把调用者与被调用者分开。调用者不关心谁是被调用者,所有它需要知道的,只是存在一个具有某种特定原型、某些限制条件的被调用函数

随机数的生成

  • #include <time.h> srand(unsigned)time(NULL);

为什么拷贝构造函数必须传引用不能传值

  • 拷贝构造函数的作用就是用来赋值对象的,在使用这个对象的实例来初始化这个对象的一个新实例
  • 参数传递的过程到底发生了什么
  • 将地址传递和值传递统一起来,归根结底还是传递的是值(地址也是指,只不过可以通过它找到另一个值)
    • 值传递:对于内置数据类型的传递时,直接赋值拷贝给形参;对于类类型的传递时,需要首先调用该类的拷贝构造函数来初始化形参(局部对象);如void foo(class_type obj_local){},如果调用foo(obj);首先class_type obj_local(obj),这样就定义了局部变量obj_local供函数内部使用
    • 引用传递:无论内置类型还是类类型,传递引用或指针最终都是传递地址值,而地址总是指针类型,显然参数传递时,按简单类型的赋值拷贝,而不会有拷贝构造函数的调用
  • 拷贝构造函数用值传递会产生无限递归调用,内存溢出

空类的大小是多少?为什么

  • C++空类的大小不为零,不同编译器设置不一样,vs设置为1;
  • C++标准指出,不允许一个对象的大小为0,不同对象不能具有相同地址
  • 带有虚函数的C++类大小不为1,因为每一个对象会有一个vptr指向虚函数表,具体大小根据指针大小确定
  • C++中要求对于类的每个实例都必须有杜伊五二的地址,那么编译器自动为空类分配一个字节大小,这样保证了每个实例均有独一无二的内存地址

大内存申请的时候选用哪种?C++变量存在哪?变量的大小存在哪?符号表存在哪?

  • 大内存申请时,采用堆申请空间,用new申请
  • 不同变量存在不同的地方,局部变量、全局变量、静态变量
  • C++对变量名不做存储,在汇编以后不会出现变量名,变量名作用只是用于方便编译成汇编代码,是给编译器看的,是方便人阅读的

静态函数能定义为虚函数吗?常函数?

  • static成员不属于任何类对象或类实例,所以即使给此函数加上virtual也是没有任何意义的
  • static函数中没有this指针
  • 常函数可以

那些函数不能作为虚函数

  • 普通函数、友元函数、静态函数、构造函数、拷贝构造函数

this调用成员变量时,堆栈会发生什么变化

  • 当类的非静态成员函数访问类的非静态成员时,把编译器会自动将对象的地址作为隐函数参数传递给函数,这个隐含参数就是this指针。即使你并没有写this指针,编译器在连接时也会加上this,对各成员的访问都是通过this的。this指针首先入栈,然后成员函数的参数从右向左进行入栈,最后函数返回地址入栈

静态绑定和动态绑定的介绍

  • 对象的静态类型:对象在声明时采用的类型。在编译器确定
  • 对象的动态类型:目前所指对象的类型。是在运行期决定的。对象的动态类型可以更改,静态类型无法更改
  • 静态绑定:当定的是对象的静态类型,某特性(比如函数)依赖于对象的静态类型,发生在编译器
  • 动态绑定:绑定的是对象的动态类型,某特性(比如函数)依赖于对象的动态类型,发生在运行期

设计一个类计算子类的个数

  • 为类设计一个static变量count作为计数器
  • 类定义结束后初始化count
  • 在构造函数中对count+1
  • 设计拷贝构造函数,在进行拷贝构造函数中count+1
  • 在赋值构造函数中count+1
  • 在析构函数中count-1

虚函数的代价

  • 带有虚函数的类,每一个类会产生一个虚函数表,用来存储指向虚函数成员的指针,增大类
  • 带有虚函数的类发每一个对象,都会有一个指向虚表的指针,会增加对象的空间的大小
  • 不能内联

类对象的大小

  • 类的非静态成员变量大小,静态成员不占类的空间,成员函数也不占据类的空间大小
  • 内存对齐另外分配的空间大小,类的数据也是需要进行内存对齐操作的
  • 虚函数的话,会在类对象插入vptr指针
  • 如果该类是某类的派生类,那么派生类继承基类部分数据成员也会存在派生类的空间中

移动构造函数

  • 有时我们会遇到这种情况,我们用对象a初始化对象b后对象a我们就不再使用了,但是对象a的空间还在,既然拷贝函数实际上就是把a对象的内容复制一份给b,那么为什么不能直接使用a的空间呢,因此有了移动构造函数
  • 拷贝构造函数,对于指针,我们一定要采用深层复制,而在移动构造函数中,对于指针,我们采用浅复制
  • C++引入了移动构造函数,专门处理这种,用a初始化b后就将a析构的情况
  • 与拷贝类似,移动也是使用一个对象的值设置另一个对象的值。但是又与拷贝不同的是,移动实现的是对象值真实的转移;源对象丢失其内容,被目的对象占有。移动操作的发生的时候,是当移动值的对象是未命名的对象是时候(典型的就是函数返回值或类型转换返回值)。使用临时对象的值初始化另一个对象的值,不会要求对对象的复制,因为临时对象不会有其他使用,因而,它的值可以被移动到目的对象。

何时需要合成构造函数

  • 如果一个类没有任何构造函数,但他有一个成员对象,该成员对象含有默认构造函数,那么编译器就会为该类合成一个默认构造函数。
  • 没有任何构造函数的类派生自一个带有默认构造函数的基类,那么需要为该类合成一个构造函数
  • 带有虚函数的类
  • 带有一个虚基类的类

何时需要合成复制构造函数

  • 对一个对象做显示的初始化操作
  • 对象被当作参数交给某函数时
  • 函数传回一个类对象时
  • 如果一个类没有拷贝构造函数,但是含有一个类类型的成员变量,该类型有拷贝构造函数
  • 如果一个类没有拷贝构造函数,但是该类继承自含有拷贝构造函数的基类
  • 如果一个类没有拷贝构造函数,但是该类声明或继承了虚函数
  • 如果一个类没有拷贝构造函数,但是该类含有虚基类

何时需要成员初始化列表

  • 初始化一个引用成员变量时
  • 初始化一个const成员变量时
  • 调用一个基类构造函数,而构造函数拥有一组参数时
  • 调用一个成员类的构造函数,而构造函数有一组参数
  • 编译器会一一操作初始化列表,以适当的顺序在构造函数之内安插初始化操作,并且在任何显示用户代码前

程序员定义的析构函数被扩展的过程

  • 析构函数函数体被执行
  • 如果class 拥有成员类对象,而后者拥有析构函数,那么它们会以声明顺序的相反顺序被调用
  • 如果对象有一个vptr,限制被重新定义
  • 如果有任何直接的上一层非虚基类拥有析构函数,则它们会有声明顺序被调用
  • 如果任何虚基类拥有析构函数

构造函数的执行算法

  • 虚基类及基类构造函数
  • vptr初始化
  • 成员初始化列表
  • 程序员提供的代码

迭代器++it,it++哪个好,为什么

  • 前置返回一个引用,后置返回一个对象

    ++i实现代码为:

    1
    iknt & operator++(){
    2
        *this += 1;
    3
        return *this;
    4
    }
  • 前置不会产生临时对象,后置必须产生临时对象

    i++实现代码为:

    1
    int operator++(int){
    2
        int temp = *this;
    3
        ++*this;
    4
        return temp;
    5
    }

C++如何处理多个异常

  • C++中的异常情况

    语法错误、运行错误

  • C++异常处理机制

    异常处理基本思想:执行一个函数过程中发生异常,可以不用在本函数立即进行处理,而是抛出异常,让函数的调用者直接或间接处理这个问题

    C++异常处理机制由3个模块组成:try、throw、catch

    抛出异常的语句个数为throw表达式;如果try快中程序段发生了异常则抛出异常

    1
    try{
    2
    3
    }
    4
    catch(){
    5
    6
    }
    7
    catch(){
    8
    9
    }

模板和实现可不可以不写在一个文件里面?为什么

  • 因为在编译时模板并不能生成真正的二进制代码,而是在编译调用模板类或函数的cpp文件时才会去找对应的模板声明和实现,在这种情况下编译器是不知道实现模板类或函数的cpp文件的存在,所以他只能找到模板类或函数的声明而找不到实现,而只好创建一个符号寄希望于连接程序找地址。但模板类或函数的实现并不能被编译成二进制代码,结果连接程序找不到地址只好报错
  • 模板定义很特殊,由template<…>处理的任何东西都意味着编译器在当时不为他分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一种机制能去掉制定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义

在成员函数中调用delete this会出现什么问题?对象还可以使用吗?

  • 在类对象的内存空间中,只有数据成员和虚函数表指针,并不包含代码内容,类的成员函数单独放在代码段中。在调用成员函数时,隐含传递一个this指针,让成员函数知道当前哪个对象在调用它。当调用delete this时,类对象的内存空间被释放。在delete this之后进行的其他函数的调用只要不涉及this指针的内容,都可以正常执行。一旦涉及this指针,如操作数据成员,调用虚函数等,就会出现不可预期的问题

  • delete this之后释放了类对象的内存空间,那么这段内存已经还给了系统,不再属于这个进程。照这个逻辑看应该发生指针错误,无访问权限之类的问题,但实际不是这样,内存空间并不是马上被挥手给系统,可能是缓冲或者其他什么原因,导致这段内存空间暂时没有被系统收回。此时这段内存是可以访问的,你可以操作他,但其中的值确实不确定的

  • 如果在类的析构函数中调用delete this,会发生什么?

    会导致堆栈溢出,因为delete里调用析构函数并释放内存,会形成无限递归

auto_ptr作用

  • auto_ptr的出现,主要是为了解决“有异常抛出时发生内存泄露”的问题;抛出异常,将异常导致指针p所指向的空间得不到释放而导致内存泄漏
  • auto_ptr构造时取得某个对象的控制权,在析构时释放该对象。我们实际上是创建一个auto_ptr类型的局部对象,该局部对象析构时,会将自身所拥有的指针空间释放,所以不会有内存泄漏
  • auto_ptr的构造函数是explicit,阻止了一般指针隐式转换为auto_ptr的构造,所以不能直接将一般类型的指针赋值给auto_ptr类型的对象,必须用auto_ptr的构造函数创建对象
  • 由于auto_ptr对象析构时会删除它所拥有的指针,作用使用时避免多个auto_ptr对象管理同一个指针
  • auto_ptr内部实现,析构函数中删除对象用的时delete而不是delete[],所以auto不能管理数组
  • auto_ptr支持所拥有的指针类型之间的隐式类型转换

class、union、struct的区别

  • c语言中struct只是一个聚合数据类型,没有权限设置,无法添加成员函数,无法实现面向对象编程,且如果没有 typedef结构名,声明结构变量必须添加关键字struct
  • C++中struct功能大大扩展,可以有权限设置,可以有成员函数,继承,可以实现面向对象编程,允许声明时省略关键字strut
  • union:一种数据格式,能够存储不同数据类型,但只能同时存储其中的一种。C++中union时一种特殊的类,可以有访问权限,成员变量,成员函数,不能包含虚函数和静态数据,不能继承和派生

动态联编和静态联编

  • 在C++中,联编是指一个计算机程序的不同部分彼此关联的过程,按照联编所进行的阶段不同,可以分为静态联编和动态联编
  • 静态联编是指联编工作在编译阶段完成,这种联编过程是在程序运行之前完成的,又称为早期联编。要实现静态联编,在编译阶段就必须确认程序中的操作调用与执行该操作代码间的关系,确定这种关系成为束定,在编译时的束定成为静态束定。静态联编对函数的选择基于指向对象或者引用的类型。其优点是效率高,但灵活性差
  • 动态联编是指联编在程序运行时动态的进行,根据当时的情况来确定调用哪个同名函数,实际上时在运行时虚函数的实现。这种联编又称为晚期联编,或动态束定。动态联编对成员函数的选择时经济与对象的类型,针对不同的对象类型做出不同的编译结果

动态编译与静态编译

  • 静态编译,编译器在编译可执行文件时,需要用到的对应动态链接库中的部分提取出来,连接到可执行文件中去,使可执行文件在运行时不需要依赖于动态链接库
  • 动态编译的可执行文件需要附带一个动态链接库,在执行时,需要调用其对应动态连接库的命令。所以其优点一方面时缩小了执行文件文件本身的体积,另一方面加快了编译速度,节省了系统资源。缺点是哪怕很简单的程序,只用到连接库的一两条命令,也需要附带一个相对庞大的连接库;二是如果其他计算机上没有安装对应的运行库,则动态编译的可执行文件不能运行

动态链接和静态链接区别

  • 静态链接库就是把(lib)文件中用到的函数到目直接连接进目标程序,程序运行期间不需要其他库文件;动态链接时把调用的函数所在文件模块(dll)和调用函数在文件中的位置等信息连接进目标程序,程序运行的时候再从dll中寻找相应函数代码,因此需要相应dll文件支持
  • 静态连接库与动态连接库都是共享代码的方式,如果采用静态连接库,无论你愿不愿意,lib中的指令都全部被直接包含在最终exe文件中,但若是使用dll,改dll不必被包含着最终exe文件中,exe文件执行时可以“动态”的引用和卸载这个与exe独立的dll文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他静态库或动态链接库,动态链接库可以
  • 动态链接库就是在需要调用其中函数的时候,根据函数映射表找到该函数然后调入堆栈执行。如果当前工程中有多处对dll文件中同意函数的调用,那么执行时,这个函数只会留下一份拷贝。但lib会留下多份拷贝

vloatile关键字的作用

  • 用它声明的类型变量表示可以被某些编译器未知因素修改

空类会默认添加哪些东西

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

new、delete、operator new、operator delete、placement new、placementdelete

  • new 申请内存和初始化对象
  • operator new 只申请内存
  • placement new用于在给定内存中初始化对象

宏定义一个取两个数较大值的功能

  • #define MAX(x,y) ((x>y)?x:y)

define、const、typedef、inline使用方法

  • const、define区别
    • const定义的常量是变量带类型,define只是常熟不带类型
    • define在预处理阶段起作用,const在编译连接过程起作用
    • define没有类型检查、const有
    • define占代码段,const占数据段
    • const不能重定义,define可以取消,然后重定义
    • define独特功能,可以防止文件重复引用
  • define和别名typedef的区别
    • 执行时间不同,typedef在编译阶段,有类型检查,define在预处理阶段,没有类型检查
    • 功能差异,typedef结合struct使用,define功能更多
    • define没有作用域限制,typedef有自己的作用域
  • inline和define区别
    • define是预处理阶段,inline是编译阶段
    • inline函数有类型检查,更安全

printf实现原理

  • 在C++中函数参数的扫描时从后往前的,通过压入堆栈的方式来给函数传参数,数据有两块,一块是堆,一块是栈,栈是从内存高地址向内存低地址生长的,控制生长的就是堆栈指针,最先压入的参数在最上面,所以最后压入的参数总能被函数找到。printf的第一个被找到的参数就是那个字符指针,就是被双引号括起来那一部分,函数通过判断字符串里控制参数的个数来判断参数个数及数据类型,通过这些就可以算出数据需要的堆栈指针的偏移量

include的顺序及尖括号和双引号的区别

  • 表示编译器只在系统默认目录或尖括号内的工作目录下搜索
  • 表示现在用户工作目录下找头文件,找不到去系统默认目录下找

helloworld程序开始到打印到屏幕上的全过程

  • 开始
  • 操作系统找到程序相关信息,检查是否是可执行文件,并通过程序首部信息,确定代码和数据在可执行文件中的位置并计算出对应的磁盘块地址
  • 创建新进程,将程序可执行文件映射到该进程结构中
  • 为程序设置cpu上下文环境,跳转到程序开始处
  • 执行命令,发生缺页中断
  • 分配一页物理内存,将代码从磁盘读入内存,继续执行程序
  • 程序执行puts函数,在显示器上写一字符串
  • 找到显示设备,将字符串送给管理设备的进程
  • 设备的窗口系统确认是合法操作,将字符串转换成像素,写入存储映像区
  • 解释信号

模板类和模板函数区别

  • 函数模板的实例化时由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显示地指定。

为什么模板类一般都是放在一个h文件中

  • 模板定义很特殊。由template<…>处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。
  • 在分离式编译的环境下,编译器编译某一个.cpp文件时并不知道另一个.cpp文件的存在,也不会去查找(当遇到未决符号时它会寄希望于连接器)。这种模式在没有模板的情况下运行良好,但遇到模板时就傻眼了,因为模板仅在需要的时候才会实例化出来,所以,当编译器只看到模板的声明时,它不能实例化该模板,只能创建一个具有外部连接的符号并期待连接器能够将符号的地址决议出来。然而当实现该模板的.cpp文件中没有用到模板的实例时,编译器懒得去实例化,所以,整个工程的.obj中就找不到一行模板实例的二进制代码,于是连接器也黔驴技穷了。

C++中访问权限和继承权限

  • public、private、protected

cout和printf区别

  • cout<<是一个函数,有缓冲输出
  • printf没有缓冲输出,立即输出

重载运算符

  • 我们只能重载已有的运算符,而无权发明新的运算符;对于一个重载的运算符,其优先级和结合律与内置类型一致才可以;不能改变运算符操作数个数;

  • :: ?: sizeof typeid **不能重载;

  • 两种重载方式,成员运算符和非成员运算符,成员运算符比非成员运算符少一个参数;下标运算符、箭头运算符必须是成员运算符;

  • 引入运算符重载,是为了实现类的多态性;

  • 当重载的运算符是成员函数时,this绑定到左侧运算符对象。成员运算符函数的参数数量比运算符对象的数量少一个;至少含有一个类类型的参数;

  • 从参数的个数推断到底定义的是哪种运算符,当运算符既是一元运算符又是二元运算符(+,-,*,&);

  • 下标运算符必须是成员函数,下标运算符通常以所访问元素的引用作为返回值,同时最好定义下标运算符的常量版本和非常量版本;

  • 箭头运算符必须是类的成员,解引用通常也是类的成员;重载的箭头运算符必须返回类的指针;

重载函数匹配原则

  • 名字查找
  • 确定候选
  • 寻找最佳

定义和声明的区别

  • 如果是指变量的什么和定义,声明只是告诉编译器某个类型的变量会被使用,定义是指分配了内存
  • 函数的声明和定义,声明不需要实现,定义需要实现

C++类型转换

  • static_cast 能进行基础类型之间的转换,也是最长看到的类型转换。它主要有如下几种用法:
    • 用于类层次结构中父类和子类之间指针或引用的转换。进行上行转换是安全的
    • 进行下行转换时,没有动态类型检查,是不安全的
    • 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum,安全性需要开发人员保证
    • 把void指针转成目标指针(不安全)
    • 把其他指针转成void指针
  • const_cast去除指向常熟对象的指针或引用的常量性
  • reinterpret_cast可以把指针转换成一个证书,或把证书转换成一个指针
  • dynamic_cast主要用在继承体系中的安全向下转型,会用运行时信息(RTTI)来进行类型安全性检查,必须包含虚函数(通过使用vtable中的信息来判断实际类型)

全局变量和static变量区别

  • 全局变量(外部变量)的说明之前再冠以static就构成了静态的全局变量。全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。

  • 这两者在存储方式上并无不同。这两者的区别在于非静态全局变量的作用域是整个源程序,当一个源程序由多个原文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其他源文件中引起错误。static全局变量与普通的全局变量的区别是static全局变量只初始化一次,防止在其他文件单元被引用。

  • static函数与普通函数有什么区别?
    static函数与普通的函数作用域不同。尽在本文件中。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件。

  • static函数与普通函数最主要区别是static函数在内存中只有一份,普通静态函数在每个被调用中维持一份拷贝程序的局部变量存在于(堆栈)中,全局变量存在于(静态区)中,动态申请数据存在于(堆)

静态成员与普通成员的区别

  • 生命周期

    静态成员变量从类被加载开始到类被卸载,一直存在;

    普通成员变量只有在类创建对象后才开始存在,对象结束,它的生命期结束;

  • 共享方式

    静态成员变量是全类共享;普通成员变量是每个对象单独享用的;

  • 定义位置

    普通成员变量存储在栈或堆中,而静态成员变量存储在静态全局区;

  • 初始化位置

    普通成员变量在类中初始化;静态成员变量在类外初始化;

  • 默认实参

    可以使用静态成员变量作为默认实参

ifdef、endif

  • 满足条件时编译

隐式转换

  • C++的基本类型中并非完全的对立,部分数据类型之间是可以进行隐式转换的。所谓隐式转换,是指不需要用户干预,编译器私下进行的类型转换行为。很多时候用户可能都不知道进行了哪些转换
  • C++面向对象的多态特性,就是通过父类的类型实现对子类的封装。通过隐式转换,你可以直接将一个子类的对象使用父类的类型进行返回。在比如,数值和布尔类型的转换,整数和浮点数的转换等。某些方面来说,隐式转换给C++程序开发者带来了不小的便捷。C++是一门强类型语言,类型的检查是非常严格的。
  • 基本数据类型 基本数据类型的转换以取值范围的作为转换基础(保证精度不丢失)。隐式转换发生在从小->大的转换中。比如从char转换为int。从int->long。自定义对象 子类对象可以隐式的转换为父类对象。
  • C++中提供了explicit关键字,在构造函数声明的时候加上explicit关键字,能够禁止隐式转换。
  • 如果构造函数只接受一个参数,则它实际上定义了转换为此类类型的隐式转换机制。可以通过将构造函数声明为explicit加以制止隐式类型转换,关键字explicit只对一个实参的构造函数有效,需要多个实参的构造函数不能用于执行隐式转换,所以无需将这些构造函数指定为explicit。
  • © 2020 QSH
  • Powered by Hexo Theme Ayer
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信