c/c++常见面试题02

1 两个字符串s、t,把t字符串插入到s字符串中,s字符串有足够的空间存放t字符串

void insert(char *s, char *t, int i)
{
	char *q = t;
	char *p =s;
	if(q == NULL)return;
	while(*p!='\0')
	{
		p++;
	}
	while(*q!=0)
	{
		*p=*q;
		p++;
		q++;
	}
	*p = '\0';
}

2 以下三条输出语句分别输出什么?

char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc"; 
const char str4[] = "abc"; 
const char* str5 = "abc";
const char* str6 = "abc";
cout << boolalpha << ( str1==str2 ) << endl; // 输出什么?
cout << boolalpha << ( str3==str4 ) << endl; // 输出什么?
cout << boolalpha << ( str5==str6 ) << endl; // 输出什么? 

答:分别输出false,false,true。str1和str2都是字符数组,每个都有其自己的存储区,它们的值则是各存储区首地址,不等;str3和str4同上,只是按const语义,它们所指向的数据区不能修改。str5和str6并非数组而是字符指针,并不分配存储区,其后的“abc”以常量形式存于静态数据区,而它们自己仅是指向该区首地址的指针,相等。

3 以下代码中的两个sizeof用法有问题吗?

void UpperCase( char str[] ) // 将 str 中的小写字母转换成大写字母
{
	for( size_t i=0; if( 'a'<=str[i] && str[i]<='z' )
	str[i] -= ('a'-'A' );
}
char str[] = "aBcDe";
cout << "str字符长度为: " << sizeof(str)/sizeof(str[0]) << endl;
UpperCase( str );
cout << str << endl;

答:函数内的sizeof有问题。根据语法,sizeof如用于数组,只能测出静态数组的大小,无法检测动态分配的或外部数组大小。函数外的str是一个静态定义的数组,因此其大小为6,函数内的str实际只是一个指向字符串的指针,没有任何额外的与数组相关的信息,因此sizeof作用于上只将其当指针看,一个指针为4个字节,因此返回4。

4、非C++内建型别 A 和 B,在哪几种情况下B能隐式转化为A?
答:
a. class B : public A { ……} // B公有继承自A,可以是间接继承的
b. class B { operator A( ); } // B实现了隐式转化为A的转化
c. class A { A( const B& ); } // A实现了non-explicit的参数为B(可以有其他带默认值的参数)构造函数
d. A& operator= ( const A& ); // 赋值操作,虽不是正宗的隐式类型转换,但也可以勉强算一个

5、以下代码有什么问题?

struct Test
{
	Test( int ) {}
	Test() {}
	void fun() {}
};
void main( void )
{
	Test a(1);
	a.fun();
	Test b();
	b.fun();
}

答:变量b定义出错。按默认构造函数定义对象,不需要加括号。

6 以下代码有什么问题?

cout << (true?1:1) << endl;

答:三元表达式“?:”问号后面的两个操作数必须为同一类型。

7 以下代码能够编译通过吗,为什么?

unsigned int const size1 = 2;
char str1[ size1 ];
unsigned int temp = 0;
cin >> temp;
unsigned int const size2 = temp;
char str2[ size2 ];

答:str2定义出错,size2非编译器期间常量,而数组定义要求长度必须为编译期常量。

8 以下反向遍历array数组的方法有什么错误?

vector array;
array.push_back( 1 );
array.push_back( 2 );
array.push_back( 3 );
for( vector::size_type i=array.size()-1; i>=0; --i ) // 反向遍历array数组
{
cout << array[i] << endl;
}

答:首先数组定义有误,应加上类型参数:vector array。其次vector::size_type被定义为unsigned int,即无符号数,这样做为循环变量的i为0时再减1就会变成最大的整数,导致循环失去控制。

9 以下代码中的输出语句输出0吗,为什么?

struct CLS
{
	int m_i;
	CLS( int i ) : m_i(i) {}
	CLS()
}
CLS(0);
{
};
CLS obj;
cout << obj.m_i << endl; //没有带参数,调用的是默认构造函数CLS()

答:不能。在默认构造函数内部再调用带参的构造函数属用户行为而非编译器行为,亦即仅执行函数调用,而不会执行其后的初始化表达式。只有在生成对象时,初始化表达式才会随相应的构造函数一起调用。

10 C++中的空类,默认产生哪些类成员函数?

class Empty
{
public:
	Empty(); // 缺省构造函数
	Empty( const Empty& ); // 拷贝构造函数
	~Empty(); // 析构函数
	Empty& operator=( const Empty& ); // 赋值运算符
	Empty* operator&(); // 取址运算符
	const Empty* operator&() const; // 取址运算符 const
};

11 以下两条输出语句分别输出什么?

float a = 1.0f;
cout << (int)a << endl;
cout << (int&)a << endl;
cout << boolalpha << ( (int)a == (int&)a ) << endl; // 输出什么?
float b = 0.0f;
cout << (int)b << endl;
cout << (int&)b << endl;
cout << boolalpha << ( (int)b == (int&)b ) << endl; // 输出什么?

答:分别输出false和true。注意转换的应用。(int)a实际上是以浮点数a为参数构造了一个整型数,该整数的值是1,(int&)a则是告诉编译器将a当作整数看(并没有做任何实质上的转换)。因为1以整数形式存放和以浮点形式存放其内存数据是不一样的,因此两者不等。对b的两种转换意义同上,但是0的整数形式和浮点形式其内存数据是一样的,因此在这种特殊情形下,两者相等(仅仅在数值意义上)。

注意,程序的输出会显示(int&)a=1065353216,这个值是怎么来的呢?前面已经说了,1以浮点数形式存放在内存中,按ieee754规定,其内容为0x0000803F(已考虑字节反序)。这也就是a这个变量所占据的内存单元的值。当(int&)a出现时,它相当于告诉它的上下文:“把这块地址当做整数看待!不要管它原来是什么。”这样,内容0x0000803F按整数解释,其值正好就是1065353216(十进制数)。

通过查看汇编代码可以证实“(int)a相当于重新构造了一个值等于a的整型数”之说,而(int&)的作用则仅仅是表达了一个类型信息,意义在于为cout<<及==选择正确的重载版本。

12 以下代码有什么问题?

typedef vector IntArray;
IntArray array;
array.push_back( 1 );
array.push_back( 2 );
array.push_back( 2 );
array.push_back( 3 );
// 删除array数组中所有的2
for( IntArray::iterator itor=array.begin(); itor!=array.end(); ++itor )
{
if( 2 == *itor ) array.erase( itor );
}

答:同样有缺少类型参数的问题。另外,每次调用“array.erase( itor );”,被删除元素之后的内容会自动往前移,导致迭代漏项,应在删除一项后使itor–,使之从已经前移的下一个元素起继续遍历。
13 写一个函数,完成内存之间的拷贝。[考虑问题是否全面]
答:

void* mymemcpy( void *dest, const void *src, size_t count )
{
     char* pdest = static_cast<char*>( dest );
     const char* psrc = static_cast<const char*>( src );
     if( pdest>psrc && pdest<psrc+count )
/*这种情况的话(把内存区域想象成一个坐标轴的横轴)就是目标地址在源地址的后面,
有可能目标地址会和源地址发生重复,就是说假设dest[0]的内容就是src[5]的内容,
那么从前向后复制的话,那src[0]的内容复制到了dest[0]里面去了,也就修改了src[5]
的内容,造成src内存区域的内容破坏了。就只能从后向前复制。以下考虑的情况都可以
这样的去想。*/
     {
         for( size_t i=count-1; i!=-1; --I )
                 pdest[i] = psrc[i];
     }
     else
     {
         for( size_t i=0; i<count; ++I )
             pdest[i] = psrc[i];
     }
     return dest;
}
int main( void )
{
     char str[] = "0123456789";
     mymemcpy( str+1, str+0, 9 );
     cout << str << endl;

     system( "Pause" );
     return 0;
}

14 .实现双向链表删除一个节点P,在节点P后插入一个节点,写出这两个函数;
答:

//假设线性表的双向链表存储结构
typedef struct DulNode{
struct DulNode *prior; //前驱指针
ElemType data; //数据
struct DulNode *next; //后继指针
}DulNode,*DuLinkList;
//删除操作
Status ListDelete_DuL(DuLinkList &L,int i,ElemType &e)
{
	if(!(p=GetElemP_DuL(L,i))) //此处得到i位置的节点指针,如果有需要也得写出具体函数实现
		return ERROR;
	e=p->data;
	p->prior->next=p->next;
	p->next->prior=p->pror;
	free(p);
	return OK;
}
//插入操作
Status ListInsert_DuL(DuLinkList &L,int i,ElemType &e)
{
	if(!(p=GetElemP_DuL(L,i)))
		return ERROR;
	if(!(s=(DuLinkList)malloc(sizeof(DuLNode)))) 
		return ERROR;
		
	s->data=e;
	s->prior=p->prior;
	p->prior->next=s;
	s->next=p;
	p->prior=s;
	return OK;
}

15 写一个函数,将其中的\t都转换成4个空格。
答:该函数命名为convert,参数的意义为:*strDest目的字符串,*strSrc源字符串,length源字符串的长度
函数实现为:

char* convert(char *strDest, const char *strSrc,int length)
{
	char * cp = strDest;
	int i=0;
	while(*strSrc && i{
	if (*strSrc=='\t') //将\t转换成4个空格
	{
		for(int j=0;j<4;j++)
		*cp++=' ';
	}
	else //否则直接拷贝 
	{
		*cp++=*strSrc;
		strSrc++;
		i++;
	}
	return strDest;
}

16.Windows程序的入口是哪里?写出Windows消息机制的流程。
答:
Windows程序的入口是WinMain函数
消息机制:系统将会维护一个或多个消息队列,所有产生的消息都会被放入或是插入队列中。系统会在队列中取出每一条消息,根据消息的接收句柄而将该消息发送给拥有该窗口的程序的消息循环。每一个运行的程序都有自己的消息循环,在循环中得到属于自己的消息并根据接收窗口的句柄调用相应的窗口过程。而在没有消息时消息循环就将控制权交给系统。

17 如何定义和实现一个类的成员函数为回调函数?
答:
所谓的回调函数,就是预先在系统的对函数进行注册,让系统知道这个函数的存在,以后,当某个事件发生时,再调用这个函数对事件进行响应。
定义一个类的成员函数时在该函数前加CALLBACK即将其定义为回调函数,函数的实现和普通成员函数没有区别

18 C++里面是不是所有的动作都是main()引起的?如果不是,请举例。
答:不是,比如中断引起的中断处理不是直接由main()引起的,而是由外部事件引起的。

19 C++里面如何声明const void f(void)函数为C程序中的库函数?
答:在该函数前添加extern “C”声明

20 下列哪两个是等同的

int b; 
A const int* a = &b; 
B const* int a = &b; 
C const int* const a = &b; 
D int const* const a = &b;

答:
各式表示的意思分别为:
A const int* a = &b; //a是const,但指针a可变
B const int a = &b; //a是const,但a可变
C const int const a = &b; //a和a都是const,常量和指针的值都不能改变
D int const const a = &b; //a和*a都是const,常量和指针的值都不能改变
因此C,D两者是相同的。
总结个技巧:如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。

21 内联函数在编译时是否做参数类型检查?
答:做类型检查,因为内联函数就是在程序编译时,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体来代替。

22 某32位系统下, C++程序,请计算sizeof 的值

#include 
#include 
void Foo ( char str[100] )
{
	printf("sizeof(str)=%d \n", sizeof(str) );
}
main()
{
	char str[] = "www.ibegroup.com";
	char *p1 = str ;
	int n = 10;
	void *p2 = malloc( 100 );
	printf("sizeof(str)=%d \n", sizeof(str) );
	printf("sizeof(p1)=%d \n", sizeof(p1) );
	printf("sizeof(n)=%d \n", sizeof(n) );
	printf("sizeof(p2)=%d \n", sizeof(p2) );
	Foo(str);
}

答:(1)17 (2)4 (3) 4 (4)4 (5)4

23 回答下面的问题
(1) 头文件中的 ifndef/define/endif干什么用? 预处理
答:防止头文件被重复引用

(2) #include 和 #include “filename.h” 有什么区别?
答:
对于#include ,编译器从标准库路径开始搜索filename.h
对于#include “filename.h” ,编译器从用户的工作路径开始搜索filename.h

(3) 在C++ 程序中调用被 C 编译器编译后的函数,为什么要加 extern “C”声明?
答:函数和变量被C++编译后在符号库中的名字与C语言的不同,被extern "C"修饰的变量和函数是按照C语言方式编译和连接的。由于编译后的名字不同,C++程序不能直接调用C 函数。C++提供了一个C 连接交换指定符号extern“C”来解决这个问题。

24 回答下面的问题
(1) 请问运行Test 函数会有什么样的结果?

Void GetMemory(char **p, int num)
{
	*p = (char *)malloc(num);
}
void Test(void)
{
	char *str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}

答:输出“hello”

(2) 请问运行Test 函数会有什么样的结果?

void Test(void)
{
	char *str = (char *) malloc(100);
	strcpy(str, “hello”);
	free(str);
	if(str != NULL)
	{
		strcpy(str, “world”);
		printf(str);
	}
}

答:输出“world”

(3) 请问运行Test 函数会有什么样的结果?

char *GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
void Test(void)
{
	char *str = NULL;
	str = GetMemory();
	printf(str);
}

答:无效的指针,输出不确定

25 编写strcat函数
已知strcat函数的原型是char *strcat (char *strDest, const char *strSrc); 其中strDest 是目的字符串,strSrc 是源字符串。
(1)不调用C++/C 的字符串库函数,请编写函数 strcat
答:

char * __cdecl strcat (char * dst, const char * src)
{
	char * cp = dst;
	while( *cp )
		cp++; /* find end of dst */
	while( *cp++ = *src++ ) ; /* Copy src to end of dst */
	return( dst ); /* return dst */
}

(2)strcat能把strSrc 的内容连接到strDest,为什么还要char * 类型的返回值?
答:方便赋值给其他变量

26 程序什么时候应该使用线程,什么时候单线程效率高
(1) 耗时的操作使用线程,提高应用程序响应
(2) 并行操作时使用线程,如C/S架构的服务器端并发线程响应用户的请求。
(3) 多CPU系统中,使用线程提高CPU利用率
(4) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独
立的运行部分,这样的程序会利于理解和修改。
其他情况都使用单线程。

27 C++中什么数据分配在栈或堆中,New分配数据是在近堆还是远堆中?
答:栈: 存放局部变量,函数调用参数,函数返回值,函数返回地址。由系统管理堆: 程序运行时动态申请,new 和malloc申请的内存就在堆上

28 关于内存对齐的问题以及sizof()的输出
答:编译器自动对齐的原因:为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。

29 int i=10, j=10, k=3; k*=i+j; k最后的值是?
答:60,此题考察优先级,实际写成: k*=(i+j);,赋值运算符优先级最低

30 动态连接库的两种方式?
答:调用一个DLL中的函数有两种方法:
1.载入时动态链接(load-time dynamic linking),模块非常明确调用某个导出函数,使得他们就像本地函数一样。这需要链接时链接那些函数所在DLL的导入库,导入库向系统提供了载入DLL时所需的信息及DLL函数定位。
2.运行时动态链接(run-time dynamic linking),运行时可以通过LoadLibrary或LoadLibraryEx函数载入DLL。DLL载入后,模块可以通过调用GetProcAddress获取DLL函数的出口地址,然后就可以通过返回的函数指针调用DLL函数了。如此即可避免导入库文件了

31 函数模板与类模板有什么区别?
答:函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显式地指定。

32 Windows是内核级线程么。
答:见下一题

33 Linux有内核级线程么。
答:线程通常被定义为一个进程中代码的不同执行路线。从实现方式上划分,线程有两种类型:“用户级线程”和“内核级线程”。 用户线程指不需要内核支持而在用户程序中实现的线程,其不依赖于操作系统核心,应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制用户线程。这种线程甚至在象 DOS 这样的操作系统中也可实现,但线程的调度需要用户程序完成,这有些类似 Windows 3.x 的协作式多任务。另外一种则需要内核的参与,由内核完成线程的调度。其依赖于操作系统核心,由内核的内部需求进行创建和撤销,这两种模型各有其好处和缺点。用户线程不需要额外的内核开支,并且用户态线程的实现方式可以被定制或修改以适应特殊应用的要求,但是当一个线程因 I/O 而处于等待状态时,整个进程就会被调度程序切换为等待状态,其他线程得不到运行的机会;而内核线程则没有各个限制,有利于发挥多处理器的并发优势,但却占用了更多的系统开支。
Windows NT和OS/2支持内核线程。Linux 支持内核级的多线程

34 使用线程是如何防止出现大的波峰。
答:意思是如何防止同时产生大量的线程,方法是使用线程池,线程池具有可以同时提高调度效率和限制资源使用的好处,线程池中的线程达到最大数时,其他线程就会排队等候。

35 写出判断ABCD四个表达式是否正确, 若正确, 写出经过表达式中a的值
int a = 4;
(A) a += (a++);
(B) a += (++a);
(C)(a++) += a;
(D) (++a) += (a++);
a = ?
答:C错误,左侧不是一个有效变量,不能赋值,可改为(++a) += a;
改后答案依次为9,10,10,11

36 MFC中CString是类型安全类么?
答:不是,其它数据类型转换到CString可以使用CString的成员函数Format来转换

37 C++中为什么用模板类。
答:
(1) 可用来创建动态增长和减小的数据结构
(2) 它是类型无关的,因此具有很高的可复用性。
(3) 它在编译时而不是运行时检查数据类型,保证了类型安全
(4) 它是平台无关的,可移植性
(5) 可用于基本数据类型

38 CSingleLock是干什么的。
答:同步多个线程对一个数据类的同时访问

39 局部变量能否和全局变量重名?
答:能,局部会屏蔽全局。要用全局变量,需要使用"::"
局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内。

40 如何引用一个已经定义过的全局变量?
答:extern
可以用引用头文件的方式,也可以用extern关键字,如果用引用头文件方式来引用某个在头文件中声明的全局变理,假定你将那个变写错了,那么在编译期间会报错,如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错。

41 全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?
答:可以,在不同的C文件中以static形式来声明同名全局变量。
可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋初值,此时连接不会出错。

42 语句for( ;1 ;)有什么问题?它是什么意思?
答:无限循环,和while(1)相同。

43 do……while和while……do有什么区别?
答:前一个循环一遍再判断,后一个判断以后再循环。

44 请写出下列代码的输出内容

#include 
main()
{
int a,b,c,d;
a=10;
b=a++;
c=++a;
d=10*a++;
printf("b,c,d:%d,%d,%d",b,c,d);
return 0;
} 

答:10,12,120

一、判断题
1、有数组定义int a[2][2]={{1},{2,3}};则a[0][1]的值为0。(正确)

2、int (*ptr) (),则ptr是一维数组的名字。(错误 int (*ptr) ();定义一个指向函数的指针变量 )

3、指针在任何情况下都可进行>,<,>=,<=,==运算。(错误)

4、switch© 语句中c可以是int ,long,char ,float ,unsigned int 类型。( 错,不能用实形)

二、填空题
1、在windows下,写出运行结果

char str[ ]= "Hello";
char *p=str;
int n=10;
sizeof(str)=( )
sizeof(p)=( )`在这里插入代码片`
sizeof(n)=( )
void func(char str[100]){ }
sizeof(str)=( )

答案:6,4,4,4

2、运行test函数有什么结果?

void getmemory(char **p, int num)
{
	*p=(char *) malloc(num);
}
void test(void)
{ 
	char *str=NULL;
	getmemory(&str,100);
	strcpy(str,"hello");
	printf(str);
}

答案:输出hello,但是发生内存泄漏。

3、有什么结果?

//设
int arr[]={6,7,8,9,10};
int *ptr=arr;
*(ptr++)+=123;
printf("%d,%d", *ptr, *(++ptr));

答案:8,8。这道题目的意义不大,因为在不同的编译器里printf的参数的方向是不一样的,在vc6.0下是从右到左,这里先*(++ptr) 后*pt,于是结果为8,8

二、编程题
1、不使用库函数,编写函数int strcmp(char *source, char *dest)
相等返回0,不等返回-1;

int strcmp(char *source, char *dest)
{
	assert((source!=NULL)&&(dest!=NULL));
	int i,j;
	for(i=0; source[i]==dest[i]; i++)
	{
		if(source[i]=='\0' && dest[i]=='\0')
			return 0;
		else
			return -1;
	}
}
int strcmp(char *source, char *dest)
{
	while ( (*source != '\0') && (*source == *dest))
	{
		source++;
		dest++;
	}
	return ( (*source) - (*dest) ) ? -1 : 0;
}
int strcmp(const char *p1,const char * p2)
{
	int ret=0;
	while(!(ret=*p1-*p2) && *p2)
	{
		++p1;
		++p2;
	}
}

2、 写一函数int fun(char *p)判断一字符串是否为回文,是返回1,不是返回0,出错返回-1
答案:
一、

int fun(char *p)
{
	if(p==NULL)
		return -1;
	else
	{
		int length = 0;
		int i = 0;
		int judge = 1;
		length = strlen(p);
		for(i=0; i{
		if(p[i]!=p[length-1-i])
		judge = 0;
		break;
	}
	
	if(judge == 0)
		return 0;
	else
		return 1;
}

二、

int fun(char *p)
{ 
	int len = strlen(p) - 1; 
	char *q = p + len; 
	if (!p) 
	return -1; 
	while (p < q) 
	{
		if ((*p++) != (*q--)) 
		return 0; 
	} 
	return 1;
}

3、链表反转
单向链表的反转是一个经常被问到的一个面试题,也是一个非常基础的问题。比如一个链表是这样的:1->2->3->4->5通过反转后成为5->4->3->2->1。最容易想到的方法遍历一遍链表,利用一个辅助指针,存储遍历过程中当前指针指向的下一个元素,然后将当前节点元素的指针反转后,利用已经存储的指针往后面继续遍历。源代码如下:

struct linka { 
int data; 
linka* next; 
}; 
void reverse(linka*& head) 
{ 
	if(head ==NULL) 
		return; 
	linka *pre, *cur, *ne; 
	pre=head; 
	cur=head->next; 
	while(cur) 
	{ 
		ne = cur->next; 
		cur->next = pre; 
		pre = cur; 
		cur = ne; 
	} 
	head->next = NULL; 
	head = pre; 
} 

还有一种利用递归的方法。这种方法的基本思想是在反转当前节点之前先调用递归函数反转后续节点。源代码如下。不过这个方法有一个缺点,就是在反转后的最后一个结点会形成一个环,所以必须将函数的返回的节点的next域置为NULL。因为要改变head指针,所以我用了引用。算法的源代码如下:

linka* reverse(linka* p,linka*& head) 
{ 
	if(p == NULL || p->next == NULL) 
	{ 
		head=p; 
		return p; 
	} 
	else 
	{ 
		linka* tmp = reverse(p->next,head); 
		tmp->next = p; 
		return p; 
	} 
} 

4、已知String类定义如下:

class String
{
	public:
	String(const char *str = NULL); // 通用构造函数
	String(const String &another); // 拷贝构造函数
	~ String(); // 析构函数
	String & operater =(const String &rhs); // 赋值函数
	private:
	char *m_data; // 用于保存字符串
};

尝试写出类的成员函数实现。
答案:

String::String(const char *str)
{
	if ( str == NULL ) //strlen在参数为NULL时会抛异常才会有这步判断
	{
		m_data = new char[1] ;
		m_data[0] = '\0' ;
	}
	else
	{
		m_data = new char[strlen(str) + 1];
		strcpy(m_data,str);
	}
}

String::String(const String &another)
{
	m_data = new char[strlen(another.m_data) + 1];
	strcpy(m_data,other.m_data);
}

String& String::operator =(const String &rhs)
{
	if ( this == &rhs)
		return *this ;
	delete []m_data; //删除原来的数据,新开一块内存
	m_data = new char[strlen(rhs.m_data) + 1];
	strcpy(m_data,rhs.m_data);
	return *this ;
}

String::~String()
{
	delete []m_data ;
}

经典流传的c++笔试题汇总

1.求下面函数的返回值(微软)

int func(x)
{
	int countx = 0;
	while(x)
	{
		countx ++;
		x = x&(x-1);
	}
	return countx;
}

假定x = 9999。 答案:8
思路:将x转化为2进制,看含有的1的个数。

2 什么是“引用”?申明和使用“引用”要注意哪些问题?
答:引用就是某个目标变量的“别名”(alias),对应用的操作与对变量直接操作效果完全相同。申明
一个引用的时候,切记要对其进行初始化。引用声明完毕后,相当于目标变量名有两个名称,即该目标原
名称和引用名,不能再把该引用名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只
表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也
不给引用分配存储单元。不能建立数组的引用。

3.将“引用”作为函数参数有哪些特点?
(1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分
配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

4.在什么时候需要使用“常引用”? 
如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。
常引用声明方式:const 类型标识符 &引用名=目标变量名;
例1

int a ;
const int &ra=a;
ra=1; //错误
a=1; //正确

例2

string foo( );
void bar(string & s);
//那么下面的表达式将是非法的:
bar(foo( ));
bar("hello world");

原因在于foo( )和"hello world"串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。引用型参数应该在能被定义为const的情况下,尽量定义为const 。

5.将“引用”作为函数返回值类型的格式、好处和需要遵守的规则?
格式:类型标识符 &函数名(形参列表及类型说明){ //函数体 }
好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error!

注意事项:
(1)不能返回局部变量的引用。这条可以参照Effective C++[1]的Item 31。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。
(2)不能返回函数内部new分配的内存的引用。这条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
(3)可以返回类成员的引用,但最好是const。这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
(4)流操作符重载返回值申明为“引用”的作用:
流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << “hello” << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。赋值操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以继续赋值。因此引用成了这个操作符的惟一返回值选择。

#include<iostream>
using namespace std;

int &put(int n);
int vals[10];
int error=-1;

void main()
{
	put(0)=10; //以put(0)函数值作为左值,等价于vals[0]=10;
	put(9)=20; //以put(9)函数值作为左值,等价于vals[9]=20;
	cout<cout<}
	int &put(int n)
	{
	if (n>=0 && n<=9 ) 
		return vals[n];
	else 
	{ 
		cout<<"subscript error"; 
		return error; 
	}
}

(5)在另外的一些操作符中,却千万不能返回引用:±*/四则运算符。它们不能返回引用,Effective C++[1]的Item23详细的讨论了这个问题。主要原因是这四个操作符没有side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则,第2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。

6.“引用”与多态的关系?
引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。
例4

Class A; 
Class B : 
Class A{}; 
B b; 
A& ref = b;

7.“引用”与指针的区别是什么?
指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。此外,就是上面提到的对函数传ref和pointer的区别。

8.什么时候需要“引用”?
流操作符<<和>>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、其它情况都推荐使用引用。
以上 2-8 参考:http://blog.csdn.net/wfwd/archive/2006/05/30/763551.aspx

9.结构与联合有和区别?
结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。

10 请在小于99999的正整数中找符合下列条件的数,它既是完全平方数,又有两位数字相同,如:144,676。用c语言编写(不能用数字转换成字符串)。

#include<stdio.h>
#include<math.h>
//函数havesamenum确认num是否满足条件
int havesamenum(int num)
{
	 int i=0,j;
	 char a[10] = {0};
	 
	 while(num>0)
	 {
		  j=num%10;
		  a[j]+=1;
		  num=num/10;
	 }
	 while(a[i]<=1&&i<10)
	 	i++;
	 if (i<10) 
	 	return 1;
	 else 
	 	return 0;
	}
void main(void)
{
	 int i,j,m; 
	 m=(int)sqrt(99999);
	 
	 for(i=1;i<m;i++)
	 {
	     j=i*i;
	     if (1==havesamenum(j)) 
	     printf("%6d\t",j);
	 }
}

11.对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。

12.下面关于“联合”的题目的输出?
a)

#include<iostream>

union
{
	int i;
	char x[2];
}a;

void main()
{
	a.x[0] = 10;
	a.x[1] = 1;
	printf("%d",a.i);
}

答案:266 (低位低地址,高位高地址,内存占用情况是0x010A)
b)

main()
{
	union{ /*定义一个联合*/
		int i;
		struct{ /*在联合中定义一个结构*/
			char first;
			char second;
		}half;
	}number;
	
	number.i=0x4241; /*联合成员赋值*/
	printf("%c%c\n", number.half.first, mumber.half.second);
	number.half.first='a'; /*联合中结构成员赋值*/
	number.half.second='b';
	printf("%x\n", number.i);
	getch();
}

答案: AB (0x41对应’A’,是低位;0x42对应’B’,是高位)6261 (number.i和number.half共用一块地址空间)

  1. 已知strcpy的函数原型:char *strcpy(char *strDest, const char *strSrc)其中strDest
    是目的字符串,strSrc 是源字符串。不调用C++/C 的字符串库函数,请编写函数 strcpy。
char *strcpy(char *strDest, const char *strSrc)
{
	if ( strDest == NULL || strSrc == NULL)
	return NULL ;
	if ( strDest == strSrc)
	return strDest ;
	char *tempptr = strDest ;
	while( (*strDest++ = *strSrc++) != ‘\0)
	return tempptr ;
}
#include<assert.h>
char * strcpy(char *strDest, const char *strSrc)
{
	assert((strDest != nullptr) && (strSrc != nullptr))
	char * adress=strDest;
	while((*strDest++=*strSrc) != '\0')
	return adress;
}

14.在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern “C”?
首先,作为extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。
通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数extern "C"是连接申明(linkage declaration),被extern "C"修饰的变量和函数是按照C语言方式编译和连接的,来看看C++中对类似C的函数是怎样编译的:作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:

void foo( int x, int y );

该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangledname”)。_foo_int_int 这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。
同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。未加extern "C"声明时的连接方式
假设在C++中,模块A的头文件如下:
// 模块A头文件 moduleA.h

#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif  

在模块B中引用该函数:
// 模块B实现文件 moduleB.cpp

#include "moduleA.h"
foo(2,3);
```  
实际上,在连接阶段,连接器会从模块A生成的目标文件moduleA.obj中寻找_foo_int_int这样的符号!
加extern "C"声明后的编译和连接方式
加extern "C"声明后,模块A的头文件变为:
// 模块A头文件 moduleA.h
```javascript
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int foo( int x, int y );
#endif  

在模块B的实现文件中仍然调用foo( 2,3 ),其结果是:
(1)模块A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;
(2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号_foo。如果在模块A中函数声明了foo为extern "C"类型,而模块B中包含的是extern int foo( int x, int y) ,则模块B找不到模块A中的函数;反之亦然。所以,可以用一句话概括extern “C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):实现C++与C及其它语言的混合编程。明白了C++中extern "C"的设立动机,我们下面来具体分析extern "C"通常的使用技巧:
extern "C"的惯用法
(1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处
理:

extern "C"
{
	#i nclude "cExample.h"
}

而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。

C++引用C函数例子工程中包含的三个文件的源代码如下:
/* c语言头文件:cExample.h */

#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);
#endif

/* c语言实现文件:cExample.c */

#i nclude "cExample.h"
int add( int x, int y )
{
	return x + y;
}

// c++实现文件,调用add:cppFile.cpp

extern "C"
{
	#include "cExample.h"
}
int main(int argc, char* argv[])
{
	add(2,3);
	return 0;
}

如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern “C” { }。
(2)在C中引用C++语言中的函数和变量时,C++的头文件需添加extern “C”,但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅将C文件中将C++中定义的extern "C"函数声明为extern类型。
C引用C++函数例子工程中包含的三个文件的源代码如下:
//C++头文件 cppExample.h

#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif

//C++实现文件 cppExample.cpp

#include "cppExample.h"
int add( int x, int y )
{
	return x + y;
}

/* C实现文件 cFile.c
/* 这样会编译出错:#i nclude “cExample.h” */

extern int add( int x, int y );
int main( int argc, char* argv[] )
{
add( 2, 3 );
return 0;
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值