笔试面试题目4(基础知识)

1. 指向类成员函数的指针:

首先 函数指针是指向一组同类型的函数的指针;而类成员函数我们也可以相似的认为,它是指向同类中同一组类型的成员函数的指针,当然这里的成员函数更准确的讲应该是指非静态的成员函数。


函数指针实例:

typedef int (*p)(int,int);	//定义一个接受两个int型且返回int型变量的函数指针类型
int func(int x,int y)
{
	printf("func:x=%d,y=%d/n",x,y);
	return (x<y?x:y);
}

int main()
{
	p fun=func;	//定义函数指针并给它赋上一个函数指针
	cout<<"min:"<<(*fun)(4,5)<<endl;	//为什么*fun需要用()扩起来呢?因为*的运算符优先级比()低,如果不用()就成了*(fun())
	return 0;
}
"指向类成员函数的指针"却多了一个类的区别:
class A
{
public:
	int func(int x,int y)
	{
		printf("A::func:x=%d,y=%d/n",x,y);
		return (x<y?x:y);
	}
};
typedef int (A::*p)(int,int);	//指针名前一定要加上所属类型类名 A::的限定


int main()
{
	p fun=&A::func;
	A a;                  // 因为成员函数地址的解引用必须要附驻与某个对象的地址,所以我们必须创建某个对象。
	cout<<"min:"<<(a.*fun)(4,5)<<endl;
	return 0;
}

接下来 我们可以再扩展一下下:
#include <tchar.h>
#include <iostream>
#include <stdio.h>
using namespace std;

class A
{
public:
	int func1(int x,int y)
	{
		printf("A::func:x=%d,y=%d/n",x,y);
		return (x<y?x:y);
	}
	virtual int func2(int x,int y)
	{
		printf("A::func:x=%d,y=%d/n",x,y);
		return (x>y?x:y);
	}
};
class B: public A
{
public:
	virtual int func2(int x,int y)
	{
		printf("B::func:x=%d,y=%d/n",x,y);
		return (x+y);
	}
};
typedef int (A::*p)(int,int);	//指针名前一定要加上所属类型类名 A::的限定
typedef int (B::*p0)(int,int);

int main()
{
	A a;  //因为成员函数地址的解引用必须要附驻与某个对象的地址,所以我们必须创建某个对象。
	B b;
	p fun=&A::func1;
	cout<<(a.*fun)(4,5)<<endl;
	cout<<(b.*fun)(4,5)<<endl<<endl;

	fun=&A::func2;
	cout<<(a.*fun)(4,5)<<endl;//请注意这里调用的是虚函数,嘿嘿 还真神奇 类成员函数指针也支持多态。
	cout<<(b.*fun)(4,5)<<endl<<endl;
	
	//fun = &B::func2;         //这样式错误滴,因为不存在派生类的"指向类成员函数的指针"到基类的"指向类成员函数的指针"的隐式转换
	fun=(int (A::*)(int,int))&B::func2;//应该进行强制转换 
	cout<<(a.*fun)(4,5)<<endl; 
	cout<<(b.*fun)(4,5)<<endl<<endl;
 
	p0 fun0=&B::func2;
	cout<<(a.*fun)(4,5)<<endl;
	cout<<(b.*fun)(4,5)<<endl<<endl;
 
	fun0 = &A::func2;           //正确,因为这里进行了隐式转换
	cout<<(a.*fun)(4,5)<<endl;
	cout<<(b.*fun)(4,5)<<endl<<endl;

	//从上面我们不难发现 指向类成员函数的指针基类和派生类的关系和指向类对象的指针基类和派生类的关系完全相反,
	//基类成员函数的布局被认为是派生类成员函数布局的一个子集
	return 0;
}

// 运行结果:
A::func:x=4,y=5
4
A::func:x=4,y=5
4

A::func:x=4,y=5
5
B::func:x=4,y=5
9

A::func:x=4,y=5
5
B::func:x=4,y=5
9

A::func:x=4,y=5
5
B::func:x=4,y=5
9

A::func:x=4,y=5
5
B::func:x=4,y=5
9

2. 指向成员变量的指针  转载:http://blog.csdn.net/oowgsoo/article/details/1533827

        "指向类成员变量的指针"这个术语中包含了"类成员变量"的术语,但是严格的说,这里的成员变量只是指非静态成员变量;这个术语中还包含了"指针"这个术语,但是严格的说,它即不包含地址,行为也不象指针。说得干脆点,那就是"指向类成员变量的指针"并非指针。尽管这个术语有很大的迷惑性,但是就其含义来说,可以把一组同类型的变量抽象为一个"指向变量的指针",同样的道理可以把一组类中同类型的类成员变量抽象为一个"指向类成员变量的指针",两者是一致的。

如果你已经熟悉常规指针的声明语法,那么声明一个"指向类成员变量的指针"并不复杂:
        int *ip; //一个指向int变量的指针
        int C::*ip; //一个指向C类中int成员变量的指针

你必须要做的就是多写一个classname::来限定这个指针到底指向哪个类一个常规的指针包含一个地址.如果解引用该指针,就会得到该地址的对象:
        int a = 12;
        int *ip;
        ip = &a;
        *ip = 0;
        a = *ip;

        但是一个"指向类成员变量的指针"并不包含一个地址,简单点说,实际上它是一个成员变量在类中的偏移量。当然,严格点说因为C++标准并为对"指向类成员变量的指针"如何实现做任何规定,说"指向类成员变量的指针"是一个整数的偏移量就不是一定正确。但是,大多数编译器确实是这样做的,下面我们看看"指向类成员变量的指针"是如何使用的?

#include "stdafx.h"
struct CPoint
{
	double x;
	double y;
};


void Print(CPoint* point, double CPoint::* p)
{
	printf("%f/n", point->*p);
}


int main(int argc, char* argv[])
{
	CPoint pt;
	pt.x = 10;
	pt.y = 20;
	
	double CPoint::* p = NULL;
	p = &CPoint::x;
	double x = pt.*p;
	Print(&pt, p);
 
	int offset = (int&)p;
	return 0;
}
double CPoint::* p = NULL;
        这是"指向类成员变量的指针"的声明,只是多了一个CPoint::而已,这个指针指向CPoint类中double类型的成员变量。
p = &CPoint::x;
        这是"指向类成员变量的指针"的赋值,记住,它是一个偏移量而不是地址,因此必须用这种静态的写法,这里不能用有地址的对象pt来赋值。
double x = pt.*p;
        这是"指向类成员变量的指针"的解引用,记住,解引用必须有实际的地址,因为必须用有地址的对象pt来解引用,.*的语法有些怪异,不过我宁愿把它拆解为pt.和*p两部分来理解。
printf("%f/n", point->*p);
        这也是"指向类成员变量的指针"的解引用, 和.*同样的道理,如果我们有一个指向CPoint的指针,我们就必须使用->*来解引用,你也可以把->*拆解为point->和*p两部分来理解。
int offset = (int&)p;
        这里把"指向类成员变量的指针"直接转换为int,这里的offset=8,恰好是CPoint类中的成员变量y在类中的偏移量,验证了我们说的"指向类成员变量的指针"是一个偏移量的说法
不过,我还是忍不住奉劝各位,尽量不要直接使用这个偏移量,这毕竟是编译器内部实现的细节,实在有太多的人喜欢这种黑客似的代码并四处炫耀,真正的"指向类成员变量的指针"的用法只应该包括声明,赋值和解引用

加上派生又如何?
#include "stdafx.h"
struct CPoint
{
 double x;
 double y;
};

struct CPoint3d : public CPoint
{
	double z;
};

void Print(CPoint* point, double CPoint::* p)
{
	printf("%f/n", point->*p);
}

int main(int argc, char* argv[])
{
	CPoint pt;
	pt.x = 10;
	pt.y = 20;

	double CPoint::* p = NULL;
	p = &CPoint::x;
	double x = pt.*p;
	Print(&pt, p);

	p = &CPoint::y;
	double y = pt.*p;
	Print(&pt, p);

	int offset = (int&)p;

	double CPoint3d::* p3d = NULL;
	p3d = &CPoint3d::z;

	offset = (int&)p3d;

	p3d = p; //正确
	//p = p3d; //错误
	p = (double CPoint::*)p3d; //强制转换

	CPoint3d pt3d;
	pt3d.z = 30;
	Print(&pt3d, (double CPoint::*)p3d);

	return 0;
}

offset = (int&)p3d;
        这里的offset=16,正是CPoint3d中的成员变量z在类中的偏移量,有没有人说offset=0的,重新去看看C++的对象模型。
p3d = p; // 正确
        存在基类的"指向类成员变量的指针"到派生类的"指向类成员变量的指针"的隐式转换,其含义无疑是说基类的成员变量偏移量只是派生类中成员变量偏移量的一个子集,因此这样的转换应该是没有问题的,但是反过来呢?
 //p = p3d; //错误
        不存在派生类的"指向类成员变量的指针"到基类的"指向类成员变量的指针"的隐式转换,因为派生类中的成员变量并不一定能够在基类中找到"指向类成员变量的指针"基类和派生类的关系和"指向类对象的指针"基类和派生类的关系完全相反,就"指向类成员变量的指针"的本质来说,这是合理的,但是这样的话,我们就无法利用公共的Print函数了,除非...

p = (double CPoint::*)p3d; //强制转换
我们做强制转换是可以的
CPoint3d pt3d;
pt3d.z = 30;
Print(&pt3d, (double CPoint::*)p3d);
        而且也只有强制转换才可以利用公共的Print函数了,这里的Print打印出来的是30,没有错误的,因为我们传入的是指向CPoint3d对象的指针,成员变量的偏移量也没有错误
但是是否一定要这样做呢?这取决于程序员自己的选择 

3. 概念理解
        在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式:
同步:
        所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事。
        例如普通B/S模式(同步):提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事
异步:
        异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。
        例如 ajax请求(异步): 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕
阻塞
        阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。
        有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。 例如,我们在socket中调用recv函数,如果缓冲区中没有数据,这个函数就会一直等待,直到有数据才返回。而此时,当前线程还会继续处理各种各样的消息。
非阻塞
        非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

对象的阻塞模式和阻塞函数调用
        对象是否处于阻塞模式和函数是不是阻塞调用有很强的相关性,但是并不是一一对应的。阻塞对象上可以有非阻塞的调用方式,我们可以通过一定的API去轮询状 态,在适当的时候调用阻塞函数,就可以避免阻塞。而对于非阻塞对象,调用特殊的函数也可以进入阻塞调用。函数select就是这样的一个例子。

        1. 同步,就是我调用一个功能,该功能没有结束前,我死等结果。
        2. 异步,就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知)
        3. 阻塞,就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。
        4. 非阻塞,就是调用我(函数),我(函数)立即返回,通过select通知调用者

        同步IO和异步IO的区别就在于:数据拷贝的时候进程是否阻塞!
        阻塞IO和非阻塞IO的区别就在于:应用程序的调用是否立即返回!

对于举个简单c/s 模式:
        同步:提交请求->等待服务器处理->处理完毕返回这个期间客户端浏览器不能干任何事
        异步:请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕
        同步和异步都只针对于本机SOCKET而言的。
        同步和异步,阻塞和非阻塞,有些混用,其实它们完全不是一回事,而且它们修饰的对象也不相同。
        阻塞和非阻塞是指当进程访问的数据如果尚未就绪,进程是否需要等待,简单说这相当于函数内部的实现区别,也就是未就绪时是直接返回还是等待就绪;

        而同步和异步是指访问数据的机制,同步一般指主动请求并等待I/O操作完毕的方式,当数据就绪后在读写的时候必须阻塞(区别就绪与读写二个阶段,同步的读写必须阻塞),异步则指主动请求数据后便可以继续处理其它任务,随后等待I/O,操作完毕的通知,这可以使进程在数据读写时也不阻塞。(等待"通知")

4. C++中指针和引用的区别                  转自:http://www.cnblogs.com/kingln/articles/1129114.html

        从概念上讲。指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变。而引用是一个别名,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的(自始至终只能依附于同一个变量)。
        在C++中,指针和引用经常用于函数的参数传递,然而,指针传递参数和引用传递参数是有本质上的不同的:
        指针传递参数本质上是值传递的方式,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。
        而在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
        引用传递和指针传递是不同的,虽然它们都是在被调函数栈空间上的一个局部变量,但是任何对于引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。而对于指针传递的参数,如果改变被调函数中的指针地址,它将影响不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量,那就得使用指向指针的指针,或者指针引用。

        为了进一步加深大家对指针和引用的区别,下面我从编译的角度来阐述它们之间的区别:
        程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变其指向的对象(指针变量中的值可以改),而引用对象则不能修改。

最后,总结一下指针和引用的相同点和不同点:
★ 相同点:
        ● 都是地址的概念: 指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。
★ 不同点:
        ● 指针是一个实体,而引用仅是个别名;
        ● 引用只能在定义时被初始化一次,之后不可变;指针可变;引用“从一而终”,指针可以“见异思迁”;
        ● 引用没有const,指针有const,const的指针不可变;
        ● 引用不能为空,指针可以为空;
        ● “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小;
        ● 指针和引用的自增(++)运算意义不一样;
        ● 引用是类型安全的,而指针不是 (引用比指针多了类型检查)


修改 By Andy  @ 2013年10月24日

你可能感兴趣的:(笔试面试题目4(基础知识))