目录
- 1. 一般变量引用
- 2. 指针变量引用
- 3. 变量引用
- 4. 如何交换两个字符串
- 5. 参数引用
- 6. 参数引用的常见错误
- 7. 指针和引用有什么区别
- 8. 为什么传引用比传指针安全
- 9. 指针的声明
- 解读复杂指针声明:右左法则
- 10. 用指针赋值
- 11. 指针加减操作
- 12. 指针比较
- 13.内存访问违规
- 14. 指针的隐式转换
- 15. 指针常量与常量指针的区别
- 18. this指针的正确叙述
- 19. this指针
- 20. 指针数组与数组指针的区别
- 22. 函数指针与指针函数的区别
- 23. 数组指针与函数指针的定义
- 24. 各种指针的定义
- 27. typedef用于函数指针的定义
- 28. 什么是“野指针”
- 29. 野指针的危害
- 30. 有了malloc/free,为什么还要new/delete
- 31. 指针的初始化
- 32. 各种内存分配、释放函数的联系和区别
- 33. 动态内存的传递
- 34. 动态内存的传递
- 36. “野指针”用于变量值的互换
- 37. 内存的分配方式有几种
- 38. 什么是句柄
- 39. 指针与句柄有什么区别
《C和C++程序员面试秘笈》-董珊山海编著
1. 一般变量引用
一般变量
1、引用相当于是变量的别名
2、对引用取地址 == 对变量取地址
引用相当于变量取别名;并没有为引用开辟内存单元,它们占用同一个存储单元
2. 指针变量引用
#include<iostream>
using namespace std;
#if 1
int main()
{
int a = 1, b = 10;
int* p = &a;
int* &pa = p; //pa是p的引用,并且此时p里面保存了变量a的地址
(*pa)++; //操作的是变量a
cout << "a = " << a << endl;//2
pa = &b; //pa依然是p的引用,不过此时p里面保存的是变量b的地址
(*pa)++; //操作的是变量b
cout << "b = " << b << endl;//11
system("read");//和Windows平台下的getchar()作用类似
return 0;
}
#endif
3. 变量引用
#include<iostream>
using namespace std;
int main()
{
int a = 1, b = 10;
int &d = a; //引用类型的变量,声明的同时必须初始
//&d = b; //error:引用是从一而终的,不能改变的,以后该引用不能再引用其他变量
system("pause");
return 0;
}
引用类型的变量
声明的同时必须初始化
引用是从一而终的,不能改变的,以后该引用不能再引用其他变量
不允许对野指针的内容进行赋值,因为可能引发未知错误
4. 如何交换两个字符串
法1:指针引用类型-->传指针引用
法2:二维指针 -->传指针的地址
以上两种方法,其实最终操作:都是操作的字符串的地址,交换二者的地址
法1
void swap1(char* &x,char* &y)
{
char *temp;
temp = x;
x = y;
y = temp;
}
法2
void swap2(char** x,char** y)
{
char *temp;
temp = *x;
*x = *y;
*y = temp;
}
5. 参数引用
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
const float pi = 3.14f;
float f;
float f1(float r)//f赋值给临时变量temp,temp返回给调用者
{
f = r * r*pi;//全局变量f
return f;
}
float& f2(float r)//没有建立临时变量temp,直接将全局变量f返回给调用者
{
f = r * r*pi;
return f;
}
#if 1
int main()
{
float f1(float = 5); // 声明f1()的默认参数调用,其默认值为5
float& f2(float = 5);// 声明f2()的默认参数调用,其默认值为5
float a = f1();
//float&b = f1();//不允许对临时变量进行引用
float c = f2();//正确,变量c直接接收全局变量f的值
float&d = f2();//正确,不另外定义变量,d是全局变量的引用(引用函数的返回值,注意变量的有效期(注意:要使得变量f的有效期比d的有效期长))
cout << f << endl;//78.5
d += 1.0f;//此时d是全局变量f的引用,所以全局变量f增加1
cout << f << endl;//79.5
system("pause");
return 0;
}
#endif
6. 参数引用的常见错误
常量类型变量,不能被非常量类型引用引用
-->const修饰的变量,不能被非const的引用引用
常量类型的变量,其引用也必须是常量类型的
常量引用,表明不能通过这个引用去更改被引用的对象
-->不能使用常量引用修改被引用变量的值
-->const引用了一个变量,那就不能通过这个引用赋值(这样就会改变原变量,与const本义冲突)
7. 指针和引用有什么区别
1、初始化要求不同
引用在创建的同时必须初始化到一个有效的对象
指针在定义的时候可以不初始化,在定义之后可以重新赋值
2、可修改性不同
引用一旦被初始化为指向一个对象,就不能被改变为另一个对象的引用(给引用赋值并不改变它和原始对象的绑定关系,只是更改原始对象的值而已)
指针在任何时候都可以改变为指向另一个对象
3、不存在NULL引用
引用初始化时,必须指向某个确实存在的对象
指针可以是NULL,不需要总是指向某些对象,可以把指针指向任意的对象
4、测试的区别
引用不会指向空值,所以使用引用之前不需要测试它的合法性
指针则需要经常进行测试
5、应用的区别
如果,一旦指向一个对象后就不改变指向,那么应该使用引用
如果,存在指向NULL(不指向任何对象)或者在不同时刻指向不同的对象,应该使用指针
8. 为什么传引用比传指针安全
引用:
不存在空引用,并且引用一旦被初始化为指向一个对象,就不能被改变为另一个对象的引用,因此引用很安全
指针:
对指针来说,它可以随时指向别的对象,并且可以不被初始化,或者为NULL,所以不安全。
const指针仍然存在空指针,并且有可能产生野指针
9. 指针的声明
一个整型数 An interger
int a;
一个指向整型数的指针 A pointer to an integer
int *a;
一个指向指针的指针,它指向的指针是指向一个整型数的 A pointer to a pointer to an integer
int* *a;
一个有10个整型数的数组 An array of 10 integers
int a[10];
一个有10个指针的数组,每个指针是指向一个整型数的 An array of 10 pointers to integers
int* a[10]; //含有10个指针元素的一维数组
一个指向 有10个整型数的一维数组 的指针 A pointer to an array of 10 integer
int (*a)[10];//指向一维数组
一个指向函数的指针,该函数有一个整型参数并返回一个整型数 A pointer to a function that takes an integer as an argument and returns an integer
int (*a)(int);
一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数 An array of 10 pointers to functions that take an integer argument and return an integer
int (*a[10])(int);//一维数组,含有10个元素,每个元素都是一个指针,每个指针是一个函数指针
解读复杂指针声明:右左法则
1、首先找到未定义的标识符,从这个标识符开始读起(一个声明里面未定义的标识符只会有一个)
2、左右法则:
先从最里面的圆括号开始看起,然后往右看,再往左看。每当遇到圆括号时就调转阅读方向。
一旦解析完圆括号里面的所有的东西,就跳出圆括号,重复这个过程,直到整个声明解析完毕
如:int (*temp[5])(int*p);
1、首先找到那个未定义的标识符,就是temp
2、temp的外面有一对圆括号
3、在这个圆括号里面
temp右边是一个[]运算符,说明temp是一个含有5个元素的数组
temp的左边有一个*,说明temp数组里的每个元素都是指针类型
要注意这里的*修饰的不是temp,而是修饰temp[5],
原因是[]运算符优先级比*高,temp先跟[]结合,因此*修饰的是temp[5]
跳出这个圆括号括号,看右边,也是一对圆括号,说明说明temp[5]数组的每个元素是函数指针类型,
函数指针所指向的函数具有int*类型的形参,返回值类型为int
如:int (*(*temp)[5])(int *p);
1、temp被一个圆括号包含,左边又有一个*,那么temp是一个指针
2、跳出括号,右边是一个[]运算符号,说明temp是一个指向数组的指针
往左看,左边有一个*号,说明这个数组的元素是指针
3、跳出括号,右边又有一个括号,说明这个数组的元素是指向函数的指针
总结:temp是一个指向数组的指针,这个数组的元素是函数指针,这些指针指向具有int*类型的形参,返回值为int类型的函数
如:int (*(*temp)(int *p))[5];
temp是个函数指针,
这类函数具有int*类型的形参,返回值是指向数组的,
数组是具有5个int元素的数组
10. 用指针赋值
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#if 1
int main()
{
char a[] = "hello,world";
char *ptr = a;
cout << *(ptr + 4) << endl;// o
cout << ptr[4] << endl; // o
cout << a[4] << endl; // o
cout << *(a + 4) << endl; // o //*(a+4) == a[4]
*(ptr + 4) += 1;
cout << a << endl; // hellp,world //'o'+1='p'
system("pause");
return 0;
}
#endif
11. 指针加减操作
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
int main()
{
int a[5] = { 1,2,3,4,5 };
int *ptr = (int*)(&a + 1);//a[5] 指针跑到了数组的外面
cout << *(a + 1) << endl;//2
cout << *(ptr - 1) << endl;//5
system("pause");
return 0;
}
对指针进行加1,操作,得到的是下一个元素的地址,而不是原地址值直接加1。
所以一个类型为t的指针移动,以sizeof(t)为移动单位
a与&a地址是一样的,但是意思不一样。
a是数组首(元素)地址,也就是a[0]的地址 ; a+1是数组下一个元素的地址,即a[1]
&a是对象(此处的对象指数组)的首地址; &a+1是下一个对象的地址,即a[5]
12. 指针比较
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
int main()
{
char str1[] = "abc";//栈上
char str2[] = "abc";//栈上
const char str3[] = "abc";//栈上
const char str4[] = "abc";//栈上
//1 2 3 4内容都是在栈中分配内存 abc加一个\'0',但是地址是不同的
const char *str5 = "abc";//栈上
const char *str6 = "abc";//栈上
char *str7 = (char*)"abc";//栈上,注意"abc"为const char[4]类型
char *str8 = (char*)"abc";//栈上
//5 6 7 8也是在栈中分配内存,它们都指向"abc"字符串
//注意"abc"存放在数据区,所以str5 6 7 8 其实指向同一块数据区的内存
cout << (str1 == str2) << endl;//0
cout << (str3 == str4) << endl;//0
cout << (str5 == str6) << endl;//1
cout << (str6 == str7) << endl;//1
cout << (str7 == str8) << endl;//1
system("pause");
return 0;
}
13.内存访问违规
#include<iostream>
using namespace std;
int main()
{
char a;
char *str1 = &a;
char *str2 = (char*)"AAA";
//char Arr[5] ="Hello";
//strcpy(str1, Arr);
//cout << str1 << endl;
//str指向1个字节大小的内存区,复制"Hello"至少需要6个字节.所以会导致内存越界
//str2[0] = (char)"B";
//cout << str2 << endl;
//str2指向"AAA"这个字符串常量。因为是常量,所以对str2[0]的赋值操作是不合法的,也会导致程序崩溃
system("read");
return 0;
}
14. 指针的隐式转换
不能对NULL进行解引用
编译时错误:不允许的隐式类型转换
运行时错误:访问野指针,或者对NULL进行解引用等
15. 指针常量与常量指针的区别
常量指针
const int* ptr;
指向常量的指针,指针可以修改指向,但指针所指向的地址的内容是不可修改的
指针常量
int* const ptr;
它首先是一个常量,然后它才是一个指针
指针是一个常量,不能修改这个指针所指向的地址,一开始初始化指向哪儿,它就只能指向哪儿
就像一个数组的数组名一样,是一个固定的指针
但是注意:这个指针指向的地址里的内容是可以修改的
指针是常量,不可以改变指针的指向(即 指针本身不可以修改),但指针指向的内容可以修改
-
- 略
18. this指针的正确叙述
类的非静态成员函数才有this指针
19. this指针
一个类的成员函数只有一份,类的所有对象共用这个成员函数的函数体
成员函数之所以能把属于此类的各个对象的数据区分开,就在于每次执
行类的成员函数时,会把当前对象的this指针(对象首地址)传入成员函
数,函数体内所有对类内数据成员的访问,都会转化为this->数据成员的方式
20. 指针数组与数组指针的区别
指针数组:表示它是一个数组,并且数组中的每一个元素是指针(每个指针,都在栈上分配了内存)
int * a[10]
数组指针:表示它是一个指针,并且指向了一个数组
int (*a)[10];
int * b = new int[10]
- 略
22. 函数指针与指针函数的区别
指针函数(返回指针类型的函数)
int* func(int _a,int _b){...}
带指针的函数,本质是一个函数,返回的是某一类型的指针
指针函数是返回指针类型的函数
每一个函数,本身都有一个入口地址,该地址相当于一个指针。
比如函数返回一个整型值,实际上也相当于返回一个指针变量的值,
只不过这时的变量是函数本身而已,而整个函数相当于一个“变量”
函数指针(是指向函数地址的指针)
本质是个指针,只不过该指针变量指向函数,可用该指针变量调用函数
函数指针是指向函数地址的指针
23. 数组指针与函数指针的定义
指针数组 int *a[10];
数组指针 int (*a)[10]; int *a=new int[10];
指向函数的指针数组 int (*a[10])(int,int);
24. 各种指针的定义
函数指针 void (*a)(int,int);
函数返回指针 int *a();
常量指针 const int*p; //指向const的指针
指针常量 int* const p;//const指针
指向const的const指针 const int* const p;
- 函数指针的使用
- 函数指针的使用
27. typedef用于函数指针的定义
如:
typedef int (*PtrToFun)(int,int);//定义了PtrToFun类型,一个函数指针类型
//定义了函数指针类型。可以用这种类型定义函数指针变量来调用相同类型的函数。
使用:
int fun(int x,int y); //定义了一个函数
PtrToFun p=fun; //把函数fun的地址赋给PtrToFun类型的函数指针变量p
int ret = p(2,3); //调用p(2,3),实现fun(2,3)的调用功能
28. 什么是“野指针”
“野指针”是指向“垃圾”内存的指针 (不是NULL指针)
“野指针”是很危险的
“野指针”的成因
1、指针变量没有被初始化。
指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存
让指针指向合法的内存后,再通过指针,对指向的内容进行修改,才是安全的
2、指针被free或者delete之后,没有置为NULL。
让人误以为该指针是个合法的指针
29. 野指针的危害
注意:
对没有初始化(没有指向合法内存)的指针/“野指针”指向的内容进行赋值操作是十分危险的
30. 有了malloc/free,为什么还要new/delete
1、简介:
malloc/free是C++/C的标准库函数
new/delete是C++的运算符
它们都可用于申请动态内存和释放内存
2、对于 非内部数据类型 的对象
1、对象在创建的同时自动执行构造函数,对象在消亡之前要自动执行析构函数
2、由于,malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free
3、new/delete能完成上述任务
4、new运算符能够完成动态内存分配和初始化工作,delete运算符运算符能完成清理和释放内存工作
C++中,new/delete运算符可以调用类的构造函数和析构函数
31. 指针的初始化
“野指针”必须初始化为NULL
32. 各种内存分配、释放函数的联系和区别
C语言的标准内存分配函数:malloc、calloc、realloc、free等
malloc与calloc的区别为1块与n块的区别
1、malloc的调用形式为(Type*)malloc(size):
在内存的动态存储区中分配一块长度为"size"字节的连续区域,返回该区域的首地址,此时内存中的值没有初始化,是随机数
2、calloc的调用形式为(Type*)calloc(n,size):
在内存的动态存储区中分配n块长度为“size”字节的连续区域,返回首地址,此时内存中的值被初始化为0
3、realloc的调用形式为(Type*)(realloc)(*ptr,size):
将ptr内存大小增大到size,新增加的内存块没有初始化
4、free的调用形式为free(void*ptr):
释放ptr所指向的一块内存空间
C++中,new/delete运算符可以调用类的构造函数和析构函数
33. 动态内存的传递
new/malloc等在堆区为字符串“abcde”申请内存时,
应该是 new(strlen("abcde")+1),
因为字符串是以'\0'作为结束符的,应该多分配一个字节的内存放置'\0'
函数中不能返回栈内存地址,应该返回堆内存地址
(因为函数调用完,栈就会被销毁)
34. 动态内存的传递
想在函数内部,为函数体外申请堆区内存,有三种方法
注意:编译器总是为函数的每个参数制作临时的变量,不注意这个问题的话,就只是在操作函数内部的临时变量,而不是操作外部的那个指针
1、传递二级指针,直接操作外部指针的地址
2、传递指针的引用,操作这个指针就是操作外部的指针
3、返回堆内存首地址
堆内存不再使用时,应该把堆内存释放,并把指针赋值为“NULL”
避免内存泄漏、野指针的产生,是良好的编程习惯
- 动态内存的传递
36. “野指针”用于变量值的互换
“野指针”不能用于变量值的互换
必须对指针进行初始化,才能对指针指向的内容进行赋值,否则会导致程序运行时崩溃
37. 内存的分配方式有几种
静态存储区
在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,例如全局变量
栈区
执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放
堆区
动态内存,在程序运行的时候用malloc或new申请,程序员自己负责在合适时使用free或delete释放内存
38. 什么是句柄
Windows环境中,句柄是用来标识项目的
Windows程序中并不是用物理地址来表示一个内存块、文件、任务或动态装入模块的,
相反,WINDOWS API给这些项目分配确定的句柄,并将句柄返回给应用程序,然后通过句柄来进行操作
句柄地址(稳定)(记载着对象在内存中的地址)-->对象在内存中的地址(不稳定)(对象实际物理内存地址)
注意:程序每次重新启动,系统不能保证分配给这个程序的句柄还是原来的那个句柄,而且绝大多数情况的确是不一样的
39. 指针与句柄有什么区别
对于Windows句柄的理解及其与一般指针的区别
1、指针对应着一个数据在内存中的地址,得到了指针就可以自由地修改该数据,
2、Windows并不希望一般程序修改其内部数据结构,因为这样不太安全。
所以Windows给每个GlobalAlloc等函数声明的内存区域指定一个句柄,句柄是一种指向指针的指针
句柄和指针都是地址,不同之处在于:
1、句柄所指的可以是一个很复杂的结构,并且很有可能与系统有关的。
比如说线程的句柄,它所指的就是一个类或者结构,它和系统有很密切的关系。
当一个线程由于不可预料的原因而终止时,系统就可以返回它所占用的资料,比如CPU、内存等。
反过来想可以知道,这个句柄中的某一项是与系统进行交互的。
由于Windows系统是一个多任务的系统,它随时都可能要分配内存、回收内存、重组内存。
2、指针也可以指向一个复杂的结构,但是通常是由用户定义的,所以必需的工作都要用户完成,特别是在删除的时候。