部分c/c++疑点解析1

一、char amessage[ ] = "now is the time";
    char *pmessage = "now is the time";
    *(amessage+3) = 'H';    //OK
    *(pmessage+3) = 'H';   //error,不能尝试修改指针定义的字符数组

二、void func(int *i)
{
     int a=5;
     *i=a;  
}
int main()
{
 int  i=0;
 cout<<i<<endl;
 func(&i);
 cout<<i<<endl;
}
打印的结果是0,5
而如果改成
void func(int *i)
{
     int a=5;
     i = &a;
}
打印的结果是0,0。
为什么两个结果不同呢,因为在main函数中传给func函数的是i的地址的复制值,在第一个中,那个值没有改变,
但是这个地址所指向的值发生了改变,在第二个中,只是那个复制的地址值发生了改变。原函数中的那个地址值没有改变,它所指向的值也没有改变

如果函数的参数是一个指针,不要指望用该指针去申请动态内存。下面示例中,Test函数的语句GetMemory(str, 200)并没有使str获得期望的内存,str依旧是NULL,为什么?
 
void GetMemory(char *p, int num)
{
       p = (char *)malloc(sizeof(char) * num);
}
void Test(void)
{
      char *str = NULL;
      GetMemory(str, 100); // str 仍然为 NULL
      strcpy(str, "hello"); // 运行错误
}
示例试图用指针参数申请动态内存
 
毛病出在函数GetMemory中。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,_p指向了新的空间,把p抛开了。只是把_p所指的内存地址改变了,但是p丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会泄露一块内存,因为没有用free释放内存。
如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”,见下示例
 
void GetMemory2(char **p, int num)
{
*p = (char *)malloc(sizeof(char) * num);
}
void Test2(void)
{
      char *str = NULL;
      GetMemory2(&str, 100); // 注意参数是 &str,而不是str
      strcpy(str, "hello");
      cout < < str  < < endl;
      free(str);
}

三、笔试经常遇到的复制字符串的函数的写法,这是从.NET 2003 安装文件夹下找到的 
文件路径:/Microsoft Visual Studio .NET 2003/Vc7/crt/src
文件名:strncpy.c
文件内容:

/***
*strncpy.c - copy at most n characters of string
*
*       Copyright (c) Microsoft Corporation. All rights reserved.
*
*Purpose:
*       defines strncpy() - copy at most n characters of string
*
*******************************************************************************/

#include  <cruntime.h >
#include  <string.h >

/***
*char *strncpy(dest, source, count) - copy at most n characters
*
*Purpose:
*       Copies count characters from the source string to the
*       destination.  If count is less than the length of source,
*       NO NULL CHARACTER is put onto the end of the copied string.
*       If count is greater than the length of sources, dest is padded
*       with null characters to length count.
*
*
*Entry:
*       char *dest - pointer to destination
*       char *source - source string for copy
*       unsigned count - max number of characters to copy
*
*Exit:
*       returns dest
*
*Exceptions:
*
*******************************************************************************/

char * __cdecl strncpy (
        char * dest,
        const char * source,
        size_t count
        )
{
        char *start = dest;

        while (count && (*dest++ = *source++))    /* copy string */
                count--;

        if (count)                              /* pad out with zeroes */
                while (--count)
                        *dest++ =  '/0 ';

        return(start);


四、 typdedef struct _A{
    ...  ... // some definitions
    int x;
    ...  ... // some definitions
    int y;
}A

void foo(int *px)
{
    //print the value of y
   【                    】
}

void main()
{
    A *a = ( A *) malloc (sizeof(A));

    if ( a == NULL )
       return ;
    foo( &(a- >x));
}

1.void foo(int *px) 

#define offsetof(s,m)   (size_t)&(((s *)0)->m)
    printf("%d/n", *(int*)((char*)px + offsetof(A, y) - offsetof(A, x)));

#define offsetof(s,m)   (size_t)&(((s *)0)->m)
是msvc中自带的。对于编译器来说,表达式&(((s *)0)->m)是要计算m的地址,所以,编译器根本不会产生任何读取m的操作(此处的m为非法地址的数据内容),所以没有问题。 编译器只需简单的根据s的定义,得出m的偏移

2.void foo(int *px) 

  A *temp=( A *) malloc (sizeof(A)); 
  int addr=(int)(&(temp- >y)-&(temp- >x));
  printf("%d/n", *(px+addr));  
}
不过如果struct _A的定义中有类似#pragma pack(1)的语句,第二个答案就会出错

五、1. 什么是“引用”?申明和使用“引用”要注意哪些问题?
        答:引用就是某个目标变量的“别名”(alias),对应用的操作与对变量直接操作效果完全相同。申明一个引用的时候,切记要对其进行初始化。引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。不能建立数组的引用。
2. 将“引用”作为函数参数有哪些特点?
       (1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
       (2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用

六、在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之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。
       _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
      #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"
{
        #include "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 */
    #include "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
       /* 这样会编译出错:#include "cExample.h" */
      extern int add( int x, int y );
      int main( int argc, char* argv[] )
      {
            add( 2, 3 );
            return 0;
       }

 

七、重载(overload)和重写(overried,有的书也叫做“覆盖”)的区别?
        
常考的题目。从定义上来说:
         重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
         重写:是指子类重新定义复类虚函数的方法。
         从实现原理上来说:
         重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!
         重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。

 

八、多态的作用?
        主要是两个:1. 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用;2. 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用。

九、New delete 与malloc free 的联系与区别?
          答案:都是在堆(heap)上进行动态的内存操作。用malloc函数需要指定内存分配的字节数并且不能初始化对象,new 会自动调用对象的构造函数。delete 会调用对象的destructor,而free 不会调用对象的destructor.,如果失败,malloc返回NULL,而new抛出一个异常对象,类型为“std::bad_alloc”,(虽然存在不抛出异常的new版本),存在处理数组形式的“new”和“delete”,而“malloc”和“free”不存在;对于空指针(NULL)的处理,free()是不安全的,而delete是安全的。

十、 描述内存分配方式以及它们的区别?
        1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
        2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。
         3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。

十一、请说出const与#define 相比,有何优点?
        答案:1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
       2) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。

十二、.类成员函数的重载、覆盖和隐藏区别?
       a.成员函数被重载的特征:
         (1)相同的范围(在同一个类中);
         (2)函数名字相同;
         (3)参数不同;
         (4)virtual 关键字可有可无。
      b.覆盖是指派生类函数覆盖基类函数,特征是:
         (1)不同的范围(分别位于派生类与基类);
         (2)函数名字相同;
         (3)参数相同;
         (4)基类函数必须有virtual 关键字。
       c.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
         (1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
        (2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值