[C++面试题]之指针与引用(2)

1、下面这个程序测试会有什么结果?

#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中,编译器总是要为函数的每个参数制作临时副本,在本例中,void GetMemory(char *p , int num)中的*p实际上是主函数中str的一个副本,而在函数GetMemory中只是把p所指向的内存地址改变了,但是str丝毫未变,因为函数GetMemory没有返回值,因此str并不指向p所申请的那段内存,所以函数GetMemory并不能输出任何东西,如下图所示。事实上,每次执行一次GetMemory就会申请一块内存,但是申请的内存却不能有效释放,结果是内存一直被独占,最终造成内存泄露。

1

 

如果一定要用指针去申请内存,那么应该采用指向指针的指针,传str 的地址给函数GetMemory。代码如下:

#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");

	cout << *str << endl;
	cout << str << endl;
	cout << &str << endl;

	return 0;
}

这样的话程序就可以运行成功了,我们分别打印 *str 、 str 、 &str 可以发现,结果分别是 h 、 hello 、 0024FA80 。str就是字符串的值;*str 是字符串首字符,&str 就是字符串的地址值。

当然也可以用函数返回值来传递动态内存。这种方法更简单,代码如下:

#include<iostream>
using namespace std;

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");

	cout << *str << endl;
	cout << str << endl;
	cout << &str << endl;

	return 0;
}

我们可以对这道题推而广之,看一下整型变量是如何传值的,代码如下:

#include<iostream>
using namespace std;

void GetMemory1(int *num)
{
	*num=5;
}

int main ()
{
	int a;
	GetMemory1(&a);
	cout << a << endl;
	return 0;
}

GetMemory1把 a 的地址传了进来,*num 是地址里的值,是 a 的副本.通过直接修改地址里的值,不需要有返回值,也把 a 给修改了,因为 a 所指向的地址的值发生了改变.

答案:

程序崩溃.因为GetMemory 并不能传递动态内存,主函数中的 str 一直是NULL。

 

2、写出下面程序运行的结果。

#include<iostream>
using namespace std;

int main ()
{
	int a[3];
	a[0]=0; a[1]=1; a[2]=2;
	int *p , *q;
	p=a;
	q=&a[2];
	cout << a[q-p] <<endl;
}

      解析:本程序的结构如下:

(1)先声明了一个整型数组a[3],然后分别给数组赋值。

(2)又声明了两个整型指针 p、q,但是并没有定义这两个指针所指向的地址。

(3)使整型指针 p 的地址指向 a(注意 a 就是a[0]),使整型指针 q 的地址指向 a[2]。

     可实际验证程序如下:

#include<iostream>
using namespace std;

int main ()
{
	int a[3];
	a[0]=0; a[1]=1; a[2]=2;
	int *p , *q;
	p=a;
	cout << p <<endl;
	cout << *p <<endl;

	q=&a[2];
	cout << q <<endl;
	cout << *q <<endl;
	cout << a[q-p] <<endl;
}

     上面的输出结果分别是:

     002DFD24

     0

     002DFD2C

     2

     2

     2

     q 的实际地址是 002DFD2C,p 的实际地址是 002DFD24。 002DFD2C-002DFD24=0x08(十六进制减法),相差是 8。

     q-p的实际运算是(q的地址值(002DFD2C)- p的地址值(002DFD24))/sizeof(int),即结果为 2 。

答案:

运行结果是 2 。

 

3、请问下面的代码的输出结果是多少?

#include<iostream>
using namespace std;

class A
{
public:
	A()
	{
		m_a = 1;
		m_b = 2;
	}

	~A(){};

	void fun()
	{
		printf("%d%d",m_a,m_b);
	}
private:
	int m_a;
	int m_b;

};

class B
{
public:
	B()
	{
		m_c=3;
	}

	~B();

	void fun()
	{
		printf("%d",m_c);
	}
private:
	int m_c;
};

void main ()
{
	A a;
	B *p = (B *)(&a);
	p->fun();
}

解析:首先可以肯定的是上面的代码是非常槽糕的,无论是可读性还是安全性都很差。写这种代码的人,按照Jarne Stroustrup(C++标志化制定者)的说法,应该“斩立决”。

但是不得不说这也是一道很好考察你对内存偏移的理解的题:

     B *p = (B *)(&a);

     这是一个野蛮的转化,强制把 a 地址内容看成是一个B类对象,p 指向的是 a 类的内存空间。

     B类只有一个元素m_c 但是 A类的内存空间存放第一个元素的位置是 m_a, p指向的是对象的内存首地址,比如:0x22ff58,但p->fun()调用B::fun()来打印m_c时,编译器对m_c的认识就是m_c距离对象的偏移量是 0,于是打印了对象A首地址的偏移量 0x22ff58+0变量值,即就是m_a的值1.

答案:

运行结果是:1

 

4、下面程序输出结果是什么?

#include<iostream>
using namespace std;

class A
{
public:
	int m_a;
	A()
	{
		m_a = 1;
	}

	void print ()
	{
		printf("%d",m_a);
	}
};

class B : public A
{
public:
	int m_a;
	B()
	{
		m_a = 2;
	}
};

int main ()
{
	B b;
	b.print();
	printf("%d\n",b.m_a);
}

解析:B类中的 m_a 把 A 类中的 m_a 覆盖掉了。在构造 B 类时,先调用 A 类的构造函数,所以 A 类中的 m_a 是1,b.print()打印的是 A 类中的    m_a ,而B类中的 m_a 是2。

答案:

12

 

5、C++中有了 malloc/free ,为什么还需要 new/delete 呢?

答案:

(1)malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。

(2)对于非内部数据类型的对象而言,只用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。

(3)因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new ,以及一个能完成清理与释放内存工作的运算符delete。new/delete 不是库函数而是运算符。

 

指针与引用部分算是写完了,还有很多没弄明白的,比如:指针函数、指向指针的指针也说得很少。可能上面的内容还有很多错误之处,欢迎指正…

posted @ 2011-11-04 14:42  it笨笨  阅读(3331)  评论(4编辑  收藏  举报