c++ 面试题
const char * p;
p is a pointer to const char;
char const * p;
因为C++里面没有const*的运算符,所以const只能属于前面的类型.
C++标准规定,const关键字放在类型或变量名之前与之后是等价的。
const int n=5; //same as below int const m=10; const int *p; //same as below const (int) * p int const *q; // (int) const *p
转义字符
所有的ASCII码都可以用“\”加数字(一般是8进制数字)来表示。而C中定义了一些字母前加"\"来表示常见的那些不能显示的ASCII字符,如\0,\t,\n等,就称为转义字符,因为后面的字符,都不是它本来的ASCII字符意思了。
我们见到的\x,\n,\a等等都是叫转义字符,它告诉编译器需要用特殊的方式进行处理。
回调函数
简而言之,回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。
如何定义和实现一个类的成员函数为回调函数?
隐含this指针将一个回调型的成员函数作为回调函数安装时就会因为隐含的this指针使得函数参数个数不匹配,从而导致回调函数安装失败。要解决这一问题的关键 就是不让this指针起作用。
1). 不使用成员函数,为了访问类的成员变量,可以使用友元操作符(friend),在C++中将该函数说明为类的友元即可。
2). 使用静态成员函数,静态成员函数不使用this指针作为隐含参数,这样就可以作为回调函数了。
C++里面是不是所有的动作都是main()引起的?
比如全局变量的初始化,就不是由main函数引起的,但是这个初始化动作并不能为编译器的断点所截断。
C++里面如何声明const void f(void)函数为C程序中的库函数?
extern "c" const void f(void);
p.写出判断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
后置++返回const类型的副本 ,不能赋值。
Age& operator++() //前置++ { ++i; return *this; } const Age operator++(int) //后置++ { Age tmp = *this; ++(*this); //利用前置++ return tmp; }
p154、 void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, “hello”);
free(str);
if(str != NULL){
strcpy(str, “world”);
printf(str);
}
}
请问运行Test 函数会有什么样的结果?
答:输出“world”
printf(str+6);输出 "bc"
P175
void GetMemory(char *p) { p = (char *)malloc(100); } void Test(void) { char *str = NULL; GetMemory(str); strcpy(str, “hello world”); printf(str); }
请问运行Test函数会有什么样的结果?
答:程序崩溃。
p是形参,Test函数中的 str一直都是 NULL。strcpy(str, “hello world”);将使程序崩溃。
C++ 字符串常量
char str1[] = "abcd";
char str2[] = "abcd";
const char str3[] = "abcd";
const char str4[] = "abcd";
const char *str5 = "abcd";
const char *str6 = "abcd";
char *str7 = "abcd";
char *str8 = "abcd";
cout << ( str1 == str2 ) << endl;
cout << ( str3 == str4 ) << endl;
cout << ( str5 == str6 ) << endl;
cout << ( str7 == str8 ) << endl;
结果是:0 0 1 1
str1,str2,str3,str4是数组变量,它们有各自的内存空间;字符数组作为局部变量被存储在栈区;
而str5,str6,str7,str8是指针,它们指向相同的常量区域。,"abcd"被存储在静态数据区,而且是全局的。
#include <stdio.h> char *returnStr() { char p[]="hello world!"; return p; } int main() { char *str=NULL;//一定要初始化,好习惯 str=returnStr(); printf("%s\n", str); return 0; }
"hello world!"是一个字符串常量,存放在静态数据区,没错,
但是把一个字符串常量赋值给了一个局部变量(char []型数组),该局部变量存放在栈中,
这样就有两块内容一样的内存,也就是说“char p[]="hello world!";”这条语句让“hello world!”这个字符串在内存中有两份拷贝,一份在动态分配的栈中,另一份在静态存储区。这是与前者最本质的区别,
当returnStr函数退出时,栈要清空,局部变量的内存也被清空了,
所以这时的函数返回的是一个已被释放的内存地址,所以打印出来的是乱码。
p159.C++中什么数据分配在栈或堆中?
答:栈: 存放局部变量,函数调用参数,函数返回值,函数返回地址。由系统管理
堆: 程序运行时动态申请,new 和malloc申请的内存就在堆上
p160、函数模板与类模板有什么区别?
答:函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化
必须由程序员在程序中显式地指定。
p编写库函数 strcpy,strcat,atol
P168.以下代码有什么问题?
struct Test { Test( int ) {} Test() {} void fun() {} }; void main( void ) { Test a(1); a.fun(); Test b(); b.fun(); }
答:变量b定义出错。按默认构造函数定义对象,不需要加括号。
P169 以下代码有什么问题?
cout << (true?1:”1″) << endl;
答:三元表达式“?:”问号后面的两个操作数必须为同一类型。
P ?? 167 非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& ); // 赋值操作,虽不是正宗的隐式类型转换,但也可以勉强算一个
P172 C++中的空类,默认产生哪些类成员函数?
答:
class Empty
{
public:
Empty(); // 缺省构造函数
Empty( const Empty& ); // 拷贝构造函数
~Empty(); // 析构函数
Empty& operator=( const Empty& ); // 赋值运算符
Empty* operator&(); // 取址运算符
const Empty* operator&() const; // 取址运算符 const
};
P下列代码的打印结果为0吗?
#include <stdlib.h> #include <iostream> using namespace std; struct CLS { int m_i; CLS( int i ) : m_i(i){} CLS() { CLS(0); } }; int main() { CLS obj; cout << obj.m_i << endl; system("PAUSE"); return 0; }
打印结果是不定的,不一定为0
代码奇怪的地方在于构造函数中调用了自己的另一个构造函数
我们知道,当定义一个对象时,会按顺序做2件事情:
1)分配好内存(非静态数据成员是未初始化的)
2)调用构造函数(构造函数的本意就是初始化非静态数据成员)
显然上面代码中,CLS obj;这里已经为obj分配了内存,然后调用默认构造函数,但是默认构造函数还未执行完,却调用了另一个构造函数,这样相当于产生了一个匿名的临时CLS对象,它调用CLS(int)构造函数,将这个匿名临时对象自己的数据成员m_i初始化为0;但是obj的数据成员并没有得到初始化。于是obj的m_i是未初始化的,因此其值也是不确定的.
正确的方式:
struct CLS { int m_i; CLS( int i ) : m_i(i){} CLS() { new (this)CLS(0); } };
在Java中一个构造函数里调用另一个构造函数,直接用this关键字。
sizeof 类
用sizeof对类名操作,得到的结果是该类的对象在存储器中所占据的字节大小,由于静态成员变量不在对象中存储,因此这个结果等于各非静态数据成员(不包括成员函数)的总和加上编译器额外增加的字节(内存对齐)。后者依赖于不同的编译器实现,C++标准对此不做任何保证。
一个类中,虚函数、成员函数(包括静态与非静态)和静态数据成员都是不占用类对象的存储空间的。
类的大小与它当中的构造函数,析构函数,以及其他的成员函数无关,只与它当中的成员数据有关.
如果在类中声明了虚函数(不管是1个还是多个),那么在实例化对象时,编译器会自动在对象里安插一个指针指向虚函数表VTable,在32位机器上,一个对象会增加4个字节来存储此指针。
每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址,所以空类大小为1.
P51. 以下反向遍历array数组的方法有什么错误?[STL易]
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; }
1.数组定义有误,应加上类型参数:vector<int> array.
2.vector::size_type被定义为unsigned int,即无符号数,这样做为循环变量的i为0时再减1就会变成最大的整数,导致循环失去控制
P185 对于C++中类(class) 与结构(struct)的描述正确的为:
A,类中的成员默认是private的,当是可以声明为public,private 和protected,
结构中定义的成员默认的都是public;
B,结构中不允许定义成员函数,当是类中可以定义成员函数;
C,结构实例使用malloc() 动态创建,类对象使用new 操作符动态分配内存;
D,结构和类对象都必须使用new 创建;
E,结构中不可以定义虚函数,当是类中可以定义虚函数.
F,结构不可以存在继承关系,当是类可以存在继承关系.
答:A,D,F
P203.下面代码有什么问题?
Void test2() { char string[10], str1[10]; for(i=0; i<10;i++) { str1[i] =’a'; } strcpy(string, str1); } }
数组越界
P204下面代码有什么问题?LUPA开源社区+j H2B F,c U
Void test3(char* str1) { char string[10]; if(strlen(str1)<=10) { strcpy(string, str1); } }
数组越界
strcpy拷贝的结束标志是查找字符串中的\0 因此如果字符串中没有遇到\0的话 会一直复制,直到遇到\0,上面的123都因此产生越界的情况
建议使用 strncpy 和 memcpy
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之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。
_foo_int_int 这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。
同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。
p如何限制一个类只在堆上分配和栈上分配?
#include <iostream> using namespace std; class A { public: virtual void Fun(int number = 10) { std::cout << "A::Fun with number " << number<<endl; } }; class B: public A { public: virtual void Fun(int number = 20) { std::cout << "B::Fun with number " << number<<endl; } }; int main() { B b; A &a = b; a.Fun(); system("pause"); return 0; //虚函数动态绑定:B,缺省实参是编译时确定的。。。为10 }
打印结果:B::Fun with number 10
在编译的时候,编译器只知道a是一个类型a的引用,具体指向什么类型在编译期是不能确定的,因此会按照A::Fun的声明把缺省参数number设为10。
在C++中,static静态成员变量不能在类的内部初始化。在类的内部只是声明,定义必须在类定义体的外部,通常在类的实现文件中初始化,如:double Account::Rate=2.25;static关键字只能用于类定义体内部的声明中,定义时不能标示为static
在C++中,const成员变量也不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数。
const数据成员 只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类的声明中初始化const数据成员,因为类的对象没被创建时,编译器不知道const数据成员的值是什么。
const数据成员的初始化只能在类的构造函数的初始化列表中进行。要想建立在整个类中都恒定的常量,应该用类中的枚举常量来实现,或者static cosnt。
class Test { public: Test():a(0){} enum {size1=100,size2=200}; private: const int a;//只能在构造函数初始化列表中初始化 static int b;//在类的实现文件中定义并初始化 const static int c;//与 static const int c;相同。 }; int Test::b=0;//static成员变量不能在构造函数初始化列表中初始化,因为它不属于某个对象。 cosnt int Test::c=0;//注意:给静态成员变量赋值时,不需要加static修饰符。但要加cosnt
typedef和define具体的详细区别
1) #define是预处理指令,在编译预处理时进行简单的替换,不作正确性检查,不关含义是否正确照样带入,只有在编译已被展开的源程序时才会发现可能的错误并报错。例如:
#define PI 3.1415926
程序中的:area=PI*r*r 会替换为3.1415926*r*r
如果你把#define语句中的数字9 写成字母g 预处理也照样带入。
2)typedef是在编译时处理的。它在自己的作用域内给一个已经存在的类型一个别名,但是You cannot use the typedef specifier inside a function definition。
3)typedef int * int_ptr;
与
#define int_ptr int *
作用都是用int_ptr代表 int * ,但是二者不同,正如前面所说 ,#define在预处理 时进行简单的替换,而typedef不是简单替换 ,而是采用如同定义变量的方法那样来声明一种类型。也就是说;
//refer to (xzgyb(老达摩))
#define int_ptr int *
int_ptr a, b; //相当于int * a, b; 只是简单的宏替换
typedef int* int_ptr;
int_ptr a, b; //a, b 都为指向int的指针,typedef为int* 引入了一个新的助记符
这也说明了为什么下面观点成立
//QunKangLi(维护成本与程序员的创造力的平方成正比)
typedef int * pint ;
#define PINT int *
那么:
const pint p ;//p不可更改,但p指向的内容可更改
const PINT p ;//p可更改,但是p指向的内容不可更改。
pint是一种指针类型 const pint p 就是把指针给锁住了 p不可更改
而const PINT p 是const int * p 锁的是指针p所指的对象。
3)也许您已经注意到#define 不是语句 不要在行末加分号,否则 会连分号一块置换。
如何限制一个类只在堆上分配和栈上分配
1 在C++中如何限制一个类对象只在堆上分配?
仿照设计模式中的单实例模式或者工厂模式来解决,这里采用单实例模式方式来说明。
将类的构造函数属性置为private,同时提供static成员函数getInstance,在函数中new一个新对象,然后返回对象指针或者引用。这样实现的类可以保证只可以在堆上分配对象。
2 在C++中如何限制一个类对象只在栈上分配?
我们已经知道,产生堆对象的唯一方法是使用new操作,如果我们禁止使用new不就行了么。再进一步,new操作执行时会调用operator new,而operator new是可以重载的。方法有了,就是使new operator为private,为了对称,最好将operator delete也重载为private。现在,你也许又有疑问了,难道创建栈对象不需要调用new吗?是的,不需要,因为创建栈对象不需要搜索内存,而是直接调整堆栈指针,将对象压栈,而operator new的主要任务是搜索合适的堆内存,为堆对象分配空间。
从C++文件到生成exe文件经过哪三个步骤?
1)用户点击编译程序时,编译程序将C++源代码转换成目标代码,目标代码通常由 机器指令和记录如何将程序加载到内存的信息组成。其后缀通常为.obj或.o;
2)目标文件中存储的只是用户所编写的代码的转换结果,并不包括底层的操作指令,不能直接运行。例如程序包 iostream 实现了所有有关输入和输出的操作,并且其所有实现操作的机器代码都放在一个库中,库是对已实现的程序经编译后所产生的代码集合,用户可以在程序中直接使用库。
3)一个被称为链接程序的特殊程序将用户程序的目标文件和iostream库中必要代码链接起来生成一个可执行文件,其后缀通常为.exe 。这个可执行文件中包含了执行该用户程序所需要的所有机器代码,其过程大体如下所示:

请用C++实现以下print函数,打印链表I中的所有元素, 每个元素单独成一行
void print(const std::list<int> &I){
}
void print(const std::list<int> &I){ for(const std::list<int>::const_itorator it = I.begin():it != I.end(): it++)//访问常量必须使用常迭代器。 cout<<*it<<endl; }
如何记忆两种结合性和15种优先级?
下面讲述一种记忆方法。
结合性有两种,一种是自左至右,另一种是自右至左,大部分运算符的结合性是自左至右,只有单目运算符、三目运算符的赋值运算符的结合性自右至左。
优先级有15种。记忆方法如下:
记住一个最高的:构造类型的元素或成员以及小括号。
记住一个最低的:逗号运算符。
剩余的是一、二、三、赋值。
意思是单目、双目、三目和赋值运算符。
在诸多运算符中,又分为:
算术、关系、逻辑。
两种位操作运算符中,移位运算符在算术运算符后边,逻辑位运算符在逻辑运算符的前面。再细分如下:
算术运算符分 *,/,%高于+,-。
关系运算符中,〉,〉=,<,<=高于==,!=。
逻辑运算符中,除了逻辑求反(!)是单目外,逻辑与(&&)高于逻辑或(||)。
逻辑位运算符中,除了逻辑按位求反(~)外,按位与(&)高于按位半加(^),高于按位或(|)。
这样就将15种优先级都记住了,再将记忆方法总结如下:
去掉一个最高的,去掉一个最低的,剩下的是一、二、三、赋值。双目运算符中,顺序为算术、关系和逻辑,移位和逻辑位插入其中。
若输入B,则以下程序运行后的输出结果是()
int main(void) { char grade; scanf("%c",&grade); switch(grade) { case 'A': printf(">=85"); case 'B': case 'C': printf(">=60"); case 'D': printf("<60"); default: printf("error."); } }
A、error.
B、>=60
C、>=85
D、>=60<60error
选D,case后的常量值匹配后,不再检查case语句的值,直接从开始匹配处执行后面所有语句。故要跳出应加上break。
取一个数组名的地址所产生的是一个指向数组的指针,而不是一个指向某个指针常量的指针。
所以&a后返回的指针便是指向数组的指针,&a+1指向的是数组末尾元素的下一个
跟a(一个指向a[0]的指针)在指针的类型上是有区别的。a+1指向的是数组第二个元素。
const int* get() { const int* a=new int(10); return a; } const int* m=get(); *m=0;//error typedef const int* mtype const mtype get() { const int* a=new int(10); return a; } const int* m=get(); *m=0;//error typedef int* mtype const mtype get() { int* const a=new int(10); return a; } const int* m=get(); *m=0;//ok
对左值和右值的一个最常见的误解是:等号左边的就是左值,等号右边的就是右值。
左值和右值都是针对表达式而言的,左值是指表达式结束后依然存在的持久对象,右值是指表达式结束时就不再存在的临时对象。一个区分左值与右值的便捷方法是:看能不能对表达式取地址,如果能,则为左值,否则为右值。
什么是左值引用:
区分清楚了左值与右值,我们再来看看左值引用。左值引用根据其修饰符的不同,可以分为非常量左值引用(eg.1 double &r =i;)和常量左值引用(eg.1 const double &r =i;)。
非常量左值引用只能绑定到非常量左值,不能绑定到常量左值、非常量右值和常量右值。如果允许绑定到常量左值和常量右值,则非常量左值引用可以用于修改常量左值和常量右值,这明显违反了其常量的含义(eg.1中就是出现了非常量左值引用绑定到常量右值的情况)。如果允许绑定到非常量右值,则会导致非常危险的情况出现,因为非常量右值是一个临时对象,非常量左值引用可能会使用一个已经被销毁了的临时对象。
常量左值引用可以绑定到所有类型的值,包括非常量左值、常量左值、非常量右值和常量右值。
对左值和右值的一个最常见的误解是:等号左边的就是左值,等号右边的就是右值。
左值和右值都是针对表达式而言的,左值是指表达式结束后依然存在的持久对象,右值是指表达式结束时就不再存在的临时对象。一个区分左值与右值的便捷方法是:看能不能对表达式取地址,如果能,则为左值,否则为右值。
int k[2][3]={0,2,4,6,8,10}; cout<<k<<","<<k[0]<<","<<k+1<<","<<k[1]<<endl; //k==k[0] k+1==k[1] cout<<**k<<","<<**(k+1)<<","<<*(*(k+1)+2)<<endl; // 0 6 10

浙公网安备 33010602011771号