现在的位置: 首页 > 综合 > 正文

C++ 笔试面试常考题

2013年09月11日 ⁄ 综合 ⁄ 共 8682字 ⁄ 字号 评论关闭

(一)写函数完成内存的拷贝

void* memcpy( void *dst, const void *src, unsigned int len )
{
    register char *d;
    register char *s;
    if (len == 0)
        return dst;
    if ( dst > src )   //考虑覆盖情况
    {
        d = (char *)dst + len - 1;
        s = (char *)src + len - 1;
        while ( len >= 4 )   //循环展开,提高执行效率
        {
            *d-- = *s--;
            *d-- = *s--;
            *d-- = *s--;
            *d-- = *s--;
            len -= 4;
        }
        while ( len-- )
        {
            *d-- = *s--;
        }
    }
    else if ( dst < src )
    {
        d = (char *)dst;
        s = (char *)src;
        while ( len >= 4 )
        {
            *d++ = *s++;
            *d++ = *s++;
            *d++ = *s++;
            *d++ = *s++;
            len -= 4;
        }
        while ( len-- )
        {
            *d++ = *s++;
        }
    }
    return dst;
}

(二)static关键字用途

    
static关键字是C, C++中都存在的关键字, 它主要有三种使用方式, 其中前两种在C/C++语言中使用, 第三种只在C++中使用

     

    一般回答:
1.限制变量的作用域;    2.设置变量的存储域,只在定于变量的源文件内可见

 

    <1>局部
静态变量

      在C/C++中, 局部变量按照存储形式可分为三种auto, static, register。

与auto类型(普通)局部变量相比, static局部变量有三点不同
1. 存储空间分配不同
      auto类型分配在


上, 属于动态存储类别, 占动态存储区空间, 函数调用结束后自动释放, 而static分配在静态存储区

, 在程序整个运行期间都不释放. 两者之间的作用域相同, 但生存期不同.
2. static局部变量在所处模块在初次运行时

进行初始化

工作, 且只操作一次
3. 对于局部静态变量, 如果不赋初值, 编译期会自动赋初值0或空字符

, 而auto类型的初值是不确定的. (对于C++中的class对象例外, class的对象实例如果不初始化, 则会自动调用默认构造函数, 不管是否是static类型)

特点: static局部变量的”记忆性”与生存期的”全局性”
     所谓”记忆性
”是指在两次函数调用时, 在第二次调用进入时, 能保持第一次调用退出时的值. 即每次运行结果不同,这将破坏程序运行时的可重复性;

    “生存期
”全局性和唯一性. 普通的local变量的存储空间分配在stack上, 因此每次调用函数时, 分配的空间都可能不一样,而static具有全局唯一性的特点, 每次调用时, 都指向同一块内存, 这就造成一个很重要的问题 ---- 不可重入性!!!
      这样在多线程程序设计


递归程序设计



中, 要特别注意这个问题.

<2>外部
静态变量/函数

     用来表示不能被其它文件访问的全局变量
函数
。但为了限制全局变量/函数的作用域, 函数或变量前加static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件
(所以又称内部函数)。注意此时, 对于外部(全局)变量, 不论是否有static限制, 它的存储区域都是在静态存储区, 生存期都是全局的. 此时的static只是起作用域限制作用, 限定作用域在本模块(文件)内部.
     使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。

总结:

1. 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
2. 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。

<3>静态数据成员/成员函数

      C++赋予这个关键字与前面不同的第三种含义:表示属于一个类

不属于此类的任何特定对象

的变量和函数. 这是与普通成员函数的最大区别, 也是其应用所在, 比如在对某一个类的对象进行计数时, 计数生成多少个类的实例, 就可以用到静态数据成员. 在这里面, static既不是限定作用域的, 也不是扩展生存期的作用, 而是指示变量/函数在此类中的唯一性

. 这也是”属于一个类而不是属于此类的任何特定对象的变量和函数”的含义. 因为它是对整个类来说是唯一的, 因此不可能属于某一个实例对象的. (针对静态数据成员而言, 成员函数不管是否是static, 在内存中只有一个副本, 普通成员函数调用时, 需要传入this指针, static成员函数调用时, 没有this指针. )


 

 

      全局变量和局部变量在内存中区别是什么?
全局变量储存在全局静态存储区,局部变量在堆栈

 

 


 

 

(三) 引用与指针有什么区别


1) 引用必须被初始化,指针不必。
2) 引用初始化以后不能被改变,指针可以改变所指的对象。
3) 不存在指向空值的引用,但是存在指向空值的指针。

(四) 写出float x 与“零值”比较的if语句

   ----------------------------------------------------

   const float EPSINON = 0.00001;
   if ((x >= - EPSINON) && (x <= EPSINON)

  ----------------------------------------------------

      浮点数在内存中的存储机制和整型数不同,其有舍入误差

在计算机中用近似表示任意某个实数。具体的说,这个实数由一个整数或定点数(即尾数)乘以某个基数(计算机中通常是2)的整数次幂得到,这种表示方法类似于基数为10的科学记数法。
      所以浮点数在运算过程中通常伴随着因为无法精确表示而进行的近似或舍入。但是这种设计的好处是可以在固定的长度上存储更大范围的数。
例如,一个指数范围为±4的4位十进制浮点数可以用来表示43210,4.321或0.0004321,但是没有足够的精度来表示432.123和43212.3(必须近似为432.1和43210)。当然,实际使用的位数通常远大于4。
      所以浮点数不能够判断相等

像 if(x==0)的这样的编码是不总是正确的,我们在判断浮点数相等时,推荐用范围来确定,若x在某一范围内,我们就认为相等


,至于范围怎么定义,要看实际情况而已了,float,和double 各有不同
所以const float EPSINON = 0.00001;
if ((x >= - EPSINON) && (x <= EPSINON) 这样判断是可取的
至于为什么取0.00001,可以自己按实际情况定义

-----------------------------------------------------------------------

|     上面的解法恐怕是8086以前的事情, 汇编早都可以用一条指令比较了.既然想考精度,就换个不是0的,比如0.00002 ,                  

|      则:    if(x-0.00002>0.000001&&x-0.0002<-0.000001) 
-----------------------------------------------------------------------

 

 

(五) 写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。另外,当你写下面的代码时会发生什么事?

        least = MIN(*p++, b);

 

 

    宏定义可以实现类似于函数的功能,但是它终归不是函数,而宏定义中括弧中的“参数”也不是真的参数,在宏展开的时候对“参数”进行的是一对一的替换。

  程序员对宏定义的使用要非常小心,特别要注意两个问题:

  (1)谨慎地将宏定义中的“参数”和整个宏用用括弧括起来。所以,严格的解答如下:

        

          #define MIN(A,B) ((A) <= (B) ? (A) : (B)
        //注意后面没有分号“;”






 

    (2)防止宏的副作用。

  宏定义 #define  MIN(A,B)  ((A) <= (B) ? (A) : (B)) 对 MIN(*p++, b)的作用结果是:

              ((*p++) <= (b) ? (*p++):(*p++))


    显然这个表达式会产生副作用,指针p会作三次++自增操作。

 (六)   extern "C"

    作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在symbol库中的名字与C语言的不同。例如,假
设某个函数的原型为:

     void foo(int x, int y);

  

    该函数被C编译器编译后在symbol库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。_foo_int_int这样的名字包含了函数名和函数参数数量及类型信息,C++就是考这种机制来实现函数重载的。

  为了实现C和C++的混合编程,C++提供了C连接交换指定符号extern "C"来解决名字匹配问题,函数声明前加上extern "C"后,如下:

      
extern "C"  
void foo(int x, int y);



 

则编译器就会按照C语言的方式
将该函数编译为_foo,这样C语言中就可以调用C++的函数
了。

    extern "C"的真实目的是实现类C和C++的混合编程
。在C++源文件中的语句前面加上extern "C",表明它按照类C的编译和连接规约来编译和连接,而不是C++的编译的连接规约。这样在类C的代码中就可以调用C++的函数or变量等。(注:我在这里所说的类C,代表的是跟C语言的编译和连接方式一致的所有语言)

    

     详细参见: http://www.cnblogs.com/skynet/archive/2010/07/10/1774964.html
 (好)

                http://www.cppblog.com/Macaulish/archive/2008/06/17/53689.html

(七)memset(),memcpy(),memccpy(),memmove(),bcopy(),strcpy(),strncpy()

 

  void *memset(void *s,int c,int strlen);
  void *memcpy(char *str_d,char *str_s,int n);
  void *memccpy(void *dest,const void *src,int c,int n);
  void *memmove(void *dest,const void *src,int n);
  void bcopy(const void *src,void *dest,int n);
  char *strcpy(char *dest,const char *src);
  char *strncpy(char *dest,const char *src,int strlen);

以上的函数是我们在c编程中经常遇到的,下面是对每个函数的简单解释:
      void *memset(void *s,int c,int n)

用整型值c填充由指针s指向的内存区域的前n个字节.返回指向该内存区域的指针s。s并不一定是指向字符的指针,他可以是指向任何类型的指针,甚至可以是指向结构的指针.

      void *memcpy(void *dest,const void *src,int n)

将指针src指向的前n个字节拷贝到dest指向的前n个内存区域中。如果src和dest有重复区域,则会被覆盖.即在拷贝的过程中向后移.这样可能达不到预期的效果,此时就使用memmove
.

--------------------------------------------------------------    

       memcpy, 拷贝不重叠的内存块
       void *memcpy(void* pvTo, void* pvFrom, size_t size) //byte是java里的变量类型
      {
           assert(pvTo != NULL && pvFrom != NULL);
           void* pbTo = (byte*)pvTo;
           void* pbFrom = (byte*)pvFrom;
           /* 内存块重叠吗?如果重叠,就使用memmove */
           assert(pbTo>=pbFrom+size || pbFrom>=pbTo+size);
           while(size-->0)
                 *pbTo++ == *pbFrom++;
           return pvTo;
       }

 

       

     另外:memcpy()用来拷贝src所指的内存内容前n个字节到dest所指的内存地址上。与strcpy()不同的是,memcpy()会完整的复制n个字节,不会因为遇到字符串结束'/0'而结束



--------------------------------------------------------------    

      void *memmove(void *dest,const void *src,int n)

该函数和前面函数的区别是当src和desc有重复区域时,则会先将desc向后移,然后再进行拷贝操作.

      void *memccpy(void *dest,const void *src,int c,int n);

The memccpy() function copies no more than n bytes from memory area src to memory area dest, stopping when the character c is found.
返回结果是一个指向dest中的第一个c后面的字符,如果在src的前n个字符中没有找到c,则返回NULL

      void bcopy(const void *src,void *dest,int n);

从src拷贝n个字节到dest中.如果n=0,则拷贝0个字节

 

      所有上面的拷贝是针对任何数据类型

的,所以指针类型都是void类型.而下面的strcpy和strncpy都是针对字符串

的操作.

 

      char *strcpy(char *dest,const char *src);

把src指针指向的字符串拷贝到dest指向的数组中,包括.不能处理区域重复的问题,但是dest指向的空间必须比src的空间大.

      char *strncpy(char *dest,const char *src,int n)

把src指向的字符串的前n个字符拷贝到dest指向的数组中,如果src的前n个字符中没有结束符,则dest也没有.这样不是正确的做法.

返回值是指向字符串的指针.如果dest的长度不够,则会有溢出错误.

 

--------------------------------------

下面是几种函数的简单实现

--------------------------------------

 

 

 

(八)指针和引用的区别总结

1.从现象上看:指针在运行时可以改变其所指向的值,而引用一旦和某个对象绑定后就不再改变
2.从内存分配上看:程序为指针变量分配内存区域,而引用不分配内存区域
3.
从编译上看:程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址
值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变指向的对象(指针变量中的值可以改),而引用对象不能
改。


★ 相同点:
1. 都是地址的概念;
指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。

★ 区别:
1. 指针是一个实体,而引用仅是个别名;
2. 引用使用时无需解引用(*),指针需要解引用;
3. 引用只能在定义时被初始化一次,之后不可变;指针可变;
引用“从一而终” ^_^
4. 引用没有 const,指针有 const,const 的指针不可变;
5. 引用不能为空,指针可以为空;
6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
typeid(T) == typeid(T&) 恒为真,sizeof(T) == sizeof(T&) 恒为真,
但是当引用作为成员时,其占用空间与指针相同(没找到标准的规定)。
7. 指针和引用的自增(++)运算意义不一样;

 

★ 联系
1. 引用在语言内部用指针实现。
2. 对一般应用而言,把引用理解为指针,不会犯严重语义错误。引用是操作受限了的指针(仅容许取内容操作)。

 

 

抱歉!评论已关闭.