目录
-
IEEE754的表示方法
浮点数存储格式:浮点数存储格式为IEEE754标准,三种精度的浮点数各个部分位数如下:
举例: 以32位单精度float为例,float i = - 21.375; i在计算机存储分为三个部分,符号位,阶码E和尾码M。阶码E用偏移值表示,尾码M用原码表示,如图:
S为符号位,1代表负数,0代表整数,E代表偏移127的幂,M代表尾数。 由于i<0,所以S = 1, 对于E ,i 的二进制表示为10101.011,然后二进制右移4位,让最左边保留一位1,即10101.011 = 1.0101011*2^4,那么E = 127+4 = 131=10000011, 对于M,就是小数点后的的数0101011,由于要保证位23位,所以在末尾补0,即M=0101011000000000000000 所以i的存储为S+ E+M= 1-10000011-01010110000000000000000.
float数存储是以IEEE754(符号位+阶码+尾数)的方式
int数是以补码的形式存在
-
C和C++的区别(补充的)
一、返回值
- C中:如果函数未指定返回值类型,则默认为int
- C++中:如果一个函数没有返回值,返回值类型必须指定为void
二、参数列表
- C中:如果函数没有指定参数列表,则默认可以接受任意多个参数
- C++中:有严格的类型检测,没有参数列表的函数默认为void,不接受任意参数
三、缺省参数(即给参数一个默认值)
- C:不支持
- C++:支持(如果没有指定实参则使用缺省值,有则使用指定实参)
- 默认实参必须在参数列表的结尾
- 默认参数只能出现在函数声明或者定义二选一中 3.缺省值必须是常量或全局变量 4.缺省参数必须是值传递或者常参传递
四、函数重载
- C:不支持
- C++:支持在同一作用域中存在几个功能类似的同名函数,但参数列表(参数个数、类型、顺序)不同
五、引用和指针
引用:可以看做是一个变量的别名
特点:
- 必须初始化
- 一个变量可以有多个引用
- 引用一旦初始化,就不能在成为其他变量的引用
ps:数组不能被引用
引用与指针的异同:
- 同:底层实现相同
- 异:
- 引用必须初始化
- 引用一旦绑定就不能更改
- ++的结果不同
- 有多级指针,没有多级引用
-
C++ STL 的基本容器
c++中有两种类型的容器:顺序容器和关联容器。
顺序容器主要有:vector、list、deque,string等。
其中vector表示一段连续的内存地址,基于数组的实现,list表示非连续的内存,基于链表实现。deque与vector类似,但是对于首元素提供删除和插入的双向支持。vector的存在可以使开发者不必关心内存的申请和释放。但是,vector的一个缺点就是它的内存分配是按照2的倍数分配内存的。 堆栈是一个线性表,插入删除操作都在一端进行,deque是先进先出的,操作原理和stack是一样的
关联容器主要有:map, set, multiset, mulmap, hash_set, hash_map,hash_multiset, hash_multimap 等。
map是key-value形式的,set是单值。map和set只能存放唯一的key值,multimap和multiset可以存放多个相同的key值。
其他的杂项(不属于容器): stack, queue, valarray, bitset
以下是基本容器的简单介绍。
- vector 底层数据结构为数组 ,支持快速随机访问
- list 底层数据结构为双向链表,支持快速增删
- deque 底层数据结构为一个中央控制器和多个缓冲区,详细见STL源码剖析P146,支持首尾(中间不能)快速增删,也支持随机访问
- stack 底层一般用23实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时
- queue 底层一般用23实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时
- 45是适配器,而不叫容器,因为是对容器的再封装
- priority_queue 的底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现
- set 底层数据结构为红黑树,有序,不重复
- multiset 底层数据结构为红黑树,有序,可重复
- map 底层数据结构为红黑树,有序,不重复
- multimap 底层数据结构为红黑树,有序,可重复
- hash_set 底层数据结构为hash表,无序,不重复
- hash_multiset 底层数据结构为hash表,无序,可重复
- hash_map 底层数据结构为hash表,无序,不重复
- hash_multimap 底层数据结构为hash表,无序,可重复
-
内存的几个区域
栈:就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。
堆:就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
自由存储区:就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。
全局存储区(静态存储区):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后有系统释放。
常量存储区:这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。
-
C++的友元函数
- 友元函数是可以直接访问类的私有成员的非成员函数
- 在C++中友元函数是独立于当前类的外部函数,它是定义在类外的普通函数,它不属于任何类,但需要在类的定义中加以声明,声明时只需在友元的名称前加上关键字friend
- 一个友元函数可以同时定义为两个类的友元函数
- 友元函数既可以在类的内部,也可以在类的外部定义
- 在外部定义友元函数时,不必加关键字friend
友元写法举例:
#include <iostream>
using namespace std;
class Radius
{
friend class Circle; //声明Circle为Radius的友元类
friend void Show_r(Radius &n); //声明Show_r为友元函数
public:
Radius(int x)
{
r = x;
}
~Radius()
{
}
private:
int r;
};
void Show_r(Radius &n)
{
cout<<"圆的半径为: "<<n.r<<endl; //调用Radius对象的私有成员变量r
}
class Circle
{
public:
Circle() {}
~Circle(){}
double area(Radius a)
{
s = a.r * a.r * 3.1415926; //调用Radius对象的私有成员变量r
return s;
}
private:
double s;
};
int main(int argc, char *argv[])
{
Radius objRadius(9);
Circle objCircle;
Show_r( objRadius );
cout<<"面积为:"<<objCircle.area(objRadius)<<endl;
return 0;
}
-
string和const char*(char[])的区别
最大区别:string中不是依靠'\0'来判断结束的。
-
C+语言的位域
有些信息在存储时,并不需要占用一个完整的字节,而只需占用几个或一个二进制位。
例如在存放一个开关量时,只有0和1两种状态,用一位二进制即可。
为了节省存储空间,并使处理简单,C语言提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进制位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。
例如:
struct s
{
int x: 3;
int y: 4;
int z: 5;
double a;
}
sizeof(s) //根据内存对齐和位域,可知为16。x,y,z的总共占一个int字节
-
C++中定义和声明的基本区别
声明:就是指给除了当前变量或者函数,或者类什么的名字,不给其中的内容,就是先告诉你有这样一个什么类型的变量或者函数,但是这个变量或者函数的具体信息却是不知道的。就好比跟你介绍一个人的时候,声明就是只告诉你这个人叫什么,但是缺不给你说这个人到底怎么样,他有哪些优点,缺点,喜好问题是什么的。
定义:就不一样了,定义直接告诉你了所有的东西,这个变量是什么,这个函数是什么功能,这个类里面包含了什么东西。很具体的说明。
对于变量来说:
定义:可以为变量分配存储空间,并且可以给变量一个初始值
声明:告诉编译器这个变量的名字和类型(extern int a;(在没有赋值的情况下,变量前加上关键字extern一定为声明))
对于函数来说:
定义:就是这个函数具体的实现
声明:告诉编译器在这个程序中会有这么一个函数
简单来说,如果函数带有{},则其为定义;否则,就为声明。
-
register修饰符
作用:register修饰符暗示编译程序相应的变量将被频繁地使用,如果可能的话,应将其保存在CPU的寄存器中,以加快其存储速度。例如下面的内存块拷贝代码,
/* Procedure for the assignment of structures, */
/* if the C compiler doesn't support this feature */
#ifdef NOSTRUCTASSIGN
memcpy (d, s, l)
{
register char *d;
register char *s;
register int i;
while (i--)
*d++ = *s++;
}
#endif
限制:
- register变量必须是能被CPU所接受的类型。 这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不过,有些机器的寄存器也能存放浮点数。
- 因为register变量可能不存放在内存中,所以不能用“&”来获取register变量的地址。
- 只有局部自动变量和形式参数可以作为寄存器变量,其它(如全局变量)不行。
- 在调用一个函数时占用一些寄存器以存放寄存器变量的值,函数调用结束后释放寄存器。此后,在调用另外一个函数时又可以利用这些寄存器来存放该函数的寄存器变量。
- 局部静态变量不能定义为寄存器变量。不能写成:register static int a, b, c;
- 由于寄存器的数量有限(不同的cpu寄存器数目不一),不能定义任意多个寄存器变量,而且某些寄存器只能接受特定类型的数据(如指针和浮点数),因此真正起作用的register修饰符的数目和类型都依赖于运行程序的机器,而任何多余的register修饰符都将被编译程序所忽略。
注意:
早期的C编译程序不会把变量保存在寄存器中,除非你命令它这样做,这时register修饰符是C语言的一种很有价值的补充。然而,随着编译程序设计技术的进步,在决定哪些变量应该被存到寄存器中时,现在的C编译环境能比程序员做出更好的决定。实际上,许多编译程序都会忽略register修饰符,因为尽管它完全合法,但它仅仅是暗示而不是命令。
-
构造函数和析构函数是否可以是虚函数
构造函数不可以是虚函数
原因:
- 从存储空间角度:虚函数对应一个vtable(虚函数表),可是这个vtable对应的虚指针其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过虚指针指向的 虚函数表来调用,可是对象还没有实例化,也就是内存空间还没有,无法找到虚函数指针,也就找不到虚函数表,所以构造函数不能是虚函数。
- 从使用角度:虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。
- 从实现上看,vbtl在构造函数调用后才建立,因而构造函数不可能成为虚函数
- 构造函数不需要是虚函数,也不允许是虚函数,因为创建一个对象时我们总是要明确指定对象的类型,尽管我们可能通过实验室的基类的指针或引用去访问它。但析构却不一定,我们往往通过基类的指针来销毁对象。这时候如果析构函数不是虚函数,就不能正确识别对象类型从而不能正确调用析构函数。
析构函数可以是虚函数,而且有时是必须声明为虚函数。
原因:在类的继承中,如果有基类指针指向派生类,那么用基类指针delete时,如果不定义成虚函数,派生类中派生的那部分无法析构。