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

 

 
posted @ 2014-09-07 19:38  DET橙  阅读(246)  评论(0)    收藏  举报