《C和C++程序员面试秘笈》第3章 引用和指针

1. 一般变量引用

一般变量
	1、引用相当于是变量的别名
	2、对引用取地址 == 对变量取地址
	   引用相当于变量取别名;并没有为引用开辟内存单元,它们占用同一个存储单元

2. 指针变量引用

#define _CRT_SECURE_NO_WARNINGS
#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("pause");
	return EXIT_SUCCESS;
}
#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);
	float& f2(float = 5);
	float a = f1();
	//float&b = f1();//不允许对临时变量进行引用
	float c = f2();//正确,变量c直接接收全局变量f的值
	float&d = f2();//正确,不另外定义变量,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*类型的形参,返回值是指向数组的,
	数组时具有5int元素的数组

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.内存访问违规

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
int main()
{
	char a;
	char *str1 = &a;
	char *str2 = (char*)"AAA";

	//char Arr[5] ={'H','e','l','l','o'};
	//strncpy(str1, Arr,5);
	//cout << str1 << endl;
	//str指向1个字节大小的内存区,复制"Hello"至少需要6个字节.所以会导致内存越界

	//strncpy(str1, "Hello",5);
	//cout << str1 << endl;
	//str指向1个字节大小的内存区,复制"Hello"至少需要6个字节.所以会导致内存越界

	//str2[0] = (char)"B";
	//cout << str2 << endl;
	//str2指向"AAA"这个字符串常量。因为是常量,所以对str2[0]的赋值操作是不合法的,也会导致程序崩溃
	system("pause");
	return 0;
}

14. 指针的隐式转换

不能对NULL进行解引用
编译时错误:不允许的隐式类型转换
运行时错误:访问野指针,或者对NULL进行解引用等

15. 指针常量与常量指针的区别

常量指
	const int* ptr;
	指向常量的指针,指针可以修改指向,但指针所指向的地址的内容是不可修改的
指针常量
	int* const  ptr;
	它首先是一个常量,然后它才是一个指针
	指针是一个常量,不能修改这个指针所指向的地址,一开始初始化指向哪儿,它就只能指向哪儿
	就像一个数组的数组名一样,是一个固定的指针
	但是注意:这个指针指向的地址里的内容是可以修改的
	指针是常量,不可以改变指针的指向(即 指针本身不可以修改),但指针指向的内容可以修改

18. this指针的正确叙述

类的非静态成员函数才有this指针

19. this指针

一个类的成员函数只有一份,类的所有对象共用这个成员函数的函数体

成员函数之所以能把属于此类的各个对象的数据区分开,就在于每次执行类成员函数时,会把当前对象的this指针(对象首地址)传入成员函数,函数体内所有对类数据成员的访问,都会转化为this->数据成员的方式

20. 指针数组与数组指针的区别

指针数组:表示它是一个数组,数组中的每一个元素是指针(每个指针,都在栈上分配了内存)
数组指针:表示它是一个指针,指针指向了一个数组

22. 函数指针与指针函数的区别

指针函数
	带指针的函数,本质是一个函数,返回的是某一类型的指针
	指针函数是返回指针类型的函数
	
	每一个函数,本身都有一个入口地址,该地址相当于一个指针。
		比如函数返回一个整型值,实际上也相当于返回一个指针变量的值,
		只不过这时的变量是函数本身而已,而整个函数相当于一个“变量”
		
函数指针
	本质是个指针,只不过该指针变量指向函数,可用该指针变量调用函数
	函数指针是指向函数地址的指针

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指针			cont int*p;
指向const的指针		int* const p;
指向constconst指针	const int* const p;
  1. 函数指针的使用
  2. 函数指针的使用

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
	3new/delete能完成上述任务
	4new运算符能够完成动态内存分配和初始化工作,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”
	避免内存泄漏、野指针的产生,是良好的编程习惯
  1. 动态内存的 才传递

36. “野指针”用于变量值的互换

“野指针”不能用于变量值的互换
	必须对指针进行初始化,才能对指针指向的内容进行赋值,否则会导致程序运行时崩溃

37. 内存的分配方式有几种

静态存储区
	在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,例如全局变量
栈区
	执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放
堆区
	动态内存,在程序运行的时候用malloc或new申请,程序员自己负责在合适用free或delete释放内存

38. 什么是句柄

Windows环境中,句柄是用来标识项目的
	Windows程序中并不是用物理地址来表示一个内存块、文件、任务或动态装入模块的,
	相反,WINDOWSAPI给这些项目分配确定的句柄,并将句柄返回给应用程序,然后通过句柄来进行操作

句柄地址(稳定)(记载着对象在内存中的地址)-->对象在内存中的地址(不稳定)(实际对象物理内存地址)
	注意:程序每次重新启动,系统不能保证分配给这个程序的句柄还是原来的那个句柄,而且绝大多数情况的确是不一样的

39. 指针与句柄有什么区别

对于Windows句柄的理解及其与一般指针的区别
	
1、指针对应着一个数据在内存中的地址,得到了指针就可以自由地修改该数据,
2、Windows并不希望一般程序修改其内部数据结构,因为只有不太安全。
  所以Windows给每个GlobalAlloc等函数声明的内存区域指定一个句柄,句柄是一种指向指针的指针

句柄和指针都是地址,不同之处在于:
	1、句柄所指的可以是一个很复杂的结构,并且很有可能与系统有关的。
	  比如说线程的句柄,它所指的就是一个类或者结构,它和系统有很密切的关系。
	  当一个线程由于不可预料的原因而终止时,系统就可以返回它所占用的资料,比如CPU、内存等。
	  反过来想可以知道,这个句柄中的某一项是与系统进行交互的。
	  由于Windows系统是一个多任务的系统,它随时都可能要分配内存、回收内存、重组内存。
	2、指针也可以指向一个复杂的结构,但是通常是由用户定义的,所以必需的工作都要用户完成,特别是在删除的时候。
发布了12 篇原创文章 · 获赞 10 · 访问量 7169

猜你喜欢

转载自blog.csdn.net/liangwenhao1108/article/details/104507231
今日推荐