C++知识概要.docx

上传人:b****2 文档编号:2553344 上传时间:2023-05-04 格式:DOCX 页数:19 大小:171.47KB
下载 相关 举报
C++知识概要.docx_第1页
第1页 / 共19页
C++知识概要.docx_第2页
第2页 / 共19页
C++知识概要.docx_第3页
第3页 / 共19页
C++知识概要.docx_第4页
第4页 / 共19页
C++知识概要.docx_第5页
第5页 / 共19页
C++知识概要.docx_第6页
第6页 / 共19页
C++知识概要.docx_第7页
第7页 / 共19页
C++知识概要.docx_第8页
第8页 / 共19页
C++知识概要.docx_第9页
第9页 / 共19页
C++知识概要.docx_第10页
第10页 / 共19页
C++知识概要.docx_第11页
第11页 / 共19页
C++知识概要.docx_第12页
第12页 / 共19页
C++知识概要.docx_第13页
第13页 / 共19页
C++知识概要.docx_第14页
第14页 / 共19页
C++知识概要.docx_第15页
第15页 / 共19页
C++知识概要.docx_第16页
第16页 / 共19页
C++知识概要.docx_第17页
第17页 / 共19页
C++知识概要.docx_第18页
第18页 / 共19页
C++知识概要.docx_第19页
第19页 / 共19页
亲,该文档总共19页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

C++知识概要.docx

《C++知识概要.docx》由会员分享,可在线阅读,更多相关《C++知识概要.docx(19页珍藏版)》请在冰点文库上搜索。

C++知识概要.docx

C++知识概要

C++知识概要

1.static的用法和作用

∙在全局变量前加上关键字static,全局变量就定义成一个全局静态变量。

存储在静态存储区,在整个程序运行期间一直存在。

同时全局静态变量在声明他的文件之外是不可见的

∙在局部变量之前加上关键字static,局部变量就成为一个局部静态变量。

存储在静态存储区,作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域结束。

但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且该过程中值保持不变。

∙在函数返回类型前加static,函数就定义为静态函数。

函数的定义和声明在默认情况下都是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。

∙在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。

因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。

对多个对象来说,静态数据成员只存储一处,供所有对象共用

∙静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。

因此,对静态成员的引用不需要用对象名

static成员函数不能被virtual修饰,static成员不属于任何对象或实例,所以加上virtual没有任何实际意义;静态成员函数没有this指针,虚函数的实现是为每一个对象分配一个vptr指针,而vptr是通过this指针调用的,所以不能为virtual;虚函数的调用关系,this->vptr->ctable->virtualfunction。

1.静态变量初始化

静态局部变量和全局变量一样,数据都存放在全局区域,所以在主程序之前,编译器已经为其分配好了内存。

在C++中,初始化是在执行相关代码时才会进行初始化。

1.虚函数可以声明为inline吗

不可以

虚函数用于实现运行时的多态,或者称为晚绑定或动态绑定。

而内联函数用于提高效率。

内联函数的原理是,在编译期间,对调用内联函数的地方的代码替换成函数代码。

内联函数对于程序中需要频繁使用和调用的小函数非常有用。

虚函数要求在运行时进行类型确定,而内联函数要求在编译期完成相关的函数替换

1.static修饰符

static修饰成员变量,在数据段分配内存。

static修饰成员函数,在代码区分配内存。

1.一个派生类构造函数的执行顺序如下

1.虚拟基类的构造函数(多个虚拟基类则按照继承的顺序执行构造函数)

2.基类的构造函数(多个普通基类也按照继承的顺序执行构造函数)

3.类类型的成员对象的构造函数(按照初始化顺序)

4.派生类自己的构造函数

1.必须使用成员列表初始化的四种情况

∙当初始化一个引用成员时

∙当初始化一个常量成员时

∙当调用一个基类的构造函数,而它拥有一组参数时

∙当调用一个成员类的构造函数,而它拥有一组参数时

1.构造函数为什么不能为虚函数

∙虚函数对应一个指向虚函数表的指针,但是这个指向vtable的指针事实上是存储在对象的内存空间的。

问题出来了,假设构造函数是虚的,就须要通过vtable来调用,但是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?

所以构造函数不能是虚函数。

∙因为构造函数本来就是为了明确初始化对象成员才产生的,然而virtualfunction主要是为了在不完全了解细节的情况下也能正确处理对象。

另外,virtual函数是在不同类型的对象产生不同的动作,现在对象还没有产生,也就不能使用virtual函数来完成你想完成的动作

1.析构函数为什么要虚函数

C++中基类采用virtual虚析构函数是为了防止内存泄漏。

具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。

假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。

那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。

1.构造函数析构函数可以调用虚函数吗

在构造函数和析构函数中最好不要调用虚函数

构造函数或者析构函数调用虚函数并不会发挥虚函数动态绑定的特性,跟普通函数没区别

即使构造函数或者析构函数如果能成功调用虚函数,程序的运行结果也是不可控的

1.空类的大小是多少?

为什么

∙C++空类的大小不为0,不同编译器设置不一样,vs设置为1

∙C++标准指出,不允许一个对象(当然包括类对象)的大小为0,不同的对象不能具有相同的地址

∙带有虚函数的C++类大小不为1,因为每一个对象会有一个vptr指向虚函数表,具体大小根据指针大小确定

∙C++中要求对于类的每个实例都必须有独一无二的地址,那么编译器自动为空类分配一个字节大小,这样便保证了每个实例均有独一无二的内存地址

1.移动构造函数

A(A&&b){

***

}//a=std:

:

move(b)

1.移动赋值

A&operator=(A&&b){

***

return*this;

}

1.类如何实现只能静态分配和只能动态分配

前者是把new、delete运算符重载为private属性。

后者是把构造、析构函数设为protected属性,再用子类来动态创建

建立类的对象有两种方式:

1.静态建立,静态建立一个类对象,就是由编译器为对象在栈空间中分配内存;

2.动态建立,就是使用new运算符为对象在堆空间中分配内存。

这个过程分为两步,第一步执行operatornew()函数,在堆中搜索一块内存并进行分配;第二步调用类构造函数构造对象

1.什么情况会自动生成默认构造函数

带有默认构造函数的类成员对象

带有默认构造函数的基类

带有一个虚函数的类

带有一个虚基类的类

合成的默认构造函数中,只有基类子对象和成员类对象会被初始化。

所有其他的非静态数据成员都不会被初始化

1.如何消除隐式转换

C++中提供了explicit关键字,在构造函数声明的时候加上explicit关键字,能够禁止隐式转换

如果构造函数只接受一个参数,则它实际上定义了转换为此类类型的隐式转换机制。

可以通过将构造函数声明为explicit加以制止隐式类型转换,关键字explicit只对一个实参的构造函数有效,需要多个实参的构造函数不能用于执行隐式转换,所以无需将这些构造函数指定为explicit。

1.派生类指针转换为基类指针,指针值会不会变

将一个派生类的指针转换成某一个基类指针,编译器会将指针的值偏移到该基类在对象内存中的起始位置

1.C语言的编译链接过程

源代码-->预处理-->编译-->优化-->汇编-->链接-->可执行文件

∙预处理

读取c源程序,对其中的伪指令(以#开头的指令)和特殊符号进行处理。

包括宏定义替换、条件编译指令、头文件包含指令、特殊符号

∙编译

编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码

∙汇编

汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程

∙链接阶段

链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。

1.容器内部删除一个元素

1.顺序容器

erase迭代器不仅使所指向被删除的迭代器失效,而且使被删元素之后的所有迭代器失效(list除外),所以不能使用erase(it++)的方式,但是erase的返回值是下一个有效迭代器;

it=c.erase(it);

2.关联容器

erase迭代器只是被删除元素的迭代器失效,但是返回值是void,所以要采用erase(it++)的方式删除迭代器;

c.erase(it++)

1.vector越界访问下标,map越界访问下标?

vector删除元素时会不会释放空间

通过下标访问vector中的元素时不会做边界检查,即便下标越界。

也就是说,下标与first迭代器相加的结果超过了finish迭代器的位置,程序也不会报错,而是返回这个地址中存储的值。

如果想在访问vector中的元素时首先进行边界检查,可以使用vector中的at函数。

通过使用at函数不但可以通过下标访问vector中的元素,而且在at函数内部会对下标进行边界检查

map的下标运算符[]的作用是:

将key作为下标去执行查找,并返回相应的值;如果不存在这个key,就将一个具有该key和value的默认值插入这个map

erase()函数,只能删除内容,不能改变容量大小;erase成员函数,它删除了itVect迭代器指向的元素,并且返回要被删除的itVect之后的迭代器,迭代器相当于一个智能指针,之后迭代器将失效。

;clear()函数,只能清空内容,不能改变容量大小;如果要想在删除内容的同时释放内存,那么你可以选择deque容器

intmain(){

vectorvec(10,0);

intarr[10]={0,0,0,0,0,0,0,0,0,0};

cout<

cout<<*(vec.begin()+11)<

cout<

cout<

}

1.vector的增加删除都是怎么做的?

为什么是1.5倍

vector通过一个连续的数组存放元素,如果集合已满,在新增数据的时候,就要分配一块更大的内存,将原来的数据复制过来,释放之前的内存,再插入新增的元素

初始时刻vector的capacity为0,塞入第一个元素后capacity增加为1

不同的编译器实现的扩容方式不一样,VS2015中以1.5倍扩容,GCC以2倍扩容

对比可以发现采用成倍方式扩容,可以保证常数的时间复杂度,而增加指定大小的容量只能达到O(n)的时间复杂度,因此,使用成倍的方式扩容

以2倍的方式扩容,导致下一次申请的内存必然大于之前分配内存的总和,导致之前分配的内存不能再被使用,所以最好倍增长因子设置为(1,2)之间

向量容器vector的成员函数pop_back()可以删除最后一个元素

而函数erase()可以删除由一个iterator指出的元素,也可以删除一个指定范围的元素

还可以采用通用算法remove()来删除vector容器中的元素

采用remove一般情况下不会改变容器的大小,而pop_back()与erase()等成员函数会改变容器的大小,使得之后所有迭代器、引用和指针都失效

1.函数指针

函数指针指向的是特殊的数据类型,函数的类型是由其返回的数据类型和其参数列表共同决定的,而函数的名称则不是其类型的一部分

函数指针声明

int(*pf)(constint&,constint&);

上面的pf就是一个函数指针,指向所有返回类型为int,并带有两个constint&参数的函数。

应该注意的是*pf两边的括号是必须的否则就是声明了一个返回int*类型的函数

函数指针赋值

指针名=函数名;

指针名=&函数名;

1.c/c++的内存分配,详细说一下栈、堆、静态存储区

代码段

只读,可共享;代码段(codesegment/textsegment)通常是指用来存放程序执行代码的一块内存区域。

这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读,某些架构也允许代码段为可写,即允许修改程序。

在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等

数据段

储存已被初始化了的静态数据。

数据段(datasegment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。

数据段属于静态内存分配。

BSS段

未初始化的数据段。

BSS段(bsssegment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。

BSS是英文BlockStartedbySymbol的简称。

BSS段属于静态内存分配(BSS段和data段的区别是,如果一个全局变量没有被初始化(或被初始化为0),那么他就存放在bss段;如果一个全局变量被初始化为非0,那么他就被存放在data段)

堆(heap)

堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。

当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)

栈(stack)

栈又称堆栈,是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。

除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。

由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。

从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

共享内存映射区域

栈和堆之间,有一个共享内存的映射的区域。

这个就是共享内存存放的地方。

一般共享内存的默认大小是32M

综上:

栈区(stack) —由编译器自动分配释放,存放函数的参数值,局部变量的值等其操作方式类似于数据结构中的栈

堆区(heap) —一般由程序员分配释放,若程序员不释放,程序结束时可能由OS(操作系统)回收。

注意它与数据结构中的堆是两回事,分配方式倒是类似于链表

全局区(静态区)(static) —全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。

程序结束后由系统释放

文字常量区 —常量字符串就是放在这里的。

程序结束后由系统释放

程序代码区 —存放函数体的二进制代码

1.堆与栈的区别

管理方式:

对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memoryleak

空间大小:

一般来讲在32位系统下,堆内存可以达到4G的空间,但是对于栈来讲,一般都是有一定的空间大小的

碎片问题:

对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。

对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出

生长方向:

对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。

分配方式:

堆都是动态分配的,没有静态分配的堆。

栈有2种分配方式:

静态分配和动态分配。

静态分配是编译器完成的,比如局部变量的分配。

动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行释放,无需我们手工实现

分配效率:

栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:

分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高,堆则是C/C++函数库提供的

1.野指针是什么?

野指针:

指向内存被释放的内存或者没有访问权限的内存的指针。

它的成因有三个:

1.指针变量没有被初始化。

2.指针p被free或者delete之后,没有置为NULL。

3.指针操作超越了变量的作用范围 (觉得存在错误)

1.悬空指针和野指针有什么区别

野指针:

野指针指,访问一个已删除或访问受限的内存区域的指针,野指针不能判断是否为NULL来避免。

指针没有初始化,释放后没有置空,越界

悬空指针:

一个指针的指向对象已被删除,那么就成了悬空指针。

野指针是那些未初始化的指针

1.内存泄漏

内存泄漏 是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。

内存泄漏并非指内存在物理上消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制(内存泄露的排查诊断与解决)

1.new和delete的实现原理,delete是如何知道释放内存的大小的

1.new表达式调用一个名为operatornew(operatornew[])函数,分配一块足够大的、原始的、未命名的内存空间

2.编译器运行相应的构造函数以构造这些对象,并为其传入初始值

3.对象被分配了空间并构造完成,返回一个指向该对象的指针

new简单类型直接调用operatornew分配内存;而对于复杂结构,先调用operatornew分配内存,然后在分配的内存上调用构造函数;对于简单类型,new[]计算好大小后调用operatornew;对于复杂数据结构,new[]先调用operatornew[]分配内存,然后在p的前四个字节写入数组大小n,然后调用n次构造函数,针对复杂类型,new[]会额外存储数组大小

delete简单数据类型默认只是调用free函数;复杂数据类型先调用析构函数再调用operatordelete;针对简单类型,delete和delete[]等同。

假设指针p指向new[]分配的内存。

因为要4字节存储数组大小,实际分配的内存地址为[p-4],系统记录的也是这个地址。

delete[]实际释放的就是p-4指向的内存。

而delete会直接释放p指向的内存,这个内存根本没有被系统记录,所以会崩溃

需要在new[]一个对象数组时,需要保存数组的维度,C++的做法是在分配数组空间时多分配了4个字节的大小,专门保存数组的大小,在delete[]时就可以取出这个保存的数,就知道了需要调用析构函数多少次了

1.使用智能指针管理内存资源,RAII

RAII全称是“ResourceAcquisitionisInitialization”,直译过来是“资源获取即初始化”,也就是说在构造函数中申请分配资源,在析构函数中释放资源。

因为C++的语言机制保证了,当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数。

所以,在RAII的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定

智能指针(std:

:

shared_ptr和std:

:

unique_ptr)即RAII最具代表的实现,使用智能指针,可以实现自动的内存管理,再也不需要担心忘记delete造成的内存泄漏。

毫不夸张的来讲,有了智能指针,代码中几乎不需要再出现delete了

1.内存对齐

1.分配内存的顺序是按照声明的顺序。

2.每个变量相对于起始位置的偏移量必须是该变量类型大小的整数倍,不是整数倍空出内存,直到偏移量是整数倍为止

3.最后整个结构体的大小必须是里面变量类型最大值的整数倍

classA{

inta;

doubleb;

};

classB{

inta,b;

doublec;

};

classC{

inta;

doubleb;

intc;

};classD{

inta;

doubleb;

intc,d;

};

intmain(){

cout<

cout<

}//out/*

48

16162424

*/

1.为什么内存对齐

平台原因(移植原因)

∙不是所有的硬件平台都能访问任意地址上的任意数据的;

∙某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常

性能原因:

∙数据结构(尤其是栈)应该尽可能地在自然边界上对齐

∙原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问

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

#defineMAX(x,y)(x>y?

x:

y)

1.define与inline的区别

define是关键字,inline是函数

宏定义在预处理阶段进行文本替换,inline函数在编译阶段进行替换

inline函数有类型检查,相比宏定义比较安全

1.printf实现原理

在C/C++中,对函数参数的扫描是从后向前的。

C/C++的函数参数是通过压入堆栈的方式来给函数传参数的,所以最后压入的参数总是能够被函数找到,因为它就在堆栈指针的上方。

printf的第一个被找到的参数就是那个字符指针,就是被双引号括起来的那一部分,函数通过判断字符串里控制参数的个数来判断参数个数及数据类型,通过这些就可算出数据需要的堆栈指针的偏移量了。

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

∙用户告诉操作系统执行HelloWorld程序(通过键盘输入等)

∙操作系统:

找到helloworld程序的相关信息,检查其类型是否是可执行文件;并通过程序首部信息,确定代码和数据在可执行文件中的位置并计算出对应的磁盘块地址。

∙操作系统:

创建一个新进程,将HelloWorld可执行文件映射到该进程结构,表示由该进程执行helloworld程序。

∙操作系统:

为helloworld程序设置cpu上下文环境,并跳到程序开始处。

∙执行helloworld程序的第一条指令,发生缺页异常

∙操作系统:

分配一页物理内存,并将代码从磁盘读入内存,然后继续执行helloworld程序

∙helloword程序执行puts函数(系统调用),在显示器上写一字符串

∙操作系统:

找到要将字符串送往的显示设备,通常设备是由一个进程控制的,所以,操作系统将要写的字符串送给该进程

∙操作系统:

控制设备的进程告诉设备的窗口系统,它要显示该字符串,窗口系统确定这是一个合法的操作,然后将字符串转换成像素,将像素写入设备的存储映像区

∙视频硬件将像素转换成显示器可接收和一组控制数据信号

∙显示器解释信号,激发液晶屏

∙OK,我们在屏幕上看到了HelloWorld

1.模板类和模板函数的区别是什么

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

即函数模板允许隐式调用和显式调用而类模板只能显示调用。

在使用时类模板必须加,而函数模板不必

1.C++四种类型转换

∙static_cast能进行基础类型之间的转换,也是最常看到的类型转换。

它主要有如下几种用法:

1.用于类层次结构中父类和子类之间指针或引用的转换,2.进行下行转换(把父类指针或引用转换成子类指针或引用)时,由于没有动态类型检查,所以是不安全的,3.用于基本数据类型之间的转换,如把int转换成char,把int转换成enum,4.把void指针转换成目标类型的指针

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 解决方案 > 学习计划

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2