参考《程序员面试宝典》
1. 基本概念
1.1 赋值语句
例1. 按位与操作,例如:a=3,b=3,a&b值等于 0011 & 0011 结果还是0011,那么值还是3; a=4,b=3,a|b:按位或操作, 0100 | 0011 结果是0111,输出的值为7;a||b, a和b进行或运算,只要两者有一个为真则结果即为1.
例2. while(x) { count++; x=x&(x-1);} 此循环用来求输入值x转化为二进制后1的个数。eg:9(1001)&8(1000)=8(1000)=>8(1000)&7(0111)=>0 循环两次,有2个1.
1.2 i++
例1. for(a=0,x=0;a<=1 && !x++; a++){a++;} 和 for(a=0,x=0;a<=1 && !x++; ){a++;} 前式进入循环体前a和x均自增为1,再a++后a就为2,循环判别条件a<=1不符合即不进行右边的操作,结果为a=2,x=1;而后式结果a=1,x=2.
例2.
main(){ int arr[]={6,7,8,9,10}; int *ptr=arr; *(ptr++)+=123; printf("%d,%d ",*ptr,*(++ptr)); }
C中printf计算参数时是从右往左压栈的。 第2句使得ptr指向第一个元素6;第3句等价于 *ptr=*ptr+123;ptr++, 第一个元素值应为129,而ptr指向第二个元素7;第4句从右往左运算,首先++ptr等价于ptr++,此时ptr指向第三个元素8,*ptr=8,所以全部输出为8.
1.3 类型转换
例1. float a=1.0f; count << (int)a <<endl; 结果输出为1065353216,不是1,因为浮点数在内存里和整数的存储方式不同,(int&)a相当于将该浮点数地址开始的sizeof(int)个字节当成int型的数据输出,因此这取决于float型数据在内存中的存储方式,而不是经过(int&)a显示转换的结果1.
unsigned int 变量赋值给unsigned char变量时会发生字节截断(3位和高于3位的将被程序自动丢弃)
隐式类型转换:混合算术表达(int类型和double类型数据相加为double类型)、用一种类型的表达式赋值给另一种类型的对象、表达式传值给目标函数但与形参类型不同(形参类型为目标转换类型)、函数返回表达式类型与函数类型不一致(转换为函数返回值类型)
准则:1)为防止精度丢失,类型总被提升为较宽类型 eg.char(向上提升一般转为ASCI码,如’a’为97)->int->float->double->long double
2)所有含有小于整型的有序类型的算术表达式在计算之前其类型都会被转换成整型
1.4 运算符问题
例1. char a=0xA5;char b=~a>>4+1; 优先级顺序 ~优于+优于>>,所以先执行取反操作 1010 0101 取反 0101 1010,再直接右移5位得0000 0010.
A - 65 - 0100 0001;a – 97 – 0110 0001; 可显示字符;0x是十六进制前缀,0开头是八进制。
例2. 利用位运算实现两个整数加法
int add(int a,int b){ if(b==0) return a; //没有进位的时候完成运算 int sum,carry; sum = a ^ b;//完成第一步没有进位的加法运算 carry=(a & b) << 1;//完成第二步进位并且左移运算 return add(sum,carry);//递归,相加 }
1.5 交换与比较
例1. 不用交换和判断语句找出两个数中间比较大的.
int max = ((a+b)+abs(a-b))/2
例2. 不用排序给出三个数的中间的那个数。
int max(int a,int b) {return a>=b?a:b;} int min(int a,int b) {return a<=b?a:b;} int medium(int a,int b,int c){ int t1=max(a,b); int t2=max(b,c); int t3=max(a,c); return min(t1,min(t2,t3)); }
例3. 不使用中间变量交换a,b的值。
//法一 a=a+b;//可能出界 b=a-b; a=a-b; //法二 异或 a=a^b; b=a^b; a=a^b;
1.6 C和C++的关系
例1. 在C++程序中调用被C编译器编译后的函数,为什么要加extern “C”?答:C++语言支持函数重载,C语言不支持,函数被C++编译后在库中的名字与C语言不同,假设某个函数的原型为void foo(int x,int y),该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。C++提供了C连接交换指定符号extern “C”解决名字匹配问题。
例2. C和C++。C是一种结构化语言,重点在于算法和数据结构,考虑的是通过一个过程如何将输入处理得到输出,而C++考虑的首先是一个对象模型,让这个模型能够契合与之对应的问题域,这样可以通过获取对象的状态信息得到输出或实现过程控制。
2.预处理、const与sizeof
2.1 宏定义
例1. 使用预处理指令#define声明一个常数,用以表明1年中有多少秒(忽略闰年)
#define SECOND_PER_YEAR (60 * 60 * 24 * 365) UL
没有结束分号,UL是无符号长整型, 防止在16位机的整型溢出。
例2. 写一个标准的宏MIN,输出最小值
直到嵌入inline操作符变为标准C的一部分,宏都是方便产生嵌入代码的唯一办法。
#define MIN(A,B) ( (A) <= (B) ? (A) : (B))
2.2 const
const 修饰指针,一般分为4种:
int b = 500; const int* a = &b; //情况1 int const *a = &b; //情况2 int* const a = &b; //情况3 const int* const a= &b; //情况4
情况1,2,如果const位于星号的左侧,则const用来修饰指针所指向的变量,即指针指向为常量;如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。情况1,2都是指针所指向内容为常量,此时改变*a的值的办法要么改变b的值要么指向别处,且情况1,2定义时都可以先不进行初始化,因为虽指针内容是常量但指针本身不是常量。
int b=500; const int* a=&b; *a=600; //错误 b=600; cout<< *a <<endl;//得到600 int c=700; a=&c; cout<< *a <<endl;//得到700
情况3,指针本身是常量,不能对指针更改,且定义时必须同时初始化。
int b=500,c=600; int* const a;//错误,没有初始化 int* const a = &b; //正确,必须初始化 *a = 600; //正确,允许改值 cout<< a++ << endl;//错误
情况4,指针本身和指向的值均为常量,都不可以改变。
const成员函数:不改变数据成员的函数加上const关键字进行标识,可提高程序的可读性和可靠性,一旦企图修改数据成员的值,则编译器按错误处理。如getter函数,例int GetY() const; 且必须以同样的方式重复出现在函数实现里,表示成员值不可变,否则编译器会看成不同的函数。如果把const放在函数声明前,意味着函数的返回值是常量。另,在const成员函数中,用mutable修饰成员变量名后,就可以修改类的成员变量了。
例1.const和#define有什么不同?
C++可以用用const定义常量,也可以用#define定义常量,前者比后者有更多优点:1)const常量有数据类型,而宏常量没有数据类型,编译器可以对前者进行类型安全检查,后者只进行字符替换,没有类型安全检查,并且在字符替换中可能会产生意料不到的错误。 2)有些集成化的调试工具可以对const进行调试,但是不能对宏常量进行调试,在C++程序中只使用const常量而不使用宏常量,即const常量完全取代宏常量。
2.3 sizeof
例1.
#include <iostream> #include <stdio.h> #include <string.h> using namespace std; struct{ short a1; short a2; short a3; }A; struct{ long a1; short a2 }B; int main(){ char* ss1 = "0123456789"; char ss2[] = "0123456789"; char ss3[100] = "0123456789"; int ss4[100]; char q1[]="abc"; char a2[]="a "; char* q3="a "; char *str1 = (char *) malloc(100); void *str2 = (void *) malloc(100); cout<< sizeof(ss1) <<endl; cout<< sizeof(ss2) <<endl; cout<< sizeof(ss3) <<endl; cout<< sizeof(ss4) <<endl; cout<< sizeof(q1) <<endl; cout<< sizeof(q2) <<endl; cout<< sizeof(q3) <<endl; cout<< sizeof(A) <<endl; cout<< sizeof(B) <<endl; cout<< sizeof(str1) <<endl; cout<< sizeof(str2) <<endl; return 0; }
ss1是一个字符指针,指针的大小是一个空值,4字节;ss2是一个字符数组,由具体填充值来定,填充值是“0123456789”,1个字符所占空间是1个字节,再加上隐含的“ ”,一共是11字节;ss3也是一个字符数组,预分配100,所以100字节;ss4整形数组,预分配100,每个整型变量所占空间是4,所以一共是400字节;q1与ss2类似,4字节;q2里面有一个“ ”,算作一位,所以空间大小是3字节;q3是一个字符指针,指针的大小是一个定值,4字节;str1,str2均为指针,都为4字节。
A和B是两个结构体,默认情况下,为了方便对结构体内元素的访问和管理,当结构体内的元素的长度都小于处理器的位数的时候,便以结构体里面最长的数据元素为对齐单位,即,结构体的长度一定是最长的数据元素的整数倍,如果结构体内存在长度大于处理器位数的元素,那么就以处理器的位数为对齐单位,但是结构体内类型相同的连续元素将在连续的空间内,和数组一样。结构体A中有3个short类型变量,各自以2字节对齐,结构体对齐参数按默认的8字节对齐,则a1,a2,a3都取2字节对齐,sizeof(A)为6,也是2的倍数;结构体B中a1为4字节,a2为2字节,默认对齐参数是8,a1取4字节对齐,a2取2字节对齐,结构体大小为6字节,6不为4的整数倍,补空字节增到8,符合,所以sizeof(B)为8.
CPU的优化性能是这样的:对于n字节的元素(n=2,4,8,…),它的首地址能被n整除才能获得最好的性能。
例2. sizeof和strlen之间的区别
char* ss="0123456789"; sizeof(ss); //4,ss是指向字符串常量的字符指针 strlen(ss); //1,*ss是第一个字符 char ss[]="0123456789"; sizeof(ss); //11,ss是数组,计算了“ ”位置 strlen(ss); //1,*ss是第一个字符 char ss[100]="0123456789"; sizeof(ss); //100,ss表示在内存中预分配的大小,100*1=100 strlen(ss); //10,内部实现使用一个循环计算字符串的长度,知道“ ”为止 int ss[100]="0123456789"; sizeof(ss); //400,ss表示在内存中的大小,100*4=400 strlen(ss); //错误,strlen参数只能是char*,且必须以“ ”结尾
区别:1)sizeof操作符的结果类型为size_t,它在头文件中的typeof为unsigned int类型,保证能容纳实现所建立的最大对象的字节大小。2)sizeof是运算符,strlen是函数。3)sizeof可以用类型做参数,strlen只能用char*做参数,且必须以“ ”结束,sizeof还可以用函数做参数。4)数组做sizeof的参数不退化,传递给strlen就退化为指针。5)大部分编译程序在编译的时候就把sizeof计算了,是类型或是变量的长度,这是sizeof可以用来定义数组维数的原因。6)strlen的结果要在运行的时候才能计算出来,用来计算字符串的长度,而不是类型占内存的大小。7)sizeof后如果类型必须加括号,如果是变量可不加,因为sizeof是个操作符不是函数。8)数组作为参数传给函数时传的是指针而不是数组,传递的是数组的首地址,编译器不知道数组的大小,如果想在函数里知道数组的大小,需要进入函数后用memcpy将数组赋值出来,长度由另一个形参传出去。9)计算结构变量大小就必须讨论数据对齐问题,为了使CPU存取的速度最快,C++在处理数据时经常把结构变量中的成员的大小按照4或8的倍数计算,这就叫数据对齐(data alignment),可能浪费一些内存,但是理论上CPU速度更快了。10)sizeof不能用于函数类型、不完全类型(如void)或位字段。
例3. int **a[3][4] 占多大字节?3*4*4=48
例4. 结论
sizeof不是函数也不是一元运算符,类似宏定义的特殊关键字,sizeof()括号内的内容在编译过程中是不被编译的,而是被替代类型,如int a=9;sizeof(a)在编译过程中替换为sizeof(int),结果为4.如果sizeof(a=6),也是一样转换为a的类型,且不被编译,所以a的值还是9。
1)unsigned影响的只是最高位bit的正负,数据长度不会变,即:sizeof(unsigned int) == sizeof(int)
2)自定义类型的sizeof取值等同于它的类型原型,如:typedef short WORD; sizeof(short) == sizeof(WORD);
3)对函数使用sizeof,在编译阶段会被函数返回值的类型取代,如:int f1(){return 0;} cout<<sizeof(f1())<<endl;//f1返回值是int,因此认为是int
4)只要是指针,大小就是4
5)数组的大小就是乘积。
例5. 如下返回结果是4,因为var[]等价于*var,已经退化为一个指针了
char var[10]; int test(char var[]) { return sizeof(var); };
例6. 如下float占4个字节,char占1个字节,int adf[3]占12字节,总共17字节,根据内存对齐原则,选择4的倍数,所以是20字节
class B{ float f;char p;int adf[3]; }; cout<<sizeof(B)<<endl;
例7. 一个空类占1个空间,单一继承的空类空间也为1,多重继承的空类空间也是1,但是虚继承涉及虚表(虚指针),另当别论。
2.4 内联函数和宏定义 (参考链接)
1.内联函数在运行时可调试,而宏定义不可以;
2.编译器会对内联函数的参数类型做安全检查或自动类型转换(同普通函数),而宏定义则不会;
3.内联函数可以访问类的成员变量,宏定义则不能;
4.在类中声明同时定义的成员函数,自动转化为内联函数。
内联函数和普通函数相比可以加快程序运行的速度,因为不需要中断调用,在编译的时候内联函数可以直接被镶嵌到目标代码中,而宏只是一个简单的替换。内联函数需要做参数类型检查。inline是指嵌入代码,就是在调用函数的地方不是跳转,而是把代码直接写到那里去,对于短小的代码来说inline是增加空间消耗换来的是效率提高,这方面和宏一样,但是inline和宏相比没有付出任何额外代价的情况下更安全。
内联函数一般在一个函数不断被重复调用或者函数只有简单几行且函数内不包含for、while、switch语句时调用。宏尽量少使用,它不是函数,只是在编译前将程序中有关字符串替换成宏体。inline是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。
关键字inline必须与函数定义体放在一起才能是函数成为内联,仅将inline放在函数声明前不起任何作用,inline void Foo(){……}
3. 指针和引用
3.1 指针基本问题
例1. 指针和引用的差别?
★ 相同点:
1. 都是地址的概念;
指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。
★ 区别:
1. 指针是一个实体,而引用仅是个别名;
2. 引用使用时无需解引用(*),指针需要解引用;
3. 引用只能在定义时被初始化一次,之后不可变,但是指定的对象其内容可以变;指针可重新赋值指向另一个对象;
4. 引用没有 const,指针有 const;
5. 引用不能为空,指针可以为空;
6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
7. 指针和引用的自增(++)运算意义不一样;
8. 从内存分配上看:程序为指针变量分配内存区域,而引用不需要分配内存区域。
例2.
#include <iostream> using namespace std; int main() { int iv; //T int iv2=1024; //T int iv3=999; //T int &reiv; //F,声明引用的同时必须初始化 int &reiv2 = iv; //T int &reiv3 = iv; //T int *pi; //T,声明了一个整数指针,并没有定义指向的地址 *pi = 5; //F,整数指针pi并没有指向实际的地址,直接赋值是错误的 pi=&iv3; //T,指针pi指向iv3实际的地址 const double di; //F,const常量赋值时必须初始化 const double maxwage=10.0; //T const double *pc=&maxwage; //T,const常量指针赋值并同时初始化 }
3.2 传递动态内存
例1. 实现两数的交换,参数传递、值传递、指针传递(地址传递)、引用传递
#include <iostream> using namespace std void swap1(int p,int q){ //swap1传的是值得副本,函数体内被修改了形参p,q(实际参数的一个拷贝) int temp; //行参被修改了,但是不影响主题函数中的a和b,是局部变量,函数swap1结束时,形参p,q所在栈被删除了 temp=p; p=q; q=temp; } void swap2(int *p,int *q){ //传的是一个地址进去,在函数体内的形参*p*q是指向实际参数a,b的两个指针。 int *temp; //新建了一个指针,没有分配内存 *temp=*p; //不是指向而是拷贝,把*p所指向的内存里的值(实参a的值)拷贝到*temp所指向的内存里,但是int *temp没有分配内存,所以系统在拷贝时临时给了个随机地址,让它存值,分配的随机地址函数结束后不收回,造成内存泄漏。 *p=*q; *q=*temp; } void swap3(int *p,int *q){ //传的是一个地址,在函数体内的形参*p,*q指向实际参数地址的两个指针 int *temp; //int *temp新建了一个指针,没有分配内存 temp=p; //这句是指向而不是拷贝,temp指向了*p所指向的地址(也就是a) p=q; //p指向了*q所指向的地址(即b q=temp; //q指向了*temp所指向的地址(即a),函数不实现两数的交换,函数体内只是指针的变化。 } void swap4(int *p,int *q){ //可以实现两数的交换,因为他修改的是指针所指向的地址的值 int temp; temp=*p; *p=*q; *q=temp; } void swap5(int &p,int &q){ //可以实现两数的交换,是一个引用传递,修改的结果直接影响实参 int temp; temp=p; p=q; q=temp; } int main(){ int a=1,b=2; //swap1(a,b); //swap2(&a,&b); //swap3(&a,&b); //swap4(&a,&b); //swap5(a,b); cout<<a <<" "<<b<<endl; }
例2. 运行如下函数,会出现什么后果?程序崩溃,因为GetMemory不能传递动态内存,main函数中的str一直都是NULL。
#include <iostream> using namespace std; void GetMemory(char *p,int num){ p = (char *)malloc(sizeof(char) * num); }; int main(){ char *str = null; GetMemory(str,100); strcpy(str,"hello"); return 0; }
问题在GetMemory函数中,函数中的*p实际上是主函数中str的一个副本,编译器总是要为函数的每个参数制作临时副本。本例中, p申请了新的内存,只是把p所指的内存地址改变了,但是str丝毫未变,因为函数GetMemory没有返回值,因此str并不指向p所申请的那段内存,所以函数GetMemory并不能输出任何东西。每执行一次GetMemory就会申请一次内存,但申请的内存却不能有效释放,结果是内存一直被独占,最终造成内存泄漏。
如果一定要用指针参数去申请内存,那么应该采用指向指针的指针,传str的地址给函数GetMemory。
...... void GetMemory(char **p, int num){ *p=(char *)malloc(sizeof(char) * num); }; int mian(){ char *str=null; GetMemory(&str,100); strcpy(str,"hello"); cout<<*str<<" "<<str<<" "<<&str<<endl; return 0; }
打印结果为 h hello 0*22f7c.str就是字符串的值,*str首字符的值,&str是字符串的地址值。如上使用的是“指向指针的指针”也可以用函数返回值来传递动态内存地址。
//其他不变 char *GetMemory(char *p,int num){ p = (char *)malloc(sizeof(char) * num); return p; }; int main(){ char *str = NULL; str = GetMemory(str,100); strcpy(str,"hello"); return 0; }
下例说明了整型变量时如何传递的,GetMemory2把v的地址穿了进来,*z是地址里的值,是v的副本,直接修改地址里的值不需要返回值,就把v给修改了,因为v所指向地址的值发生了改变。
..... void GetMemory2(int *z){ *z=5; }; int main(){ int v; GetMemory(&v); cout<< v << endl; return 0; }
例3. 如下函数有什么问题?
char *strA(){ char str[] = "hello world"; return str; }
这个str里存的地址是函数strA栈帧里“hello world”的首地址,函数调用完成,栈帧恢复到调用strA之前的状态,临时空间被重置,堆栈“回缩”,strA栈帧不再属于应该访问的范围,存于strA栈帧里的“hello world”当然也不会被访问,这个函数正确输出结果,但违背函数的栈帧机制。分配内存时有一句话“一旦使用,它即改变”。上述函数返回的是局部变量的地址,调用函数后,局部变量str已经释放了,所以返回的结果是不确定的且不安全,随时都有可能被收回。修改如下:
const char* strA(){ chat *str="hello world"; return str; }
首先,char c[]=”hello world”;是分配一个局部数组;char *c=”hello world”;是分配一个全局数组。局部数组是局部变量,它所对应的是内存中的栈,全局数组是全局变量,它所对应的是内存中的全局区域,字符串常量保存在只读的数据段,而不是像全局变量那样保存在普通数据段(静态存储区)。
例4. 如下函数运行会导致运行时错误,因为这种做法会给一个指针分配一个随意的地址,非常危险,不被允许。
int *ptr; ptr = (int)0x8000; *ptr=oxaabb;
例5. 判断
1)函数的形参在函数未调用时预分配存储空间(False,调用到实参才会分配空间)。2)若函数的定义出现在主函数之前,则可以不用再说明(False,函数需要在它被调用前声明,与main函数无关)。3)若一个函数没有return语句,则什么值也不返回(False,主函数main中可以不写return语句,因为编译器会隐式返回0)。4)函数的形参和实参的类型应该一致(True)。
3.3 函数指针
例1. 写出函数指针、函数返回指针、const指针、指向const的指针、指向const的const指针
void (*f)() void* f() const int* int* const const int* const
例2.如下数据声明都代表了什么?
float(**def)[10];//def是一个二级指针,它指向的是一个一维数组的指针,数组的元素都是float double*(*gh)[10];//gh是一个指针,指向一个一维数组,数组元素都是double* double(*f[10])();//f是一个数组,f有10个元素,元素都是函数的指针,指向的函数类型是没有参数且返回double的函数 int*((*b)[10]);//同 int*(*b)[10],一维数组的指针 Long(* fun)(int);//函数指针,指针返回值是long,所带的参数是int,如果去掉(* fun)的括号(),那么就是指针函数 Int (*(*F))(int,int))(int);//F是一个指向函数的指针,它指向一种函数(参数类型为int,返回值为int类型的函数)
3.4 指针数组和数组指针
例1. 以下程序的输出是:
#include<stdio.h> #include<iostream> using namespace std; int main(){ int v[2][10] = {{1,2,3,4,5,6,7,8,9,10},{11,12,13,14,15,16,17,18,19,20}}; int (*a)[10] = v; //数组指针 cout<<**a<<endl; cout<<**(a+1)<<endl; cout<<*(*a+1)<<endl; cout<<*(a[0]+1)<<endl; cout<<*(a[1])<<endl; return 0; }
a定义的是一个指针指向一个10个int元素的数组,即二维数组第一行数据{1,2,…,8,9,10}。a+1表明a指针向后移动1*sizeof(数组大小);a+1后共向后移动40个字节,*a+1仅针对这一行向后移动4个字节。
输出如下:1 11 2 2 11
例2. 一个指向整型数组的指针的定义为: int (*ptr)[]; int *ptr[]是指针数组,ptr[]里面存的是地址,指向位置的值就是*ptr[0]、*ptr[1]、*ptr[2],不要存*ptr[0]=5、*ptr[1]=6,因为这里面没有相应的地址; int *(ptr[])与int *ptr[]相同;int ptr[]是一个普通数组。
指针数组,是指一个数组里面装着指针;指向数组的指针,代表它是指针,指向整个数组。
例3. 用变量a给出下面的定义
int a;//一个整型数 int *a;//一个指向整型数的指针 int **a;//指向指针的指针,他指向的指针是指向一个整型数 int a[10];//一个有10个整型数的数组 int *a[10];//一个有10个指针的数组,该指针是指向一个整型数 int (*a)[10];//一个指向有10个整型数数组的指针 int (*a)(int);//一个指向函数的指针,该函数有一个整型参数并返回一个整型数 int (*a[10])(int);//一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数
例4. 写出如下程序片段的输出
int a[]={1,2,3,4,5}; int *ptr=(int*)(&a+1); printf("%d,%d",*(a+1),*(ptr-1));
*(a+1)是正常的指针运算,a指向第一个元素,a+1移动一个整型字节指向第二个元素,加上*号是取它的值。。(int*)(&a+1)表示指向a数组的第6个元素(尽管这个元素不存在),那么(ptr-1)所指向的数据就是a数组的第5个元素—5。
数组名本身就是指针,再加个&就变成了双指针,双指针就是指二维数组,加1,就是数组整体加一行。
3.5 迷途指针
迷途指针也叫悬浮指针、失控指针,是当对一个指针进行delete操作后——这样会释放他说指向的内存——并没有把它设置为空时产生的,后面如果没有重新赋值就试图再次使用该指针,引起的结果很糟糕。虽然这个指针仍然指向原来的内存区域,但编译器已经把这块区域分配给了其他的数据,所以删除指针后把它设置为空指针很有必要。
空指针和迷途指针的区别:当delete一个指针的时候,仅仅是让编译器释放内存,但指针本身依然存在,这一个迷途指针;使用语句ptr=0可以将迷途指针改为空指针,通常如果在删除一个指针后又把它删除了一次,会造成程序非常不稳定,但是如果只删除一个空指针会非常安全,使用迷途指针或空指针是非法的,而且有可能造成程序崩溃,但空指针造成的崩溃是可预料的。。
3.6 指针和句柄
句柄是一个32位的整数,实际上是windows在内存中维护的一个对象内存物理地址列表的整数索引,因为windows的内存管理经常会将当前空闲对象的内存释放掉,当需要时访问再重新提交到物理内存,所以对象的物理地址是变化的,不允许程序直接通过物理地址来访问对象。程序将想访问的对象的句柄传递给系统,系统根据句柄检索自己维护的对象列表就能知道程序想放问的对象及其物理地址了。句柄是一种指向指针的指针,windows是一个以虚拟内存为基础的操作系统,对象经常在内存中移动来满足程序需求,对象移动意味着地址变化了,windows为各应用程序腾出一些内存地址,用来专门登记各应用对象在内存中的地址变化,内存管理器把移动对象的新地址告诉告知这个句柄地址来保存,只要记住句柄地址就可以知道对象具体在哪存储。即:句柄地址(稳定)—>记载着对象在内存中的地址—>对象在内存中的地址(不稳定)—>实际对象。
windows系统用句柄标记系统资源,指针标记某个物理内存的地址,不同的概念。
3.7 this指针
this指针注意的有以下:
1)This指针本身是一个函数参数,只能在成员函数中使用,全局函数和静态函数不可用。成员函数默认第一个参数为 T* const this
2)this在成员函数的开始前构造,在成员的结束后清除。
3)this并不占用对象的空间,相当于非静态成员函数的隐含的参数。不过所有成员函数不管是不是隐含的都不会占用对象的空间,只会占用参数传递时的栈空间,或者直接占用个寄存器。
4)采用TYPE xx方式定义的话,即C中的struct,在栈里分配内存,这时this指针的值就是这块内存的地址;采用new方式创建对象的话,在堆里分配内存,new操作符通过eax返回分配的地址,谈后设置给指针变量,之后去调用构造函数,这时将这个内存块的地址传给ecx。
5)大多数编译器通过ecx寄存器传递this指针。
6)this指针只有在成员函数中才有定义,但与对象之间没有包含关系,不能通过对象使用this指针。
4. 循环、递归和概率
4.1 递归基础知识
例1. 递归函数mystrlen(char *buf,int N)是用来实现统计字符串中第一个空字符前面的字符长度。
#include<iostream> using namespace std; int mystrlen(char *buf,int N){ if(buf[0]==0||N==0) return 0; else if(N==1) return 1; int t=mystrlen(buf,N/2); //折半递归取长度 if(t<N/2) return t;//如果长度小于输入N值的一半,取当前长度 else return (t+mystrlen(buf+N/2,(N+1)/2)); } int main(){ char buf[]={'a','b','c','d','e','f','