答:程序=数据结构+算法html
算法的5个基本特征:肯定性、有穷性、输入、输出、可行性。java
肯定性:算法的每一步骤必须有确切的定义;ios
有穷性:算法的有穷性是指算法必须能在执行有限个步骤以后终止;c++
输入:一个算法有0个或多个输入,以刻画运算对象的初始状况,所谓0个输入是指算法自己定出了初始条件;程序员
输出:一个算法有一个或多个输出,以反映对输入数据加工后的结果。没有输出的算法是毫无心义的;正则表达式
可行性:算法中执行的任何计算步骤都是能够被分解为基本的可执行的操做步,即每一个计算步均可以在有限时间内完成;算法
面向对象的5大原则:单一职责原则(SRP)、开放封闭原则(OCP) 、里氏替换原则(LSP)、依赖倒置原则(DIP) 、接口隔离原则(ISP);编程
答:C++ 是类型不安全的,C#和java是类型安全的。windows
对于C++类型不安全举个例子:C++中能够直接将本应返回bool型的函数返回int,而后由编译器本身将int转化为bool型(非零转化为true,零转化数组
false)。注意:类型安全就是指两个类型直接要相互转换,必需要显示的转换,不能隐式的只用一个等于号就转换了。
补充:①string及STL模板库是类型安全的;②MFC中CString是类型安全的类,其中全部类型转换必须显示转换;
答:以下:
①inline:定义内联函数,该关键字是基于定义,若是只在函数声明时给出inline,则函数不会被认为是内联函数,因此必须在函数定义的地方也加上inline,同时inline只是向编译器建议函数之内联函数处理,不是强制的;
②const:定义常成员,包括const数据成员和const成员函数,const数据成员必须,也只能经过构造函数的初始化列表进行初始化,const成员函数只能访问类的成员,不能进行修改,若是须要修改,则引入下面的mutable关键字;
③mutable:这个关键字的引入是解决const成员函数要修改为员变量,一般而言,const成员函数只能访问成员变量,不能修改,可是若是成员变量被mutable修饰了,则在const成员函数中能够修改该变量。mutable和const不能同时用于修饰成员变量;
④ static:声明静态成员,包括静态数据成员和静态成员函数,它们被类的全部对象共享,静态数据成员在使用前必须初始化,而静态成员函数只能访问静态数据成员,不能访问非静态数据成员,由于该函数不含有this指针;
static成员函数不能够访问非静态成员的详细解释:
普通的非静态成员函数访问非静态成员变量是由于类实例化生成为对象后,对象的非静态成员函数都拥有一个this指针,而实际上非静态成员函数对成员变量的访问都是经过这个this指针实现的(this就是对象指针)。而非静态成员函数并不包含this指针,因此只能经过类名形式如A::n访问成员变量,而支持该访问方式的只有静态成员变量。
⑤virtual:声明虚函数,用于实现多态,该关键字是基于声明的;
⑥friend:声明友元函数和友元类,该关键字也是基于声明的;
⑦volatile:被该关键字修饰的变量是指其值可能在编译器认识的范围外被修改,所以编译器不要对该变量进行的操做进行优化。能够与const同时修饰一个变量。
答:①编辑:也就是编写C/C++程序。
②预处理:至关于根据预处理指令组装新的C/C++程序。通过预处理,会产生一个没有宏定义,没有条件编译指令,没有特殊符号的输出文件,这个文件的含义同本来的文件无异,只是内容上有所不一样。
预处理注意事项:
③编译:将预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后,产生相应的汇编代码文件。
④连接:经过连接器将一个个目标文件(或许还会有库文件)连接在一块儿生成一个完整的可执行程序。连接是将各个编译单元中的变量和函数引用与定义进行绑定,保证程序中的变量和函数都有对应的实体,若被调用函数未定义,就在此过程当中会发现。
答:使用#include” “表示引用用户库文件,在当前目录下查找,若没有就到标准库查找;
使用#include< >表示引用标准库文件,直接到到标准库查找;
因此,若引用标准库文件如stdio.h,用< >会比用" "查找快一些。
答:主要做用是防止重复引用,好比一个头文件定义以下:
#ifndef _HEAD_H_
#define _HEAD_H_
//主体代码
#endif
假如该头文件第一次被引用,_HEAD_H_没有被定义,因此就执行宏定义,直到#endif;
该头文件第二次被引用的时候,_HEAD_H_已经被定义,下面的语句就不会执行。
答:动态连接库:windows中是.dll,Linux中通常是 libxxx.a;
静态连接库:windows中是.lib,Linux中通常是 libxxx.so;
(1)静态连接与动态连接区别:
①静态函数库在连接时将整个函数库整合到中应用程序中并生成,因此程序生成文件较大;动态库相反,连接时不是全部数据都加载应用程序文件(只是加载导入库文件),因此生成文件较小。
②静态连接生成后的执行程序不须要外部的函数库支持,能够保持独立且可移植性好;动态库相反,须要库函数继续存在,可移植性很差。
③若是静态函数库改变了,那么你的程序必须从新编译连接;动态库相反,升级方便。
④静态库函数在程序连接阶段和应用程序一块儿完成连接生成;动态库函数要到程序运行时才连接生成。
⑤静态连接库不能够实现多程序共享,由于每一个须要的程序都要将其编译生成到本身的目标文件中;而动态连接库则能够一个库文件被多个程序共享。
(2)动态连接状况下程序如何得到动态库函数?
动态连接时虽然不加载库函数自己,可是会加载一个导入库(lib包含目标库和导入库),导入库里面保存了动态库函数的函数名、参数等信息,程序运行时操做系统就经过这些信息找到函数库自己并加载。
(3)动态连接分为显式加载与隐式加载:
显式加载:在程序刚运行时就加载dll;
隐式加载:在程序运行须要库函数时再加载dll;
(4)
答:注意:如下所区分的32位系统和64位系统都是针对Linux而言的:
通常注意32位系统中,short为2字节,int是4字节,float为4字节,long为4字节,double是8字节,指针占用4字节等就能够,64位除了指针占用8字节和long占用8字节,其余与32位相同。但注意,16位机器与32位有较大区别,如Int占用2字节,指针占用2字节等。
答:①能够指定类型,且有类型检查功能;②const量规定做用域规则,如在函数中定义常量,从而将其限制在函数内起做用;③能够将const用于更复杂的类型,好比数组和结构。
答:(1)早在C++98标准中就存在了auto关键字,那时的auto用于声明变量为自动变量,自动变量意为拥有自动的生命期,这是多余的,由于就算不使用auto声明,变量依旧拥有自动的生命期:
(2)C++11中的自动变量不在是上述无关紧要的做用,变为能够在声明变量的时候根据变量初始值的类型自动推导此变量匹配的类型,相似的关键字还有decltype。举个例子:
这种用法就相似于C#或java中的var关键字。auto的自动类型推断发生在编译期,因此使用auto并不会形成程序运行时效率的下降。
(3)auto的做用:用于代替冗长复杂、变量使用范围专注的变量声明。
(4)auto的用法:
①代替比较长的类型名:
原始代码:
使用auto后的用法:
for循环中的i将在编译时自动推导其类型,而不用咱们显式去定义那长长的一串。
②在定义模板函数时,用于声明依赖模板参数的变量类型
若不使用auto变量来声明v,那这个函数就难定义啦,不到编译的时候,谁知道x*y的真正类型是什么。
答:在运算式中有多种数据类型,在没有强制类型转换状况下就须要编译器按照默认的自动转换,以下图:
其中,横向的箭头表示,在运算以前必须转换的;竖向的箭头表示运算过程当中默认转换的顺序,也就是说,float类型的数据在运算以前,都转换为double类型的数据进行运算,同理,short和char类型的数据在运算以前,都是转换为int类型的数据进行运算。
这里补充一个知识点:浮点型数据在C++中不说明,默认是double。
例如,0.5默认就是double,因此fun(float c)调用时直接fun(0.5)就是不对的,会报参数不匹配错误。正确应该为:fun(0.5f)。
答:正数存储原码,负数存储为补码。
数据输出结果根据输出类型决定;
短类型数据转长类型数据,用短类型数据的符号位填充新增长的空白高位,例如:1000 0000(char)->1111 1111 1000 0000(short);
答:(1)位操做以下图:
(2)逻辑运算符
A && B:A不成立,B就不判断了;
A || B:A成立,B就不判断了;
答:首先,double变量因为精度截尾问题,是没法用==直接做比较的。因此通常就认定小于小数点后多少位开始是0。好比:
if( abs(f) <= 1e-15 )就是判断f是否为0的;
对于比较两个双精度a和b是否相等,相应的应该是:abs(a-b)<=1e-6;
对于float型数据比较,与double相似;
答:(1)这里介绍下浮点型数据float和双精度double在32位机的存储方式(并非直接将十进制转换为普通二进制就能够存储了),不管是float仍是double,在内存中的存储主要分红三部分,分别是:
①符号位(Sign):0表明正数,1表明负数
②指数位(Exponent):用于存储科学计数法中的指数部分,而且采用移位存储方式
③尾数位(Mantissa):用于存储尾数部分
指数位用于表示该类型取值范围;尾数位用于反映有效数字。
(2)具体存储模式:
类型 符号位 阶码 尾数 长度
float 1 8 23 32
double 1 11 52 64
临时数 1 15 64 80
float的指数部分有8bit,因为指数也要分正负数(这个正负数不是float型数据的正负,而只是存储指数的正负)例如2^-2,其中-2就是负指数,因此获得对应的指数范围-128~128。 float的尾数位是23bit,对应7位十进制数;
double的指数部分有11bit,因为指数分正负,因此获得对应的指数范围-1024~1024,52个尾数位,对应十进制为15位;
(3)取值范围看指数,有效数字个数看尾数。
float取值范围:2^-128~2^128;有效数据位数:因为2^23=8388608为7位,因此理论上是7为有效数据,但实际编译器是8位(缘由见下面);
double取值范围:2^-1024~2^1024;有效数据位数:因为2^52=4503599627370496为15位,因此有效数据位是15为有效数据;
(4)说了半天还只说了尾数能够反映有效数据位数,但并没说尾数是什么?下面解释:
例如9.125的表示成二进制就是:1001.001,进一步将其表示成二进制的科学计数方式为:1.001001*2^3 ;
实际上,在计算机中任何一个数均可以表示成1.xxxxxx*2^n 这样的形式。其中xxxxx就表示尾数部分,n表示指数部分。因此尾数部分就是待存储二进制数据的有效数据;
其中,由于最高位橙色的1这里,因为任何的一个数表示成这种形式时这里都是1,因此在存储时实际上并不保存这一位,这使得实际尾数位比存储的多一位,即float的23bit的尾数能够表示24bit(2^24十进制结果是8位,解释了3)的疑问),double中52bit的尾数能够表达53bit(2^53十进制依然是15位)。
(5)举个例子:
如下数字在表示为double(8字节的双精度浮点数)时存在舍入偏差的有()。
A、2的平方根
B、10的30次方
C、0.1
D、0.5
E、100
答案:ABC
解析:A:2开平方根后结果的十进制都是无穷数,因此二进制形式必定是无穷的,那么根据二进制有效数据最多53个(或十进制15个),必定有舍弃部分;
B:2^90=(2^3)^30<10^30<2^100,那么10的30次方变二进制至少有90个有效数据位数,假定就是90个。而后将该十进制转化为二进制数(方法见下面(2)),10^30=5^30*2^30,故最后一个有效二进制数1在由低到高第31位处,而有效位数前面获得为90,因此有效位数为90-30=60>53,意味着要舍弃。
C:0.1变二进制形式是无穷位0.001100110011……,因此有舍弃;
D:二进制为0.1,无舍弃;
E:100二进制1100100,符合要求无舍弃;
答:1)有符号数的取值范围都是负数部分绝对值比整数部分大1;,以char为例-128~127;
2)有符号数溢出后都是循环到数据范围的另外一端,例如char型127+1结果就是-128,127+2结果是-127,……还有-128-1结果是127,-128-2结果是126,……以此类推;
下面分别解释上述1)、2):
①因为有符号数最高位为符号位,那么实际数据位就是n-1位。例如char型只有7个数据位,能够表示-127~127,但这样问题来了,数据位0分别和符号位组成了-0和+0,那么设计者就用+0表示0,-0表示-128,这就是-128的由来,也就是说用-128代替-0。
那为何-128能够用-0表示呢?多是由于-128二进制为1 1000 0000,但char型只有8位,因此要截断最高位,这样就和-0(1000 0000)同样了;
-128 二进制补码也为1 1000 0000,和原码相同,计算机中负数都是用补码存储的,所以-128在计算机存储中也应该是截断后的1000 0000,那么也能够看做-0的补码也不变化。
②这就能够解释为何上述2)中char型127+1=-128和-128-1=127了?
127+1=
01111 1111
+0000 0001
=1000 0000
该值没有数据位溢出不用舍弃,结果恰好是-0,也就是-128的二进制值。
计算机二进制运算中没有减法或负数运算,只有加法运算,全部的负数或减法(减号当作负号,与减数组合成一个负数)都是先转化为相应补码,再进行二进制加法运算,那么-128-1就应该是(-128)补+(-1)补的结果:
-128-1=
1 1000 0000 (-128的补码与原码同)
+ 1111 1111
=11 0111 1111
高位溢出舍弃,用二进制表示结果为:0111 1111,即为127。
答: 可以下记忆:
记住一个最高的:构造类型的元素或成员以及小括号;
记住一个最低的:逗号运算符;
剩余的是单目、双目、三目和赋值运算符(赋值运算符包括=、+=、-=、*=、/=、%=、……)。
注意如有逗号表达式的是:a=(表达式1,表达式2,……,表达式n)
结果为:a=表达式n,即逗号表达式的结果是最后一个表达式的值。但若是不加括号就是第一个表达式的值,由于赋值运算符优先级比逗号运算符高。
特别提出:指针运算符混合运算的优先级:因为单目运算符*和其余的一些单目运算符优先级同级,所以运算时根据从右至左结合方式。以下:
①一元运算符 * 和前置 ++ 具备相等的优先级别,可是在运算时它是从右向左顺序进行的,即在 *++p中,++ 应用于p而不是应用于*p,实现的是指针自增1,而非数据自增1;后置自增++的优先级实际上比*高,但使用右结合方式获得的结果与之一致,故为了统一表述,均可以直接使用右结合方式。如*p++,使用优先级是先++后*,右结合也是先++后*;
②指针运算符* 与取地址运算符&的优先级相同,按自右向左的方向结合;
答:以自增运算为例,由于前缀运算符进行运算时是将值加1,而后返回结果便可;但后缀版本首先复制一个副本做为返回值,而后才是原值加1。所以,前缀版本的效率比后缀版本高。
答:(1)常见的(type)类型转换是C语言中的类型转换方式,其有不少缺陷如:容易产生两个不和互相转换的类型被转换,从而引起错误等等。C++为了克服C中的缺陷,引入了四种更加安全的cast类型转换模式,以下:
①static_cast:最经常使用的类型转换符,正常情况下的类型转换,如把int转换为float,如:int i;float f;f=(float) i;或者用C++类型转换模式:f=static_cast<float>(i);
②const_cast:用于取出const属性,把const类型的指针变为非const类型的指针,如:const int *fun(int x,int y){}; int *ptr=const_cast<int *>(fun(2,3));
③dynamic_cast:一般在它被用于安全地沿着类的继承关系向下进行类型转换,可是被转换类必须是多态的,即必须含有虚函数。例如:dynamic_cast<T*> (new C);其中类C必须含有虚函数,不然转换就是错误的;
④reinterpret_cast::interpret是解释的意思,reinterpret即为从新解释,能够把一个指针转换成一个整数,也能够把一个整数转换成一个指针。 如:int i; char *ptr="hello freind!"; i=reinterpret_cast<int>(ptr);
注:C++计算中的隐式类型转换是static_cast,转换中若表达式包含signed和unsigned int,signed会被转换为unsigned。例如:
int i = -1;
unsigned j = 1;
if(j > i)与if(i < j)
答案:上述两个判断都是false。
(2)这里补充“无符号数和有符号之间的转化关系”:
① 无符号数转换为有符号数:
看无符号数的最高位是否为1,若是不为1(即为0),则有符号数就直接等于无符号数;
若是无符号数的最高位为1,则将无符号数取补码,获得的数就是有符号数。
②有符号数转换为无符号数:
看有符号数的最高位是否为1,若是不为1(即为0),则无符号数就直接等于有符号数;
若是有符号数的最高位为1,则将有符号数取补码,获得的数就是无符号数。
具体解释可参见:http://blog.csdn.net/starryheavens/article/details/4617637
答:类型别名与宏的三点区别,如typedef char *String_t; 和#define String_d char * 两句:
(1)前者是类型别名,要作类型检查,后者只是一个替换,不作类型检查;
(2)前者编译时处理,后者预编译时处理,即预编译期间替换掉宏;
(3)前者能保证定义的全都是char* 类型,String_d却不能,如String_t a,b;String_d x,y;其中a、b、x为char*类型,而y倒是char类型,这点要注意(由于简单替换就是:char *x,y,y前面没有指针符就不是指针)。
答:宏定义是在编译器预处理阶段中就进行替换了,替换成什么只与define和undefine文本中的定义位置有关系,与它们在哪一个函数中无关。例如:
输出结果是:50 10 10
为何?由于a从新定义只在print()前,估值对它有效。
答:C++有两种字符串表示模式:C风格的char []和string模式;其中C风格的char c[]初始化要注意的原则:char c[]数组末尾是‘\0’,没有就不是字符串,如char str[4]={'1','2','s','r'}就不是字符串。
数组初始化注意事项:
(1)使用{ }初始化只有定义是能够,之后就不能用了,如int c[4]={2,3,4,5}能够,但int d[4];d[4]={1,2,3,4}就错误;
(2)不能够将一个数组赋给另外一个数组,如c=d是错误的,由于数组名是常量;
(3)C++11中用{ }初始化数组,能够省略=,如int c[4] {2,3,4,5}合法;
(4)使用{ }初始化禁止缩窄转换,如int p[3]={1,2,3.0}非法,由于浮点型的小数点后面数据被舍弃了,就是缩窄了;
下面介绍下C++的int型数组初始化:
二维数组初始化分为多种形式。注意,当只对部分元素赋初值时,未赋初值的元素自动取0值。例如:
①按行赋值
int a[ ][3]={{1,2,3},{4,5,6}};——至关于{{1,2,3},{4,5,6}}
int a[ ][3]={{1,2},{0}};——至关于{{1,2,0},{0,0,0}}
②连续赋值
int a[ ][3]={1,2,3,4,5,6};——至关于{{1,2,3},{4,5,6}}
int a[ ][3]={2};——至关于{{2,0,0}};
答:多维数组定义中只有最靠近数组名的那个维数能够省略,其他不容许省略不然没法肯定数组。例如:a[m][n]仅仅能够省略m,n必须定义。
答:前者表示建立一个指针变量,其指向一个存储数字12的地址,后者表示建立一个长度为12的数组。
答:柔性数组在C++中只用在结构体中,且只能定义在结构体的末尾,以下:
struct Node
{
int size;
char data[0];
};
注:上述也能够char data[];
此时data只是表示一个数组符号,不占内存空间,就是一个偏移地址(必定注意,此时数组名data不是一个指针常量,是不占内存的)。
做用:长度为0的数组的主要用途是为了知足须要变长度的结构体,实现结构灵活使用,方便管理内存缓冲区,减小内存碎片化,通常都在结构体末尾。
在网络通讯中的实际用途:因为考虑到数据的溢出,数据包中的data数组长度通常会设置得足够长足以容纳最大的数据,所以packet中的data数组不少状况下都没有填满数据,所以形成了浪费,而若是咱们用变长柔性数组来进行封包的话,就不会浪费空间浪费网络流量。因此,柔性数组在通讯中主要做用是,保证了维护数据包空间的连续性。
答:1)数组名是数组首地址,是一个常量,不能够看成指针变量用,如:若str为数组名,str++就不合法,至关于常量自增。
再次注意:数组名是常量!常量!常量!常量就不可被赋值,如有char s[10];char *pt,则以下:
s="hello";//将常量赋给s,实质就是将常量首地址赋值给s;
s=pt;
都是错误的,s是数组名不可被赋值,任何形式的赋值都不能够。
2)同时,一维数组名当被直接使用时,是一个指向数组首地址的指针。
3)若是数组A是多维数组,那么数组名A是数组地址(是一个多级指针),而不是第一行地址(是一级指针),虽然它们的值都是首地址值。也就是说:以二维数组A[][]为例,A和A[0]是不一样的,虽然地址值都是数组首元素地址,可是一个是二级指针,一个是一级指针。因此,只有*(A+i)与A[i]是等效的。
对于二维数组a[i][j],此时*(a+i)与a[i]是一个意思,当直接用a[i]时表明的是该数组第i行地址,因此多维数组a[][]的*(a[i]+j)或者*(*(a+i)+j)是与a[i][j]等效。
4)还有,数组名表示首地址,那么数组名前有取地址符是什么意思?例如:数组a[],a表示数组首元素地址,&a表示数组总体地址,&a+1就是该数组末尾后一个地址。
5)printf打印数组名:若char a[5]="abcd",printf("%d \n",a)则输出是数组首地址;若要输出字符须要printf("%C \n",a[i])一个个输出,如printf("%C \n",a[0])输出第一个元素。固然,printf("%s \n",a)是能够将数组做为字符串一块儿输出的。
答:分两种状况:
(1)直接计算:
结果:6
(2) 经过形参传入:
结果:4;
分析:(1)中是直接将数组名做为sizeof的操做数,因此就是计算数组自己空间大小(注意,不是数组长度),包括"\0",应该为6*1=6(单个char字符占一个字节),比较容易;(2)中的特色是数组做为函数的参数传递时,就是计算指针大小了。char str_arg[100]实际为 char *str_arg,传递的是指向数组首元素的指针,那么sizeof(char *str_arg)=4(32位Linux系统)。
补充注意:1)sizeof(malloc)等于4,由于malloc()返回的是一个指针。sizeof只对数组名的指向内容做内存计数运算;2)如有printf("%%%%\n"),只输出%%。由于每一个输出前都有一个%表示输出。
答:(1)sizeof 操做符不能返回动态分派的数组或外部的数组尺寸(只用于返回静态数组的尺寸,且通常都在编译时就会运算结果)。具体以下:
sizeof 返回的值表示的含义以下(单位字节):
数组 —— 编译时分配的数组空间大小(不是数组长度);
指针 —— 存储该指针所用的空间大小(存储该指针的地址的长度,是长整型,应该为 4 );
类型 —— 该类型所占的空间大小;
对象 —— 对象的实际占用空间大小;
函数 —— 函数的返回类型所占的空间大小。函数的返回类型不能是 void 。
(2)sizeof 返回所有数组的尺寸;strlen时字符串长度计算函数,返回字符串实际长度,遇到'/0'则结束。遇到如:char str[20]="0123456789"; int a=strlen(str); //a=10; >>> strlen 计算字符串的长度,以结束符 0x00 为字符串结束。 int b=sizeof(str); //而b=20; >>> sizeof 计算的则是分配的数组 str[20] 所占的内存空间的大小,不受里面存储的内容改变。 上面是对静态数组处理的结果。
还有如:charc1[]={'a','b','\0','d','e'};strlen(c1)=2;由于strlen计算字符串的长度,会把c1当作字符串,遇到'/0'结束。
注:sizeof对于字符串会包括'\0',strlen不会包括'\0'。
(3)对于指针,sizeof会将其视为类型:
char str[] ="aBcDe";
char *str1 ="aBcDe";
cout << "str[]字符长度为: "<<sizeof(str)<<endl;//结果为6
cout<<"*str1字符长度为: "<<sizeof(str1)<<endl; //结果为4 系统32位
cout << "str字符长度为: " << sizeof(str) / sizeof(str[0]) << endl;//结果为6
cout << "str字符长度为: " << strlen(str)<< endl;//结果为5
(4)sizeof是运算符,strlen是函数;
答:(1)字符串变量定义:
1)用字符数组定义:char str[10];//静态定义,这种方式定义最广泛,适用于做为cin,scanf的输入接口;
2)用字符指针定义字符数组:char* str;//注意这种只是定义了一个指针,并未分配内存区域,没法直接向其写入数据,因此不适用于cin和scanf输入;
3)用字符指针定义字符数组并开辟内存区域:char *str=new char[10];//动态定义,这种是能够做为cin、scanf输入使用的,由于开辟了内存区域;
4)使用容器string也能够定义字符串,可是注意string字符串的长度获取通常使用size(),而char []主要是用<string.h>(属于C的库文件)中的strlen()函数。string str可直接用于cin或者scanf输入。
(2)字符串数组定义:
1)用char数组定义:char s[10][100];//静态定义
2)字符指针数组定义:char *s[10];//注意这种只是定义了指针变量,并未分配内存空间,没法向其直接写入数据,不可用于cin或scanf。使用前必须先赋值或分配内存。
3)用字符指针定义字符串数组并开辟内存区域:char *str[10]=new char[][100]; //动态定义,可用于cin或scanf输入;
4)使用string容器定义字符串数组:string str[10]; //静态定义,可用于cin或者scanf输入;
5)使用string容器定义字符串指针数组:string* str; //未分配内存,不可用于cin或者scanf输入;
6)使用string容器定义字符串指针数组并分配内存区:string* str=new string[10]; //动态定义,可用于cin或者scanf输入;
答:C++11中的原始字符串用R表示,定义为:R “xxx(raw string)xxx” ;
其中,原始字符串必须用括号()括起来,括号的先后能够加其余字符串,所加的字符串会被忽略,而且加的字符串必须在括号两边同时出现。
原始字符串能够直接把双引号"或者转义字符\n当作普通字符串输出,而标准的字符串则会将其当作特定功能符号如\n换行。
答:长度为0 的串为空串,即为“” 。由多个空格字符构成的字符串称为空格串。
补充:空串是任何字符串的子串;
答:字符串char [](注意不是string,string是能够直接==比较的)比较须要使用函数strcm(str1,str2)函数,而不能直接使用“字符串1==字符串2”、“字符串1<字符串2”或者符串1>字符串2”;如有以下状况:
比较符号两边分别是一个地址或指针与字符串,那么比较的是字符串地址是否与被比较地址之间的关系,如:
char *p="hello";
return p=="hello";
返回值是1;
补充:若是是char[]类型的字符串,须要使用strcmp()函数;如果string串,直接使用if(s1>s2)之类比较便可。
答:相同。由于第一个就是把ptr强制转化为指向空类型的指针;
第二个(*(void**))ptr中的(void**)ptr是将str转化成指向void类型值的指针的指针,也就是二级指针。再在前面加上*就是取内容,那么内容也就是一个只想空类型的一级指针,因此强制转化后和第一个是同样的。
答:指针相减的值:只有同类型的指针才能够相减,且“相减结果/单个变量类型内存大小”才是最终结果;
如若int *p=a[0],int *q=a[2],那么q-p=(q-p)/sizeof(int)=8/4=2是编译器默认的结果。
指针变量所占内存大小:32位操做系统是32位(4字节),64位操做系统是64位(8字节);
指针变量自增自减:自增自减中指针变量值的改变大小等于所指向的对象类型的内存大小。如:char* pt1,那么pt1++就是加1,若为int* pt2,那么pt2++就是增长4。指针运算问题都是以相应变量的类型大小做为基本单位的,例如int p[4]={0,0,0,0},p+1就是指p的地址基础上偏移4字节。同理,&p[0]+1也是同样的。除非(char*)&p[0]+1才是偏移一个字节,由于地址被强制转化为char*了。
补充:
(1)指针所指向的对象类型判断方法——去掉指针名及和它前面的*,剩下的就是指针类型。
(2)上面所说的地址地址++或--都是对于偏移地址offset而言的。
下面举例:
结果:
答:区别以下:
(1)数组指针(也称行指针),定义 int (*p)[n];其中( )优先级高,首先说明p是一个指针,指向一个整型的一维数组(或二维数组的某一行),这个一维数组的长度是n,也能够说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。
(2)指针数组,定义 int *p[n];其中[]优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1时,则p指向下一个数组元素,这样赋值是错误的:p=a;由于p是个不可知的表示,只存在p[0]、p[1]、p[2]...p[n-1],并且它们分别是指针变量能够用来存放变量地址。但能够这样 *p=a; 这里*p表示指针数组第一个元素的值,a的首地址的值。
若已定义: int a[]=[0,1,2,3,4,5,6,7,8,9],*p=a,i; 其中0≤i≤9,那么p[i]是否合法,若何法那么表示什么?
p[i]表示a[i]的指针地址,即p+i。
(3)函数指针,定义 int (*pf)(int *)为一个返回值为int,参数为int*的。
假如函数指针定义以下例:
void (*fp)(int);
则函数指针调用的两种格式:
1)(*fp)(9);
2)fp(9);
函数指针赋值的两种格式:
void fun(int s){……}
1)fp=fun;(函数名就是函数起始地址)
2)fp=&fun;(这种也能够)
注意,函数名后面必定不能有括号,不然就被认为是函数调用。
答:(1)空指针与野指针的区别:
空指针也就是一般指向为NULL的指针,常见的空指针通常指向 0 地址;
野指针是指向一个已删除或释放的对象或指向未申请访问受限内存区域的指针;
具体以下三种状况:
1)指针变量未初始化
任何指针变量刚被建立时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。因此,指针变量在建立的同时未被初始化,它就是野指针。定义指针变量时要避免野指针,要么将指针设置为NULL,要么让它指向合法的内存;
2)指针释放后以后未置空
释放内存后指针要手动置为空,不然会产生野指针。有时指针在free或delete后未赋值 NULL,便会令人觉得是合法的。别看free和delete的名字(尤为是delete),它们只是把指针所指的内存给释放掉,但并无把指针自己干掉。此时指针指向的就是被释放区的“垃圾”内存,也就成为了野指针。释放后的指针应当即将指针置为NULL,防止产生“野指针”;
3)指针操做超越变量做用域
不要返回指向栈内存的指针或引用,由于栈内存在函数结束时会被释放。
(2)空指针性质:
空指针不指向任何的对象或者函数,任何对象或者函数的地址都不多是空指针;
malloc()申请内存空间失败的时候,人家返回的值为NULL,而不是任意的;
虽然空指针可能大多数指向0地址,但对0x0这个地址取值是非法的,由于系统规定是不会给你分配0地址的。
(3)空指针的printf输出
例如:char* str=null;
printf("%s\n",str);
在win系统输出结果:(null)
在Linux系统输出结果:Segmentation fault(出错)
注:若str是野指针,即未初始化,那么这个输出代码会形成程序崩溃。
答:静态链表是用数组来描述的,也能够说是静态链表就是结构体数组。
数组中的结构体元素包括两部分:数据和指针,其中指针指向下一个元素在数组中所对应的下标。
1)使用数组对元素进行存储,在定义时大小已经肯定,后期不可扩展容量;
2)静态链表的例子正好说明了数组相对链表除了能够随机访问外,还有另外一个优点:相同的数据占用存储空间小(由于链表还要存储节点指针)。
答:关于结构体内存对齐(在没有#pragma pack宏的状况下):
•原则一、数据成员对齐规则:结构(struct或联合union)的数据成员,第一个数据成员放在offset为0的地方,之后每一个数据成员存储的起始位置要从该成员类型大小(注意是成员类型大小,而不是成员大小,如char a[10],应该取类型大小为1字节而不是数组大小10字节)的整数倍开始(好比int在32位机为4字节,则要从4的整数倍地址开始存储)。
•原则二、结构体做为成员:若是一个结构里有某些结构体成员,则结构体成员要从其内部类型最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储。)
•原则三、收尾工做:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员类型长度的整数倍,不足的要补齐(注意:补齐是补高位,就是填充0)。
举个例子:
struct stc
{
int a;
char b[9];
char c;
};
这个结构体的大小是16字节=4字节(int)+9字节(char*9)+1字节(char)+2字节(补全)
注意:
1)若结构体中含有静态成员,sizeof不包括静态和成员,由于静态成员存储在全局区(或静态区),sizeof计算的是栈空间大小(此结论亦适用于类);
2)若是编译器中提供了#pragma pack(n),上述对其模式就不适用了,例如设定变量以n字节对齐方式,则上述成员类型对齐宽度(应当也包括收尾对齐)应该选择成员类型宽度和n中较小者;
3)通常数据存储都是小端机器模式,即数据低位存储在低地址处。
补充:
(1)位域:所谓"位域"是把一个字节中的二进位划分为几 个不一样的区域, 并说明每一个区域的位数。每一个域有一个域名,容许在程序中按域名进行操做。 这样就能够把几个不一样的对象用一个字节的二进制位域来表示。结构体中存在位域时,对于位域变量大小有以下规定:
1) 若是相邻位域字段的类型相同,且紧邻的位域位宽之和小于给定类型的sizeof大小,则后面的字
段将紧邻前一个字段存储;
2) 若是相邻位域字段的类型相同,但其位宽之和大于给定类型的sizeof大小,则后面的字
段将重新的存储单元开始,其偏移量为其类型大小的整数倍;
3) 若是相邻的位域字段的类型不一样,则各编译器的具体实现有差别,VC6采起不压缩方
式,Dev-C++采起压缩方式;
4) 若是位域字段之间穿插着非位域字段,则不进行压缩;
5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。
(2) sizeof(类):类的大小计算:类的存储大小sizeof运算也能够当作结构体来计算,注意函数声明不占内存。
可是要注意继承问题:好比派生类继承基类,那么:
派生类大小=基类成员(不包括静态成员)+本身自己
注意:
1)类的静态成员因为存放在全局区域,为全部类共享,sizeof运算不包括静态成员;
2)虚函数在类中有一个函数指针指向虚表,例如,对于一个类对象如obj,其中指针运算*obj,有以下两种状况:
①类中有虚函数,那么虚函数表的指针将放在全部类对象成员最前面,那么*obj指向虚函数表指针。在32位系统中,指针是4个字节,那么对象内存的前4个字节都是存储虚函数表指针,第5个字节才开始存放类成员;
②若无虚函数,则*obj直接指向按顺序类中的第一个成员。
③因此如有虚函数声明的类,sizeof运算会将虚函数表指针大小算在内(32bit为4字节)。不管类中有几个虚函数,sizeof类都等于sizeof(数据成员)的和+sizeof(一个V表指针,为4);
④对于上述③中所说的只是在单继承或非继承时成立,派生类多继承时每继承一个含有虚函数的基类,其就增长一个虚函数表指针。若其自己还有虚函数,则该虚函数地址存放在派生类的第一个虚函数表中;
3)空类,或者类中只有函数声明(虚函数除外),没有成员变量的类,其大小sizeof(空类)=1。注意,空类求sizeof不考虑对齐问题;
4)对于子类,它的sizeof是它父类成员(不管成员是public或private),再加上它本身的成员,对齐后的sizeof;
5)对于子类和父类中都有虚函数的状况,子类的sizeof是它父类成员(不管成员是public或private,但不包括父类虚表指针),再加上它本身的成员,对齐后的sizeof,再加4(子类本身的虚表指针);
6)若既有虚函数又有虚继承,那么子类sizeof=基类sizeof(包括基类虚指针)+子类sizeof(包括子类虚指针)+子类指向基类的指针(32bit为4字节)(这个要注意)
更多sizeof运算参考:http://blog.csdn.net/u014186096/article/details/48290013
(3)C/C++中的联合体(共用体):联合体是相似结构体的,但联合体中的成员是共用内存的,也就是联合体中的成员共用同一段内存(这也是联合体存在的做用,用于节省内存),其内存大小根据成员类型最大长度的对齐原则肯定,并且,联合体中每一时刻只有当前的一个成员有效,即须要用哪一个成员,就对共用内存从新写入该成员的数据,不重写读出来的就仍是前一个成员使用的数据;
注意:有人说联合体的变量不能在定义时初始化,这是错误的。联合体变量能够在定义时初始化,可是初始化的值必须是第一个成员变量,并且要用{}括起来。以下:
union Test
{ char m[6];
int a;
float b;
};
Test test = {1};
由于最大长度的类型是int或者float,都为4,而m[6]占用6字节内存。因此采用补齐原则,联合体应为4*2=8字节内存,即sizeof(test)=8;
答:因为union只存储一个成员,若一个union有一个int变量和一个插入变量,那么若前一个int变量被赋值后,此时union存储的就是该int变量。若此时读取后一个char变量,因为char并无被重写,因此读取的仍是int变量的低8位。根据读取的int低八位数字就能够判断大端或小端存储了。
代码以下:
答:(1)C++的结构体与C的结构体区别:
C++中的struct对C中的struct进行了扩充,它已经再也不只是一个包含不一样数据类型的数据结构了,它已经获取了太多的功能:
①C的struct不能够包含成员函数,C++的struct能包含成员函数;
②C的struct不能够继承,C++的struct能继承;
③C的struct不能够实现多态,C++的struct能实现多态;
(2)C++的struct和class的区别:
最本质的一个区别:成员默认属性和默认继承权限的不一样;
①若不指明,struct成员的默认属性是public的,class成员的默认属性是private的;
②若不指明,struct成员的默认继承权限是public的,class成员的默认继承权限是private的;c和c++中struct的主要区别是c中的struct不能够含有成员函数,而c++中的struct能够。
补充:c++中struct和class的主要区别在于默认的存取权限不一样,struct默认为public,而class默认为private。
答:举例:
enum en1{
x1,
x2,
x3=10,
x4,
x5,
} emx1;
其中,emx1就是一个枚举变量,也可en1 emx1来定义枚举变量。
枚举变量不初始化,若该变量是全局变量,则系统自动初始化为0;若为局部变量,则为随机值。注:上述枚举缺省赋值,则系统自动根据已有元素值依次赋值(首位缺省自动赋值为0),因此x1~x5依次为:0,1,10,11,12。
答:(1)静态成员static
特性:在C++中,静态成员是属于整个类的而不是某个对象,静态成员变量只存储一份供全部对象共享。
注意事项:
①、静态成员在C++中既能够被类名引用,也能够被对象引用,这与C#不太同样。但注意不能经过类名来调用类的非静态成员函数;
②、静态成员函数不能够引用非静态成员变量,由于静态成员函数属于整个类,在类实例化对象以前就已经分配空间了,而类的非静态成员必须在类实例化对象后才有内存空间。但反之,类的非静态成员函数能够调用用静态成员函数;
③、类的静态成员变量必须先初始化再使用,而且静态成员必须在类体外进行初始化(由于它是多个对象共享的),不能够在类中声明时就初始化,格式如:int Info::mm=9;
④静态成员变量和函数在类声明外定义实现时都不能再加static关键字。
举例:
结果:
可见:static const能够在声明时赋值,其余的都在外面用::引用赋值。且静态成员支持类::引用和对象引用两种模式。
补充:类的静态成员变量是被全部类对象共享的;
类成员函数中定义的静态变量是被全部类对象中对应的那个成员函数所共享的(对其余成员函数无效)。
(2)常量const
在类中,咱们可能不但愿某个值在程序中的任何地方被修改,就像Math类中的PI那样,那么,咱们能够使用成员常量来实现。声明方式如:const 类型 常量名;
即将const int 变量ee初始化为111。如有多个要初始化,在ee()后面用逗号分隔。注意:C++中单纯的const常量不能够直接在声明时初始化(这是与C、C#不一样的),须要在构造函数初始化表中初始化,以下:
提醒:静态变量不能够使用初始化列表。
(3)static const类型的类成员
也就是(1)中所提到的,c++中static const类型成员应该是static和const(只读)合在一块儿的解释,那么应该有以下原则:
①、有const修饰表示初始化值不可修改;
②、有static修饰表示是对象共有的,只是因为它是在类成员函数中定义的,那么其做用范围只在对应的对象成员函数中,在对象其余成员函数中是无效的。
其初始化有以下规定:
①、static const和const static是一样的;
②、static const因为有static修饰,因此能够在类体外进行初始化,方式同单纯的static变量;但注意,虽然有const修饰,但不能够像const那样用初始化列表初始化;
③、但其又和单纯的static有所不一样,static const int型在声明时初始化在C++中也是合法的,如static const int nn=97;
答:全局变量声明与定义:
(1)在库文件中用extern声明全局变量(不包括定义)
举个例子,在test1.h中有下列声明:
以上,如今库文件用extern声明全局变量,那么全局变量的引用方式有两种:
①文件中引用库文件并定义(若全局变量已经在其余引用程序中定义,此处不需再定义);
②文件中使用extern引用并定义(同①,若已定义不需再定义);
(2)在库文件中用extern声明并定义全局变量(包括定义)
把全局变量的声明和定义放在一块儿,这样能够防止忘记了定义,如把上面test1.h改成:extern char g_str[] = "123456";
那么全局变量的引用只有一种方式:extern引用(注:若还用引用库文件会形成全局变量定义语句执行两次,引起错误);
答:静态变量定义在类成员函数中的做用周期是和该成员函数同样的,成员函数被销毁该静态变量存储内存就会释放。因此有以下结果:
运行:
可知:并非每次调用showtest()都会初始化num=0,因为它是静态的,因此只有第一次初始化有效。编译到成员函数中变量定义处时进行初始化,之后就不在进行初始化。其有全局变量的做用周期,但只有局部变量的做用域。
答:int const *p是常量指针(const of point),int * const p是指针常量(point of const),名称与指针符*与const的位置关系是对应的;const在*号前表示指针所指向的数据是常量,不可更改;const在*号后表示指针自己是常量,不可更改。下面有一个例子:
char a[] = "hello";
const char* p1 = a;
注意:int const与const int同样。
答:(1)不能做为switch语句参数的有字符串和浮点型(float、double)
① char、short、int、long、bool 基本类型均可以用于switch语句。
② float、double都不能用于switch语句(由于一旦在比较中出现精度不一致问题就无法继续了)。
③ enum类型,即枚举类型能够用于switch语句。
④ 全部类型的对象都不能用于switch语句。
⑤ 字符串也不能用于switch语句(switch是用"="进行比较,而char[]没有"="的概念,只有strcmp)。
(2)switch在编程语言中被划为“循环语句”
可是注意,switch循环中不能够使用continue,只能够使用break;
continue只能够在for/while/do while中使用;
答:在C++中单独{ }括起来的叫作程序块,其对程序执行顺序并没有改变,只是限定其中定义的变量,即其中定义的变量出了该花括号就无效了。
时刻谨记:局部变量只要出了{ }就失效。
答:如:
该代码段是能够编译并执行经过的,由于编译器会把http:当作label,即goto语句的目标。相似:
step1:a=fun();
goto step1;
总结:标签经常使用在goto语句中;
答:赋值表达式做为判断语句使用时,若赋值为非零时表达式为真,赋值为0时为假。例如:
结果是:循环一次都不会执行。
答:函数重载目的:使用方便,规范编程代码,提升可靠性。(注:并不能够节省空间)
其3个判断依据分别是:参数类型、参数个数、const;
答:函数的默认参数传递顺序是从右到左。
例如:
①int i = 3; printf("%d %d", ++i, ++i),运行输出为:5 4;
②对于printf函数,变量参数多于输出格式符:
结果为:2 89。
答:a) 始终用const限制全部指向只输入参数的指针和引用;
b) 优先经过值传递来取得原始类型int、float、char等和开销比较低的值的对象;
c) 优先按const的引用取得其余用户定义类型的输入;
d) 若是函数须要其参数的副本,则能够考虑经过值传递代替经过引用传递。这在概念上等同于经过const引用传递加上一次复制,能
够帮助编译器更好的优化掉临时变量。
答:(1)做用:C++中有时屡次调用同一函数时用一样的实参,C++提供简单的处理办法:给形参一个默认值,这样形参就没必要必定要从实参取值了。若有一函数声明:float area(float r=6.5);指定r的默认值为6.5,若是在调用此函数时,确认r的值为6.5,则能够没必要给出实参的值。
如:area( ); //至关于area(6.5);
(2)规定:指定某个参数的默认值,那么必须也指定其右端的全部参数默认值,不然调用函数省略有默认值的实参时,编译器匹配参数会出错。
如:如有float volume(float h,float r=12.5); //只对形参r指定默认值12.5函数调用能够采用如下形式:
可是以下可能就有问题:
对于f1,若调用时是f1(1.2, 2, 3),那么编译器不知道参数2究竟是赋给b仍是c的,因此报错。
答:main主函数不能被调用,为何还有返回值?
由于C语言实现都是经过函数mian的返回值来告诉操做系统函数的执行是否成功,0表示程序执行成功,返回非0表示程序执行失败,具体值表示某种
具体的出错信息.还有虽然别的函数不能调用main函数,但系统能够调用main的。
答:通常状况下,咱们在写程序的时候,每每忽略了主函数的参数,例如:
int main(){ }
在命令行下,输入程序的名称就能够运行程序了。实际上,咱们还能够经过输入程序名和相关的参数来为程序的运行提供更多的消息。参数紧跟在程序名后面,参数之间用空格分开。这些参数被称为:command-line arguments(命令行参数),也每每被称为程序的argument list(参数表)。main函数经过两个参数获取输入参数表信息,分别是argv和argc。第一个参数是一个整型的变量,它记录了用户输入的参数的个数(参数个数包括程序名),第二个参数argc是一个char型的指针数组,它的成员记录了指向各参数的指针,而且argc[0]是程序名,argc[1]是第一个参数。例如:
若该程序名为mytest,那么输入:
mytest aa bb cc dd e
则输出结果是:
the name of the program D:/WINYES/TCPP30E/OUTPUT/MYTEST.EXT
the program has 5 argument!
The arguments are: aa bb cc dd e
由上可知:argv=6,argc[i]是第i个参数的首地址。
答:(1)C++引用变量定义举例:
int a;
int& b=a;
上述就是引用变量定义方式,引用变量没什么特别的意义,仅仅是变量的一个别名,如上b就是a的一个别名。
①注意不要把int&看作地址符;
②还有就是除了把引用变量当作函数返回值或参数外,其余状况声明引用变量就须要直接在当前初始化,以告诉编译器该引用属于哪一个变量的别名。
(2)通常做为函数参数使用,但引用变量使用中要注意的地方:
1)不要将局部引用变量做为返回值,不然编译警告且执行出错。由于引用变量返回是返回自己,不是像普通局部变量同样拷贝返回,因此一旦函数结束,局部应用变量就会自动销毁,从而形成返回出错。
2)引用做函数的形参,函数将使用原始数据而不是其拷贝。
3)常引用的做用在于提升程序效率(没有拷贝、进栈出栈等操做),同时还使得函数不能够更改引用变量值。
答:分两种状况:
1)若传的是地址或引用,那么形参,实参指向同一内容,修改形参会影响实参;
2)若传的是值,由于实参值是拷贝到形参的,因此修改形参不会影响实参。
答:常说的局部变量的地址不能够做为返回值,由于函数结束就释放了,但有例外,即局部指针指向字符串时能够做为返回值,见以下解析:
(1)字符串赋值给指针时字符串是常量,赋值给数组时字符串是变量
对上述所说的字符串因初始化对象不一样而成为变量或常量,补充一个例子:
该程序回编译出错。
由于*p1="123"是常量,长度不可变化,strcat()函数显然不可用。
(2)根据上述可知:局部指针变量指向字符串时能够做为函数返回值,而局部数组变量地址则必定不能够做为返回值;
以局部数组和局部指针变量为例:当局部指针变量的初始化值是字符串时如char* p="hello",能够做为返回值,由于指针所指的字符串是常量,存储在常量区,不随函数结束而销毁;
但当返回值是局部数组时,即便初始化值和上述同样是字符串,例如char s[]="12345",但该字符串也只是变量,数组内容也就也就是局部变量。简单说:因为初始化数组的字符串是变量,用于初始化指针的字符串是常量。局部数组变量在函数调用结束以后,也就被操做系统销毁了,即回收了他的内存空间,它里面的东西随时都有可能被覆盖。虽然此时咱们得到了指向这一块内存的指针,但指向的内容已改变。
例以下面的例子就是返回值错误:
若其中p改成char *p="hello world",那么返回值不会出错。由于这里的"hello world"是做为字符串常量存在的,存储在常量区域,函数结束也不会销毁。而前者数组中"hello world"是做为变量存在的,其每一个字符都是局部变量,存储栈中,函数结束就会销毁,因此最后返回的p所指向的内容并非"hello world"。
结论:在子函数中,用于初始化指针变量的字符串是常量,该字符串自己不可被修改,存储在常量区,生命周期至程序结束;用于初始化数组的字符串是局部变量,该字符串自己能够被修改,存储在栈中,生命周期随子函数结束。
(2)进一步得出:除局部静态常量外的其余局部变量(包括局部常量)的地址都不能够做为指针类型返回值;
注意:上述仅仅是返回值是指针类型时,若返回不是指针类型,返回数据会直接拷贝,就不会存在本小节所面临的问题;
关于局部变量地址作返回值有个例外,就是静态局部变量,静态局部变量地址可作指针返回值。例如:
答:回调函数就是一个经过函数指针调用的函数,当该函数的指针(地址)做为参数传递给被调函数时才能称做回调函数。回调函数也能够象普通函数同样被程序调用;
在main中调用myCallback时,perfect做为函数指针参数传入被调用的函数,此时perfect就是回调函数。
由上可知pa->out()和pa->out(3)调用都是函数A::out(int i),
由上可知pb->out()和pb->out(4)调用都是函数B::out(int i),
由上可知p1b->out()和p1b->out(5)调用都是函数B::out(int i),
p1b->out()时,p1b的静态类型是B*,它的缺省参数是2;调用的也是B::out(int i);
做用:回调函数的做用在于保证调用函数内部实现的灵活性。
答:内联函数:即函数定义(注意是定义不是声明)位于类声明中的函数,且内联函数比较小如:
固然,除了这种内联函数外,还能够像传统的同样在类外使用关键字inline定义内联函数。
做用:避免普通的函数调用所产生的额外时间消耗,提升函数调用效率。就像宏同样,内联函数的代码是放到了符号表中的,使用时直接复制过来就行了,不用去作调用中耗时的内部处理过程(即:PC进栈出栈过程)。
补充:inline函数并不必定就被编译器视为内联函数,编译器会就状况自动肯定;
内联函数在编译阶段直接替换函数体,相似宏在预编译阶段替换同样。但内联函数和宏有最大不一样点:
内联函数在编译阶段会作类型参数检查,这也是其相对于宏最大的优点。
答:函数调用约定(calling convention)不只决定了发生函数调用时函数参数的入栈顺序,还决定了是由调用者函数仍是被调用函数负责清除栈中的参数,还原堆栈。函数调用约定有不少方式,除了常见的__cdecl,__fastcall和__stdcall以外,C++的编译器还支持thiscall方式,很多C/C++编译器还支持naked call方式。
(1)__cdecl调用方式是由函数调用者负责清除栈中的函数参数,因此这种方式支持可变参数,好比printf和windows的API wsprintf就是__cdecl调用方
式。
答:(1)strcpy(dest,src):C语言标准库函数strcpy,把从src地址开始且含有'\0'结束符的字符串复制到以dest为开始的地址空间,返回指向dest的指针。注意,dest必定要先分配足够大小地址空间,不然复制操做会形成程序崩溃。以下就有问题:
(2)strcat(dest,src):把src所指字符串添加到dest结尾处(覆盖dest结尾处的'\0')并添加'\0',返回指向dest的指针。
答:c_str()返回值是const char*,返回一个指向正规C字符串的指针;
上述代码中,b=a是拷贝赋值操做,即b开辟新内存,将a所指向的字符串常量拷贝到其中,那么b.c_str()的的返回指针天然与a.c_str()不一样,因此打印false。若b="hello world",那么打印true,由于"hello world"是字符串常量,保存在全局区,这样赋值是直接将字符串常量地址赋给b。
答:STL容器做为函数参数是值传递而不是地址传递,即便实参是容器名,但其与数组名做实参是彻底不一样的。
要实现函数中对实参的修改就是对原容器修改,通常使用引用传递。两种以下:
STL容器值传递:会将建立一个新的容器并将原容器数据拷贝过来
STL容器引用传递:传递原容器的别名,函数中操做的就是原容器自己
答:(1)栈区(stack)—— 由编译器自动分配释放 ,存放为运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
(2)堆区(heap)——通常由程序员分配释放, new, malloc之类的,若程序员不释放,程序结束时可能由OS回收 (注意:堆不能够静态分配,静态分配都是在编译阶段分配的)。
(3)全局区(或者叫静态区)(static)— 存放全局变量、静态数据、常量。程序结束后由系统释放。
(4)文字常量区 — 常量字符串就是放在这里的,如string str="Hello!"中的"Hello!"就存放在文字常量区。程序结束后由系统释放。
(5)程序代码区 — 存放函数体(类成员函数和全局函数)的二进制代码。
以下:
a、c在全局区;b、p在堆区(因为成员变量会成为对象的成员,因此b在堆区);d在栈区。
内存分配时间:
①编译时不分配内存
编译时是不分配内存的。此时只是根据声明时的类型进行占位,到之后程序执行时分配内存才会正确。因此声明是给编译器看的,聪明的编译器能根据声明帮你识别错误;
②运行时必分配内存
运行时程序是必须调到“内存”的。由于CPU(其中有多个寄存器)只与内存打交道的。程序在进入实际内存以前要首先分配物理内存。注意,涉及到内存分配的都是在运行阶段分配才有意义。
内存释放的两种模式:两个关键字delete和free释放内存,new和delete搭配、malloc和free搭配;
答:C++内存模型组成有三部分:自由区、静态区、动态区;
根据c/c++对象生命周期不一样,c/c++的内存模型有三种不一样的内存区域,即:自由存储区,动态区、静态区。
自由存储区:局部非静态变量的存储区域,即日常所说的栈;
动态区: 用new ,malloc分配的内存,即日常所说的堆;
静态区:全局变量,静态变量,字符串常量存在的位置;
注:代码虽然占内存,但不属于c/c++内存模型的一部分;
更多参考:http://blog.csdn.net/xiongchao99/article/details/74524807#t3
答:三个函数的申明分别是:
void* realloc(void* ptr, unsigned newsize);
void* malloc(unsigned size);
void* calloc(size_t numElements, size_t sizeOfElement);
都在stdlib.h函数库内,它们的返回值都是请求系统分配的地址,若是请求失败就返回NULL ;
malloc用于申请一段新的地址,参数size为须要内存空间的长度,如:
char* p;
p=(char*)malloc(20);
calloc与malloc类似,参数sizeOfElement为申请地址的单位元素长度,numElements为元素个数,如:
char* p;
p=(char*)calloc(20,sizeof(char));
这个例子与上一个效果相同
realloc是给一个已经分配了地址的指针从新分配空间,参数ptr为原有的空间地址,newsize是从新申请的地址长度 ,
如:
char* p;
p=(char*)malloc(sizeof(char)*20);
p=(char*)realloc(p,sizeof(char)*40);
注意,这里的空间长度都是以字节为单位。
C语言的标准内存分配函数:malloc,calloc,realloc,free等;
C++中为new/delete函数。
答:C++有三种,C++11有四种,这些方案的区别就在于数据保留在内存中的时间。
自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被建立,在执行完函数或代码块时,它们使用的内存被释放。C++有两种存储持续性为自动的变量;
静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态。它们在程序整个运行过程当中都存在。C++有3种存储持续性为静态的变量;
线程存储持续性(C++11):当前,多核处理器很常见,这些CPU可同时处理多个执行任务。这让程序可以将计算放在可并行处理的不一样线程中。若是变量是使用关键字thread_local声明的,则其生命周期与所属的线程同样长。本书不探讨并行编程;
动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的存储持续性为动态,有时被称为自由存储(free store)或堆(heap)。
答:简而言之:C++中类对象声明中使用new通常是定义类指针(注意C++的new只可用于类指针,不可用于类对象),这种方式建立的类指针须要在使用结束时进行delete释放内存,不然会形成内存泄露;而不使用new的对象声明是由系统自动建立并释放内存的,不须要咱们手动释放。具体区别可见以下代码:
运行结果以下图:
另外由上述结果可知:声明对象Info info和new Info()会调用构造函数,但Info* infom不会调用构造函数。但对于A* p=new B之类的对象指针建立,会调用B的构造函数(若B是A的子类,根据继承关系的对象构造,会先调用A的构造函数,再调用B的构造函数)。
答:new 不止是分配内存,并且会调用类的构造函数,同理delete会调用类的析构函数,而malloc则只分配内存,不会调用类的构造函数进行初始化类成员的工做,一样free也不会调用析构函数。
malloc函数的用法:void *malloc(int size);
注意:malloc只能够用来分配内存(分配的是虚拟内存,不是物理内存),还有void*并不表示返回空,这里表示须要程序员自行指定返回指针类型,是强制返回任何类型的指针,好比:int *p=(int *)malloc(size)。
更详细区别参见另外一博文:http://blog.csdn.net/xiongchao99/article/details/74524807#t18
答:多态分为两类:通用多态和特定多态;
(1)通用多态:参数多态、包含多态;
①参数多态:包括函数模板和类模板
②包含多态:virtual虚函数
(2)特定多态:重载多态、强制多态
①重载多态:重载多态是指函数名相同,但函数的参数个数或者类型不一样的函数构成多态
②强制多态:强制类型转换
答:定义:virtual void funtion1()=0;
有纯虚函数的类是抽象类,不能够生成对象,抽象类只能够派生,由他派生的类的纯虚函数没有重写的话派生类仍是一个抽象类;
答:C++的多态性:多态性表示使用父类的指针指向子类的对象,这样就能够使用父类类型的对象指针调用子类的成员函数,其实
就是父类虚函数的应用。
虚函数和纯虚函数的主要做用:实现多态性,即:虚函数的做用是容许在派生类中从新定义与基类同名的函数,而且能够经过基类指针或引用来访问基类和派生类中的同名函数。;
(1)虚函数动态绑定
如上,因为基类函数是虚函数(派生类相应函数就自动变虚函数,因此派生类同名函数能够不指定为虚函数),指向不一样对象的基类指针就能够调用各对象本身的函数。因此结果为:This is A,This is B;
若是上述基类的print()不是虚函数,那么结果就是This is A,This is A。
这就是虚函数在多态性继承和动态绑定方面的做用。
上述说到动态绑定,即经过基类指针对象或引用(注意:引用也可)指向派生类,调用重写的虚拟函数时直接调用被指对象(即派生类)所包含的相应虚拟函数;若调用的不是虚函数,那么直接调用基类的函数;
这里还介绍一下静态绑定,例如:((A)b).print(),输出This is A,属于静态绑定。((A)b).print()中不存在指针或引用问题,因此不是动态绑定;
其能够理解为:A temp=(A)b;temp.print();b的做用仅仅是用于初始化临时对象temp;
(2)虚函数要遵循“毫不从新定义继承而来的缺省参数”
说白了就是虚函数虽然是动态绑定的,但其参数是静态绑定(就是静态变量),只和对象指针的静态类型有关,即只能够初始化一次。
以下例:
缺省参数是静态绑定的,pb->out()时,pb的静态类型是A*,它的缺省参数是1;可是调用的是B::out(int i);
输出:
(3)一个类中将全部的成员函数都尽量地设置为虚函数老是有益的,但如下不能够设置为虚函数:
①只有类的成员函数才能说明为虚函数;
②静态成员函数不能是虚函数(虚函数是动态绑定的,静态函数必然不可);
③内联函数不能为虚函数(虚函数在调用中须要从虚函数表中取地址的,而内联函数是没有指定地址的);
④构造函数不能是虚函数(虚函数表是在构造函数运行时初始化(给虚函数分配地址)的,若构造函数是虚函数,那么就会出现本身在运行时才给本身分配地址,显然不可);
(4)析构函数一般声明为虚函数
由于多态(即基类对象指向派生类)状况下,若析构函数是虚函数,则对象在释放时会首先调用派生类继承的析构函数,而后再调用基类的析构函数,实现二者的同时释放。若析构函数不是虚函数,多态下对象是释放时就只会调用基类的析构函数,而形成派生类对象未释放而内存泄漏。
答:见以下代码:
结果:
上述例子说明:
(1)当创建一个对象时,若派生类中没有对象成员,首先调用基类的构造函数,而后调用下一个派生类的构造函数,依次类推,直至到达派生类次数最多的派生次数最多的类的构造函数为止。由于,构造函数一开始构造时,老是要调用它的基类的构造函数,而后才开始执行其构造函数体,调用直接基类构造函数时,若是无专门说明,就调用直接基类的默认构造函数。在对象析构时,其顺序正好相反。
(2)若派生类中有对象成员,首先调用基类的构造函数,而后调用下一个派生类中对象成员的构造函数,再调用该派生类的构造函数,以此类推,析构顺序正好相反。以下:
那么构造函数调用顺序:A()->C()->B();
(3)析构函数也遵循类多态性规则:若基类析构函数是虚函数(通常都是),释放指向派生类对象的基类指针或引用时会先调用派生类析构函数释放派生类,而后再调用基类析构函数释放基类。若不是虚析构函数,就直接调用基类析构函数,而再也不调用派生类析构函数。
注:根据多态性的动态绑定和静态绑定,用对象指针来调用一个函数有如下两种状况:
①若是是虚函数,会调用派生类中的版本。
②若是是非虚函数,会调用指针所指类型的实现版本。
为何析构函数要设置成虚函数:基类析构函数是虚函数virtual,在C++中咱们能够使用基类cBase的指针pBase(或引用)指向一个子类cChild,当pBase指针被撤销的时候,会先调用子类的析构函数,再调用基类的构造函数。若是不是virtual,那么撤销pBase指针时,将不会调用子类的析构函数,形成了内存泄露。
补充:
①析构函数通常都是虚函数,但构造函数不能够是虚函数;
②析构函数因为没有参数、没有返回值,因此是不能够被重载的。
答:(1)做用:
拷贝构造函数:用原对象建立并初始化新对象;
赋值构造函数:用原对象对已有的其余对象进行从新赋值;
析构函数:释放对象等做用。
注意:拷贝构造函数中建立的对象是一个实实在在的新开辟内存区域的对象,而并非一个指向原对象的指针。
(2)声明方式:
拷贝构造函数 MyClass(const MyClass & x);
赋值构造函数 MyClass&MyClass::operator= (const MyClass & x);
析构函数 ~MyClass();
(3)注意事项:
①拷贝构造函数也是构造函数,因此没有返回值。拷贝构造函数的形参不限制为const,可是必须是一个引用,以传地址方式传递参数,不然致使拷贝构造函数无穷的递归下去,指针也不行,本质仍是传值。
②赋值构造函数是经过重载赋值操做符实现的,它接受的参数和返回值都是指向类对象的引用变量。
(4)区别与共同点:
注意,拷贝构造函数和赋值构造函数的调用都是发生在有赋值运算符‘=’存在的时候,只是有一区别:
拷贝构造函数调用发生在对象尚未建立且须要建立时,如:
MyClass obj1;
MyClass obj2=obj1或MyClass obj2(obj1);
赋值构造函数仅发生在对象已经执行过构造函数,即已经建立的状况下,如:
MyClass obj1;
MyClass obj2;
obj2=obj1;
区别:拷贝构造函数就像变量初始化,赋值构造函数就如同变量赋值。前者是在用原对象建立新对象,然后者是在用原对象对已有对象进行赋值。
共同点:拷贝构造函数和赋值构造函数都是浅拷贝,因此遇到类成员含有指针变量时,类自动生成的默认拷贝构造函数和默认赋值构造函数就不灵了。由于其只能够将指针变量拷贝给新对象,而指针成员指向的仍是同一内存区域,容易产生:冲突、野指针、屡次释放等问题。解决方法就是本身定义具备深拷贝能力的拷贝构造函数或者赋值构造函数。
(5)拷贝与赋值构造函数内在原理(m_data是String类成员):
上述是内部实现原理,可知:
①拷贝和赋值构造函数都是新开辟内存,而后复制内容进来;
②赋值构造函数必定要最早检测本操做是否为本身给本身赋值,如果就会直接返回自己。若直接从第(2)步开始就会释放掉自身,从而形成第(3)步strcpy中的other找不到内存数据,从而使得赋值操做失败。
(6)将类中的析构函数设为私有,类外就不能够自动调用销毁对象,因此只能够经过new建立对象,手动销毁。
答:(1)包括两步:
一、将两个构造函数声明为私有private;
二、仅仅声明函数就能够了,不作定义;
解释:前者保证外部不可调用,后者保证内部成员he友元不可调用,所以可实现禁用。
(2)为何通常要禁用两个构造函数:如上所述,拷贝构造函数和赋值构造函数是都是浅拷贝,若成员含有指针,易产生冲突、野指针、屡次释放等问题。因此通常直接禁用,以防不测。
(3)可不能够不由用?能够,如今通常借助智能指针就能够不由用。
答:浅拷贝:好比拷贝类对象时,对象含有指针成员,只是拷贝指针变量自身,这样新旧对象的指针仍是指向同一内存区域;
深拷贝:同理,对象含有指针成员,拷贝时不只拷贝指针变量,还从新在内存中为新对象开辟一块内存区域,将原对象次指针成员所指向的内存数据都拷贝到新开辟的内存区域。
答:构造函数若是被定义为私有或者不代表私有公有(编译器默认为私有),就会形成建立对象时没法调用构造函数而出错。例如:
就会出错:error: ‘A::A()’ is private。缘由就是类外没法调用私有的构造函数。
可是也有例外,好比单例模式下,构造函数就是私有的。由于单例模式下,类对象是类本身以公有成员函数模式建立的;
答:构造函数里初始化方式和构造函数初始化列表方式;
后者效率通常高于前者(尤为是在对象指针变量初始化中),由于前者要先运行构造函数,后执行赋值操做,然后者只须要运行复制构造函数便可。
实际上,在构造函数类初始化应该叫作赋值而不是初始化。
答:注意必须初始化就是表示:对象成员不可被修改,只能够在声明是初始化;
因此,必定包含const、引用成员。固然还包括其余的,以下:
1.带有
const
修饰的类成员 ,如
const
int
a ;
2.包含引用成员,如
int
& p;
3.类类型的成员没有默认构造函数(就是类中的另外一个类类型的成员没有默认的参数为空的构造函数):
如上所说:class b中的类类型成员A,但构造函数并无在初始化列表中显示初始化它,因此b类的构造函数只会隐私的初始化它(注意全部成员变量都会通过构造函数初始化),而隐式初始化时就至关于b(NULL):A(NULL),而a没有参数为空的默认构造函数,因此会报错。两种解决方法:
①如上注释部分,添加默认构造函数;
②使用b类的初始化列表显示初始化。
答:(1)无论是私有仍是公有继承,基类的私有成员都是会被派生类继承的吗?
是的。派生类会继承基类的公有、私有成员和保护成员,只是根据继承方式和成员类型限制,不能访问私有等成员而已。
(2)派生类访问属性
① 基类的私有成员不管什么继承方式,在派生类中均不能够直接访问;
②在公有继承下,基类的保护成员和公有成员均保持原访问属性;
③在保护继承方式下,基类的保护和公有成员在派生类的访问属性均为保护属性;
④在私有继承下,基类的保护和公有成员在派生类中的访问属性均为私有属性。
(3)补充:除了public能够类外访问外(所谓类外访问通常就是类对象访问),其余两个都不能被类外访问;
可是protect相比private的访问权限仍是大一些,由于派生类的成员函数能够访问继承而来的保护(protect)成员,而不能访问继承而来的private成员。
总结访问属性:
①private:
本身所在类的成员函数访问。被派生类继承后,派生类的成员函数不可访问它(这一点比较特殊,虽然基类私有成员在派生类中仍然为私有成员,但不可被派生类的成员函数访问)。类外(类对象或者派生类对象)均不可访问它;
②protect:
本身所在类的成员函数可访问;
被非私有继承后,派生类的成员函数可访问它;
类外(本身所在类的类对象或派生类对象)均不可访问;
③public:本身所在类的成员函数可访问;
被非私有继承后,派生类成员函数能够访问它;
本身所在类对象、公有继承的派生类对象都可访问它;
(4)禁止类被继承的方法:将类的构造函数设置为私有,这样派生类在实例化时首先要先实例化基类,但基类的构造函数私有不可被访问,因此就会出错。所以能够得出结论:类的构造函数为私有,该类就不可被继承。
答:this指针:类的每一个成员函数都有一个this指针,其指向调用本身的类对象。this是一个指针,其值为*const类型的地址,不可改变不可被赋值。只有在成员函数中才能调用this指针。静态函数(方法)因为是全部类对象共享的,因此没有this指针,this指针原本就是成员函数的一个参数。如
MovePoint(int a,int b)函数的原型应该是 void MovePoint( Point *this, int a, int b)。
this指针的使用:通常都是隐式应用,显式应用通常为返回整个对象如return *this(*this就是所指向对象的别名)。
答:基类对象与派生类对象之间存在赋值相容性,包括如下几种状况:
结论:基类与派生类之间的的对象强制转化通常只向上(向父辈)进行,不使用父辈向下转换。即:通常都是基类指针指向派生类对象,而非派生类指针指向基类对象,由于后者是不安全。
注:向下转换在C++中虽然不安全,但并不由止这种作法。
答:1)该数组中若干个元素必须是同一个类的若干个对象。对象数组的定义、赋值和引用与普通数组同样,只是数组的元素与普通数组不一样,它是同类的若干个对象。定义例如:DATE dates[7];
代表dates是一维对象数组名,该数组有7个元素,每一个元素都是类DATE的对象。
注:有人可能不一样意“同类的若干个对象”,认为派生类对象也能够,可是考虑到实际中计算数组存储空间=数组长度×单个元素大小,这就要求各个元素大小相同,显然派生类对象大于基类对象,不适合做为元素。
2)对象数组能够被赋初值,也能够被赋值
例以下面是定义对象数组并赋初值和赋值:
DATE dates[4]={ DATE(7, 7, 2001), DATE(7, 8, 2001), DATE(7, 9, 2001), DATE(7, 10, 2001) }//赋初值
dates[0] = DATE(7, 7, 2001);//赋值
dates[1] = DATE(7, 8, 2001);
dates[2] = DATE(7, 9, 2001);
dates[3] = DATE(7, 10, 2001);
在创建数组时,一样要调用构造函数。若是有50个元素,就须要调用50次构造函数。在须要的时候,能够在定义数组时提供实参以实现初始化。
答:常量指针指向常对象, 常对象只能调用其常成员函数。例如:
所以经过a->f()调用的结果是voidf() const;
答:C/C++是容许多继承的,例如:class C : public A, public B。可是Java是不容许多继承的,Java对于多继承的功能是用接口来实现的。
答:即Derive::virtual public Base{ },这种用法主要是为了在多重继承的状况下节省空间,保证被屡次继承的基类自备拷贝一份。
如:类D继承自类B一、B2,而类B一、B2都继承自类A,所以在类D中两次出现类A中的变量和函数。为了节省内存空间,能够将B一、B2对A的继承定义为虚拟继承,而A就成了虚拟基类。以下图区别:
普通多继承 虚继承
答:不管是类模板或是函数模板,都是在函数或类前面加上template<class T>,而后将函数或者类中的参数类型改成T,就成为了类/函数模板。
(1)函数模板的目的:函数模板能够用来建立一个通用的函数,以支持多种不一样的形参,避免重载函数的函数体重复设计。
函数模板声明格式如:
具体定义以下:
注意:函数模板最大特色是把函数使用的数据类型做为参数,有多个类型参数则每一个参数前面要有class或者typename(class和typename能够混合着用)。函数模板的实例化由编译器在调用时自动完成,但下面几种状况须要程序员本身指定,急不可省略实参:
1)从模板函数实参表得到的信息有矛盾之处。
2)须要得到特定类型的返回值,而无论参数的类型如何。
3)虚拟类型参数没有出如今模板函数的形参表中。
4)函数模板含有常规形参。
(2)模板函数
在使用函数模板时,要将这个形参实例化为肯定的数据类型,将类型形参实例化的参数称为模板实参,用模板实参实例化的函数称为模板函数。模板函数的生成就是将函数模板的类型形参实例化的过程。
(3)类模板
类模板定义:一个类模板(也称为类属类或类生成类)容许用户为类定义一种模式,使得类中的某些数据成员、默写成员函数的参数、某些成员函数的返回值,可以取任意类型(包括系统预约义的和用户自定义的)。
类模板格式:
类体外的成员函数定义应以下(必须在传统定义前进行模板声明):
template <类型名 参数名1,类型名 参数名2,…>
函数返回值类型 类名<参数名 1 参数名 2,…>::成员函数名(形参表)
{
函数体
}
举例:
类模板的实例化不一样于函数模板自动进行,必须由程序员显示指定,格式如:Test<int> ts;
(4)模板类
模板类:就是类模板实例化后的结果。该实例化如上所述须要程序员显示指定。
答:是的。虽然平时声明通常都是在头文件.h中,实现是在.cpp源文件中,但使用模板时C++编译器是直接到声明代码的头文件中寻找实现部分的。若是模板的声明和实现分离,那么编译不会报错但连接会报错,由于编译器在声明的头文件里面找不到实现。
至于缘由:由于模板定义很特殊。因为template<…> 的参数类型在实例化前并不明确,因此编译器在不为它分配存储空间。它一直处于等待状态直到被一个模板实例告知,编译器和链接器的某一机制去掉指定模板的多重定义。因此为了容易使用,几乎老是在头文件中放置所有的模板声明和定义。
答:(1)定义:特化是模板中的概念,指模板中有一些特殊类型须要单独定义的状况。
(2)特化实现:在原模板下添加一个template<>开头的同名模板,参数定义为你须要的特殊类型,内容则根据本身需求定义。
①类模板特化:例如stack类模板针对bool类型有特化,由于实际上bool类型只须要一个二进制位,就能够对其进行存储,使用一个字或者 一个字节都是浪费存储空间的。以下:
上述template<> class stack<bool> {……}就是对stack的特化部分。
②函数模板特化:一样,函数模板特化也是针对某个特定类型的特殊处理,一个比较经典的例子:
若是须要获得正确结果就须要针对const char*的函数模板特化:
(3)模板偏特化
①定义:模板的偏特化是指须要根据模板的某些但不是所有的参数进行特化。
1)类模板的偏特化
例如c++标准库中的类vector的定义:
这个偏特化的例子中,一个参数被绑定到bool类型,而另外一个参数仍未绑定须要由用户指定。
2)函数模板偏特化
严格的来讲,函数模板并不支持偏特化,但因为能够对函数进行重载,因此能够达到相似于类模板偏特化的效果。
若是将(a)称为基模板,那么(b)称为对基模板(a)的重载,而非对(a)的偏特化。C++的标准委员会仍在对下一个版本中是否容许函数模板的偏特化进行讨论。
(4)模板特化时的匹配规则
1)类模板的匹配规则
最优化的优于次特化的,即模板参数最精确匹配的具备最高的优先权。例子:
每一个类型均可以用做普通型(a)的参数,但只有指针类型才能用做(b)的参数,而只有void*才能做为(c)的参数
2)函数模板的匹配规则
非模板函数具备最高的优先权。若是不存在匹配的非模板函数的话,那么最匹配的和最特化的函数具备高优先权。例子:
答:(1)友元类
A是B的友元类,则表示A不须要继承B类也能够访问B的成员(包括公有,保护,私有成员)。但注意,友元关系不可逆,即B不必定是A的友元类;友元关系也不可传递,即A的派生类不必定是B的友元类。若要使B为A的友元类,在A类的成员列表中定义:friend class B;
例如:
(2)友元函数
若是要在类外访问类的全部成员,或者类A中的函数要访问类B中的成员,那么该函数就应该是友元函数。如上面所述的友元类声明同样,友元函数的声明也是在须要被访问的类中声明友元函数如:friend +普通函数声明。
说明:友元类便可以声明为类的公有、也能够是私有成员,只要声明为友元函数,就能够访问类的全部成员。
友元函数声明与调用举例:
答:C++中规定,重载运算符必须和用户定义的自定义类型的对象一块儿使用,即重载运算的参数中必须包括用户自定义的类型对象。
C++中绝大部分的运算符可重载,有几个不能重载的运算符,分别是: . 和 .* 和 ?: 和 :: 和 sizeof。
运算符重载规则以下:
①、 C++中的运算符除了少数几个以外,所有能够重载,并且只能重载C++中已有的运算符。
②、 重载以后运算符的优先级和结合性都不会改变。
③、 运算符重载是针对新类型数据的实际须要,对原有运算符进行适当的改造。通常来讲,重载的功能应当与原有功能相相似,不能改变原运算符的操做对象个数,同时至少要有一个操做对象是自定义类型。
运算符重载为类的成员函数的通常语法形式为:
函数类型 operator 运算符(形参表) { 函数体;}
运算符重载为类的友元函数的通常语法形式为:
friend 函数类型 operator 运算符(形参表) { 函数体;}
以下:
const Point Point::operator+(const Point& p) { return Point(x+p.x); }
Point const operator-(const Point& p1,const Point& p2){ return Point(p1.x-p2.x); }
就是运算符的重载定义
注意:算符重载为类的成员函数时,形参个数比原始的少一个。
(1) 双目运算符重载为类的成员函数时,函数只显式说明一个参数,该形参是运算符的右操做数。
(2) 前置单目运算符重载为类的成员函数时,不须要显式说明参数,即函数没有形参。
(3)后置单目运算符重载为类的成员函数时,函数要带有一个整型形参(该形参无实质用处,是用于和前置区别的)。
具体可参考:http://blog.csdn.net/dingyuanpu/article/details/5852825
注意:运算符重载通常有两种:重载为类的成员函数或重载为友元函数
(1)只能使用成员函数重载的运算符有:=、()、[]、->、->*、new、delete。
(2)单目运算符最好重载为成员函数。
(3) 对于复合的赋值运算符如+=、-=、*=、/=、&=、!=、~=、%=、>>=、<<=建议重载为成员函数。
(4) 对于其它运算符,建议重载为友元函数。
更多描述见:http://blog.chinaunix.net/uid-21411227-id-1826759.html
答:抽象类是含有纯虚函数的类,其做用是做为多个表象不一样,但本质相同类的抽象。
故抽象类仅能够做为基类被继承,不能够实例化生成对象,不能初始化,不能被当作返回值,不能当作参数,但能够作指针类型和引用。
答:箭头操做符左边必须是指针,点操做符左边必须是实体(类对象或者结构体)。举个栗子以下:
那么访问member_a有两种方式:(*ps).member_a = 1;或者ps->member_a = 1;
其中*ps是结构体,用点操做符;而ps是指向结构体的地址指针,须要用箭头操做符。
答:成员函数被重载特征是:
(1)相同的范围(在同一个类中);
(2)函数名字相同
(3)参数不一样
(4)virtual 关键字无关紧要
覆盖就是指派生类函数覆盖基类virtual函数,特征是:
(1)不一样的范围(分别位于派生类与基类)
(2)函数名字相同
(3)参数相同
(4)基类函数必须有virtual 关键字
“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,派生对象都是调用派生类的同名函数。规则以下:
(1)若是派生类的函数与基类的函数同名,可是参数不一样,此时不论有无virtual关键字、基类的函数将被隐藏;
(2)若是派生类的函数与基类的函数同名,而且参数也相同、可是基类函数没有virtual 关键字,此时基类的函数被隐藏(注意别与覆盖混淆);
总结:
①同一类中的同名函数是重载;
②不一样类中同名函数多是覆盖,也多是隐藏。根据是否有virtual以及函数参数是否相同区分;
注意:若派生类中从新定义了基类的成员变量,则在使用派生类对象调用该对象时,只要对象没有virtual修饰,调用哪一个根据当前成员实际属于哪一个类肯定。以下:
输出结果:12
由于print()属于A,那么其中调用的a就属于a,故为1。同理下面的printf(“%d”,b.a)中的a属于b,因此就是2.
答:(1)智能指针(smart pointer)类是存储指向动态分配(堆)对象指针的类,智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。
做用:因为 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete。程序员忘记 delete,流程太复杂,最终致使没有 delete,异常致使程序过早退出,智能指针就是用来有效缓解这类问题的。由于智能指针就是一个类,当超出了类的做用域是,类会自动调用析构函数,析构函数会自动释放资源。
如原始程序:
若在delete以前发生异常,就会致使指针ps指向的内存未释放而内存泄漏。若改成使用智能指针,以下:
即便程序出现异常,只要ps指针失效(程序运行范围超出函数)就会被智能指针类自动释放。
原理:每次建立类的新对象时,初始化指针并将引用计数置为1;当对象做为另外一对象的副本而建立时,拷贝构造函数拷贝指针并增长与之相应的引用计数;对一个对象进行赋值时,赋值操做符减小左操做数所指对象的引用计数(若是引用计数为减至0,则删除对象),并增长右操做数所指对象的引用计数;调用析构函数时,构造函数减小引用计数(若是引用计数减至0,则删除基础对象)。
(2)c++里面的经常使用的智能指针包括:auto_ptr、unique_ptr、shared_ptr和weak_ptr,第一个auto_ptr已经被c++11弃用,为何摒弃auto_ptr呢?由于auto_ptr可能会出现两个或更多智能指针指向同一目标,从而形成在结束时对同一内存指针屡次释放而致使程序崩溃。share_ptr智能指针能够避免这种状况,由于share_ptr自带了引用型计数器,能够记录同一内存指针被share_ptr指针指向的次数,释放时先查看计数器的值,从而决定是否delete。unique_ptr则是能够在编译时检测出该类错误,直接不让编译经过,从而避免程序崩溃。因此相对而言auto_ptr是最容易形成程序崩溃的。
(3)智能指针类一般用类模板实现:
通常用两个类来实现智能指针的功能,其中一个类是指针类,另外一个是计数类,以下:
(3)智能指针应用
在C++中智能指针的引用库是<memory>,C++11后包括上述提到的后三种智能指针。经常使用的也就是两种:unique_ptr和share_ptr。其中后者更好用,用法以下:
就定义并初始化了两个share_ptr型智能指针s1,s2。
答:explicit用来防止构造函数初始化的隐式转换。
发生隐式转换,除非有心利用,隐式转换经常带来程序逻辑的错误,并且这种错误一旦发生是很难察觉的。原则上应该在全部的构造函数前加explicit关键字,当你有心利用隐式转换的时候再去解除explicit,这样能够大大减小错误的发生。
答:(1)构造函数能够。可是不建议抛出异常,由于抛出异常后析构函数就不会执行了,从而须要手动释放内存;
(2)析构函数不能够抛出异常,由于容易形成死循环。
①缘由:C++异常处理模型是处理那些由于出现异常而失效的对象,处理方式是调用这些失效对象的析构函数,释放掉它们占用的资源。若是析构函数向外抛出异常,则异常处理代码会调用本身,而后本身又抛出异常,……陷入无尽递归嵌套之中,所以这是不被容许的。
②处理析构函数异常的正确方式:将异常封装在析构函数内部,而不是抛出异常。
答:string函数库中有以上几个关于字符搜索的函数,返回地址;
常见用法:
string s,str;
s.find(str);//查找s字符串中含有的str,并返回str首字符的地址,没查找到就返回string::npos;
s.rfind(str);//反向查找s字符串中的str,并返回str首字符的地址,没查找到就返回string::npos;
s.find_first_of(str); //函数是查找s中查找与str字符串中任何一个字符匹配的字符,若是有,则返回第一个找到的字符索引,不然返回string::npos;
s.find_first_not_of(str); //函数是查找s中查找与str字符串中任何一个字符都不匹配的字符,若是有,则返回第一个找到的字符索引,不然返回string::npos;
substr(index,length); //函数是截取字符串,第一个参数为起始位置,第二个参数是长度;
答:STL的最大特色就是:
数据结构和算法的分离,非面向对象本质。访问对象是经过象指针同样的迭代器实现的;
容器是象链表,矢量之类的数据结构,并按模板方式提供;
算法是函数模板,用于操做容器中的数据。因为STL以模板为基础,因此能用于任何数据类型和结构。
STL中六大组件:
1)容器(Container),是一种数据结构,如list,vector,和deques ,以模板类的方法提供。为了访问容器中的数据,能够使用由容器类输出的迭代器;
2)迭代器(Iterator),提供了访问容器中对象的方法。例如,能够使用一对迭代器指定list或vector中的必定范围的对象。迭代器就如同一个指针。事实上,C++的指针也是一种迭代器。可是,迭代器也能够是那些定义了operator*()以及其余相似于指针的操做符地方法的类对象;
3)算法(Algorithm),是用来操做容器中的数据的模板函数。例如,STL用sort()来对一个vector中的数据进行排序,用find()来搜索一个list中的对象,函数自己与他们操做的数据的结构和类型无关,所以他们能够在从简单数组到高度复杂容器的任何数据结构上使用;
4)仿函数(Function object)
5)迭代适配器(Adaptor)
6)空间配制器(allocator)
注意:本文中前三者是主要解释部分;
在C++标准中,STL被组织为下面的13个头文件:
<algorithm>、<deque>、<functional>、<iterator>、<vector>、<list>、<map>、<memory>、<numeric>、<queue>、<set>、<stack> 和<utility>。
1)C++的STL容器介绍:
STL的容器能够分为如下几个大类:
一:序列容器, 有vector, list, deque, string、array(array是C++11新增的).(该类型容器也属于STL一级容器,注:STL中一级容器是容器元素自己是基本类型,非组合类型。)
二 : 关联容器, 有set, multiset, map, mulmap, hash_set, hash_map, hash_multiset, hash_multimap
三: 其余的杂项: stack, queue, valarray, bitset
如上,咱们重点关注vector,deque,list,map:
其中vector和deque内部是数组结构,也就是顺序存储结构。
deque是双端队列;
list是双向循环链表;
关联容器的元素是自动按key升序排序,因此是排好序的。例如Map、mulmap;
部分容器的介绍以下:
2)C++中的容器迭代器iterator应用以及迭代失效问题
(1)迭代器(iterator)是检查容器内元素并遍历元素的数据类型
在C++相似指针的做用,对操做容器很方便。在java和C#中直接让其代替了指针。每种容器都有本身的迭代器类型,这里以vector为例:
vector<int>::iterator iter //iter是vector<int>类型容器的迭代器
迭代器都有begin()和end()两个函数能够获取指向容器起始地址和结束地址。
iterator迭代器能够使用的操做符有:
++、--是基本的双向迭代符;
*用于取指向地址的元素;
==用于判断两个迭代器是否相等;
(2)迭代失效
vector非末尾位置进行插入或删除(erase)都会导致迭代器失效;
失效缘由:
①插入操做:
由于vector 动态增长大小时,并非在原空间后增长新空间,而是以原大小的两倍在另外配置一个较大的新空间,然
后将内容拷贝过来,接着再原内容以后构造新元素,并释放原空间,故容器原先的全部迭代器都会失效;
②删除操做:
删除操做后,被删除数据对应的迭代器及其后面的全部迭代器都会失效。最多见的解决方法:从新对迭代器赋初值,对于erase()函数,因为其返回值是下一个元素的迭代器iter,因此删除后直接使用iter = v.erase(iter)从新对iter赋值便可;
补充:而对于关联结构如链表list,哈希表map等删除一段连续元素偏偏要使用iter++。
(3)vector的访问方式
访问vector中的数据 使用两种方法来访问vector。
一、 vector::at()
二、 vector::operator[],就是好比对于vector<int> array,能够用array[1]之类的访问;
operator[]主要是为了与C语言进行兼容。它能够像C语言数组同样操做。但at()是咱们的首选,由于at()进行了边界检查,若是访问超过了vector的范围,将抛出一个例外。因为operator[]不作边界检查,容易形成一些错误,全部咱们不多用它。
三、vector建立二维数组,如vector<vector<int>> array,array就是一个行和列都是可变的二维数组;
四、对于vector数组长度求取,好比vector<vector<int>> array,array.size()表示二维数组行数,array[0].size()表示列数;
3)算法部分介绍:
STL算法部分主要由头文件<algorithm>, <numeric>, <functional>组成;要使用STL中的算法函数必须包含头文件<algorithm>,对于数值算法须包含<numeric>,<functional>中则定义了一些模板类,用来声明函数对象;
STL中算法大体分为四类:
非可变序列算法:指不直接修改其所操做的容器内容的算法。
可变序列算法:指能够修改它们所操做的容器内容的算法。
排序算法:包括对序列进行排序和合并的算法、搜索算法以及有序序列上的集合操做。
数值算法:对容器内容进行数值计算。
查找算法(13个):判断容器中是否包含某个值;
adjacent_find:在iterator对标识元素范围内,查找一对相邻重复元素,找到则返回指向这对元素的第一个元素的Forward Iterator;不然返回last;
binary_search:在有序序列中查找value,找到返回true。重载的版本实用指定的比较函数对象或函数指针来判断相等;
count:利用等于操做符,把标志范围内的元素与输入值比较,返回相等元素个数;
count_if:利用输入的操做符,对标志范围内的元素进行操做,返回结果为true的个数;
equal_range:功能相似equal,返回一对iterator,第一个表示lower_bound,第二个表示upper_bound;
其余:find,find_end,find_first_of,find_if,lower_bound,upper_bound,search,search_n;
排序和通用算法(14个):提供元素排序策略;
inplace_merge:合并两个有序序列,结果序列覆盖两端范围。重载版本使用输入的操做进行排序;
merge:合并两个有序序列,存放到另外一个序列。重载版本使用自定义的比较;
nth_element:将范围内的序列从新排序,使全部小于第n个元素的元素都出如今它前面,而大于它的都出如今后面。重载版本使用自定义的比较操做;
partial_sort:对序列作部分排序,被排序元素个数正好能够被放到范围内。重载版本使用自定义的比较操做;
partial_sort_copy:与partial_sort相似,不过将通过排序的序列复制到另外一个容器;
其余:partition,random_shuffle,reverse,reverse_copy,rotate, rotate_copy,sort,stable_sort,stable_partition;
删除和替换算法(15个);
排列组合算法(2个):提供计算给定集合按必定顺序的全部可能排列组合;
算术算法(4个);
生成和异变算法(6个);
关系算法(8个);
集合算法(4个);
堆算法(4个);
更多介绍见:http://blog.csdn.net/xiongchao99/article/details/73694530
答:scanf的调用格式为:scanf(格式控制,地址表列),注意是地址表,就是说后面参数必须是地址符,和printf参数表不同;
printf的调用格式为:printf(“格式控制字符串”,输出表列),注意是输出列表,即直接是须要输出的变量名。但有一个例外,若格式控制符为s表示字符串,即输出字符串则需输出参数是数组名(数组首地址)。
printf("%02x",xx)表示xx输出16进制,且至少要2位。若输出位数不到2位16进制,根据二进制负数高位补1,正数补0进行填充;
答:cout输出小数点后几位能够实现,比较麻烦,本文采用C的方式:
首先要引入库文件include<stdio.h>,而后使用printf("%.5f",d);
其中,d能够是double或者float类型。
答:在可变长参数函数(例如printf函数)或者不带原型声明函数中,在调用该函数时C自动进行类型提高,float类型的实际参数将提高到double,但float是32位的,double是64位的,而%d只输出低32位的数据,并将这些32位二进制以十进制数输出。注意:printf()函数须要转移字符%表示输出。
答:(1)直接使用cin>>str:
char str[10];
cin>>str;//这种方式遇到空白(如空格、制表符、换行符)就结束,如:输入姓名 Bill Colintun就只会读入Bill,舍弃后半部分;
(2)cin.getline(str,20):
int size=20;
char str[size];
cin.getline(str,size);//能够读取size-1个字符,最后一个自动添加'\0',这种方只会在读取到指定数目字符或遇到换行符时才结束。
答:涉及函数fopen、fclose、fprintf、fscanf、feof等。
(1)举例以下:
(2)feof( fp)函数
上述函数中fp指向文件的指针,feof函数的用法是从输入流读取数据,若是到达稳健末尾(遇文件结束符),返回值eof为非零值,不然为0。
答:(1)Windows下的C++多线程
首先须要引用<windows.h>库文件,建立和关闭线程以下调用:
①建立一个线程
HANDLE thread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL); //括号中为参数,其中ThreadProc为该线程运行的函数
注意:通常只用到第3和第4个参数,第四个是传递给子线程的参数;
②关闭该线程
CloseHandle(thread);
代码示例:
③保证同步的互斥量应用
上述示例中,并不能保证主线程和子线程交替运行,要确切保证交替运行通常要使用互斥量Mutex;
该函数用于创造一个独占资源,第一个参数咱们没有使用,能够设为NULL,第二个参数指定该资源初始是否归属建立它的进程,第三个参数指定资源的名称。
HANDLE hMutex = CreateMutex(NULL,TRUE,"screen");
这条语句创造了一个名为screen而且归属于建立它的进程的资源。
该函数用于释放一个独占资源,进程一旦释放该资源,该资源就再也不属于它了,若是还要用到,须要从新申请获得该资源。申请资源的函数以下:
第一个参数指定所申请的资源的句柄,第二个参数通常指定为INFINITE,表示若是没有申请到资源就一直等待该资源,若是指定为0,表示一旦得不到资源就返回,也能够具体地指定等待多久才返回,单位是千分之一秒。
具体对互斥量在多线程中应用以下:
本例程与②中比较就是在每一次线程运行前添加了互斥量,在本次线程运行结束时释放互斥量资源,从而保证两个线程的同步关系;
(2)Linux下的C++多线程
须要遵循POSIX接口,称为pthread,POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了建立和操纵线程的一整套API。在类Unix操做系统(Unix、Linux、Mac OS X等)中,都使用Pthreads做为操做系统的线程。线程库实行了POSIX线程标准一般称为Pthreads。
引入库:pthread.h
①数据类型:
pthread_t:线程句柄;
pthread_attr_t:线程属性;
②操纵函数:
pthread_create():建立一个线程;
pthread_exit():终止当前线程;
thread_cancel():中断另一个线程的运行;
pthread_join():阻塞当前其余的线程,直到join指定的线程运行结束;
pthread_attr_init():初始化线程的属性;
thread_attr_setdetachstate():设置脱离状态的属性(决定这个线程在终止时是否能够被结合);
thread_attr_getdetachstate():获取脱离状态的属性;
pthread_attr_destroy():删除线程的属性;
thread_kill():向线程发送一个信号;
③同步函数:用于 mutex 和条件变量
thread_mutex_init() 初始化互斥锁;
thread_mutex_destroy() 删除互斥锁;
thread_mutex_lock():占有互斥锁(阻塞操做);
thread_mutex_trylock():试图占有互斥锁(不阻塞操做)。即,当互斥锁空闲时,将占有该锁;不然,当即返回;
thread_mutex_unlock(): 释放互斥锁;
pthread_cond_init():初始化条件变量;
thread_cond_destroy():销毁条件变量;
thread_cond_signal(): 唤醒第一个调用pthread_cond_wait()而进入睡眠的线程;
thread_cond_wait(): 等待条件变量的特殊条件发生;
Thread-local storage(或者以Pthreads术语,称做 线程特有数据);
thread_key_create(): 分配用于标识进程中线程特定数据的键;
thread_setspecific(): 为指定线程特定数据键设置线程特定绑定;
thread_getspecific(): 获取调用线程的键绑定,并将该绑定存储在 value 指向的位置中;
thread_key_delete(): 销毁现有线程特定数据键;
④工具函数:
thread_equal(): 对两个线程的线程标识号进行比较;
pthread_detach(): 分离线程;
pthread_self(): 查询线程自身线程标识号;
⑤经过pthread实现C++多线程:
注:因为pthread库不是Linux系统默认的库,链接时须要使用库libpthread.a,因此在使用pthread_create建立线程时,在编译中要加-lpthread参数。
更多Linux的C++ 多线程请参考:http://www.cnblogs.com/youtherhome/archive/2013/03/17/2964195.html
答:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其余线程不能进行访问直到该线程读取完,其余线程才可以使用。不会出现数据不一致或者数据污染;线程不安全就是不提供数据访问保护,有可能出现多个线程前后更改数据形成所获得的数据是脏数据。
1)线程安全问题都是由全局变量及静态变量引发的。如有多个线程同时对全局变量、静态变量执行写操做,通常都须要考虑线程同步,不然的话就可能影响线程安全;
2)局部变量局部使用是安全的,由于每一个thread 都有本身的运行堆栈,而局部变量是被拷贝副本到各自堆栈中,互不干扰;
3)标准库里面的string在多线程下不是安全的,它只在两种状况下安全:①多个线程同时读取数据是安全的;②只有一个线程在写数据是安全的;
4)MFC的CString也不是多线程安全的;
5)volatile不能保证全局整形变量是多线程安全。volatile是一个类型修饰符(type specifier),volatile变量是用于多个线程访问的变量,被设计用来修饰被不一样线程访问和修改的变量。volatile提醒编译器它后面所定义的变量随时都有可能改变,所以编译后的程序每次须要存储或读取这个变量的时候,都会直接从变量地址中读取数据。若是没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,若是这个变量由别的程序更新了的话,将出现不一致的现象。。(注意,volatile变量也能够是const变量);
6)安全性:局部变量>成员变量>全局变量;
7)给出一个全局变量在多线程中的应用实例:两个线程并发执行如下代码,假设a是全局变量,初始值是1:
Void foo ( )
{
++a
printf("%d",a);
}
那么输出结果有4种可能:3 2; 2 3; 3 3; 2 2;
缘由:①每一个线程对a作++先读取,再++,最后写(a++或者++a之类的都不是原子操做,因此若不想被其余线程中途打断,都须要作线程同步);
②每一个线程对a作printf前,先将数据读取到临时变量并压栈,再弹出打印;
③以上每一小步(例如先、再、后分3小步)完成后,下一小步进行前均可能被另外一线程打断;
答:在 C中:能够放到赋值操做符=左边的是左值,能够放到赋值操做符右边的是右值。有些变量既能够当左值又能够当右值,这一说法不是很准确,左值为Lvalue,其实L表明Location,表示在内存中能够寻址,能够给它赋值(常量const类型也能够寻址,可是不能赋值),Rvalue中的R表明Read,表示能够读取它的值,不能够取地址和修改。
在 C++中,每个表达式都会产生一个左值或者右值,相应的该表达式也就被称做“左值表达式", "右值表达式"。对于基本数据类型来讲左值右值的概念和C没有太多不一样,不一样的地方在于自定义的类型,并且这种不一样比较容易让人混淆,有两点要注意:
1) 对于基础类型,右值是不可被修改的,也不可被 const, volatile 所修饰;
2) 对于自定义的类型,右值却容许经过它的成员函数进行修改;
补充:临时值是const属性的,不可更改,因此临时值也能够是右值。
以下:
int a;
int b;
a = 3;
b = 4;
a = b;
b = a;
// 如下写法不合法。
3 = a;
a+b = 4;
如上,a,b都是左值,也能够是右值;但三、4为常量,只可为右值;a+b的结果变了成了一个临时值,临时值是const类型的,不可被赋值,故不可做为左值,因此a+b=4错误。
结论:1)左值、右值广义上都是表达式;
2)C++中左值是能够取地址的值和被赋值修改的,或者说是指表达式结束后仍然存在的值;相反右值不可取地址和修改的了,如常量或临时值;
3)++a是左值,由于a先进行自增运算,后返回本身,返回值仍然为本身自己,能够取地址和赋值,因此++a是左值;
a++先将a赋值给临时变量,而后返回临时变量(接着本身进行自增运算),临时变量不可更改,因此a++不是左值;
4)左值引用和右值引用;
记住:很是量引用必须是左值(即很是量引用必须指向左值),由于很是量引用面临被修改的可能,左值才可知足;
常量引用必须是右值(即常量引用必须指向右值),由于左值可能被修改,不知足常量要求;
例如:
const cs& ref1 = 1是对的的;
cs& ref2 = 1就是错误的。ref2是很是量引用,必须指向左值,而1明显是右值。
5)若想把左值看成右值来使用,例如一个变量的值,再也不使用了,但愿把它的值转移出去,C++11中的std::move就为咱们提供了将左值引用转为右值引
用的方法。
答:格式以下:
其中,指数部分必须为整数,不但是小数。
答:在为传统面向对象语言的程序作单元测试的时候,常常用到mock对象。Mock对象经过反射机制,能够访问类的全部成员(包括公有、保护和私有成员),因此mock的反射机制也就最大程度破坏了面向对象的封装性。
答:例如,a=5,b=3:
方法一:算术法,a=a+b;b=a-b;a=a-b;
方法二:异或法,a=a^b;b=a^b;a=a^b;
答:通常是将整数和小数部分分开转化,而后加在一块儿;
整数部分:十进制整数转换为二进制整数采用"除2取余,逆序排列"法。具体作法是:十进制整数除以2,能够获得一个商和余数;再把商除以2,又会获得一个商和余数,如此进行,直到商为0时为止,而后把先获得的余数做为二进制数的低位有效位,后获得的余数做为二进制数的高位有效位,依次排列起来。也叫“倒序取余”。
小数部分:十进制小数转换成二进制小数采用"乘2取整,顺序排列"法。具体作法是:用2乘以十进制小数,能够获得积,将积的整数部分取出,再用2乘余下的小数部分,又获得一个积,再将积的整数部分取出,如此进行,直到积中的小数部分为零,此时0或1为二进制的最后一位。或者达到所要求的精度为止。
答:转义字符除了\n \t \\等外,还有\后面接数字的,如:
'\123'表示\ddd模式的3位八进制,ASCII码是83;(注:int m=0123,数值前有一个0,也表示123是八进制)
'\x12'表示\xdd模式的2位十六进制,ASCII码是18;
答:(1)正则表达式用处:主要用于字符串匹配、字符串合法性验证(如邮箱名合法性验证)、替换等。
(2)C++里面使用正则表达式通常有三种:C regex,C ++regex,boost regex,其处理速度依次减慢;
(3)C++中正则表达式用于邮箱合法性验证:
答:(1)memset()函数原型:extern void *memset(void *buffer, int c, int size) 其中,buffer:为指针或是数组,c:是赋给buffer的值,size:是buffer的长度;
(2)做用:Memset用来将buffer开始的长为size的内存空间所有设置为字符c,通常用在对定义的字符串进行初始化为''或'/0';这个函数在socket中多用于清空数组。
举例:原型是memset(buffer, 0, sizeof(buffer)),应用如char a[100];memset(a,'/0',sizeof(a));
(3)对于常见的一个问题“memset函数可否用来初始化一个类对象”,有以下答案:
理论上是能够,可是很危险,尤为是在类中有虚函数的状况下,不能够使用memset初始化,不然会破坏原对象的虚函数表指针。总之,memset如上所述通常只用于常规的数据存储区的清零或初始化,不要用于其余。
答:内存碎片分为内部碎片和外部碎片。
(1)内部碎片就是已经被分配出去(能明确指出属于哪一个进程)却不能被利用的内存空间;如某一数组容量为90,但实际只能够分配8字节的倍数大小的容量即96,剩下的6个字节内存在当前程序中得不到利用也不能再次分配给其余程序,因此成为了碎片。
(2)外部碎片是由于频繁的分配与回收物理页面会致使大量的、连续且小的页面块夹杂在已分配的页面中间,就会产生外部碎片;假设申请内存块就0~9区间,继续申请一块内存为10~14区间。把第一块内存块释放,而后再申请一块大于10个单位的内存块,好比说20个单位。由于刚被释放的内存块不能知足新的请求,因此只能从15开始分配出20个单位的内存块。如今整个内存空间的状态是0~9空闲,10~14被占用,15~24被占用。其中0~9就是一个内存碎片了。若是10~14一直被占用,而之后申请的空间都大于10个单位,那么0~9就永远用不上了,变成外部碎片。
(3)如何解决内存碎片? 采用Slab Allocation机制:整理内存以便重复使用。
Slab Allocator的基本原理是按照预先规定的大小,将分配的内存分割成特定长度的块,以彻底解决内存碎片问题。以下图:
每次根据须要,从上述合适大小的chunks内存组中选择一块进行存储,从而减小内部碎片产生。同时,使用结束后并不释放而是在机制下进行整理以便下次重复使用,从而又能够减小外部碎片(外部碎片主要因为频繁分配释放产生)。
答:最简单的栈溢出就是无限递归调用,即函数递归调用时没有设置终止条件,就会发生栈溢出。