c++ 常见问题 1

1.在C++ 程序中调用被C 编译器编译后的函数,为何要加extern “C”?html

  答:C++语言支持函数重载,C 语言不支持函数重载。函数被C++编译后在库中的名字与C 语言的不一样。假设某个函数的原型为: void foo(int x, int y);c++

  该函数被C 编译器编译后在库中的名字为_foo , 而C++ 编译器则会产生像_foo_int_int 之类的名字。编程

  C++提供了C 链接交换指定符号extern“C”来解决名字匹配问题。数组

http://yjbys.com/bishi/timu/598258.html安全

 

2.char * strcpy(char * strDest,const char * strSrc);ide

已知strcpy函数的原型是:
char * strcpy(char * strDest,const char * strSrc);
⒈不调用 库函数,实现strcpy函数。
⒉解释为何要返回char *。
解说
1
2
3
4
5
6
7
8
9
strcpy 的实现代码
char  strcpy ( char  * strDest, const  char  * strSrc)
{
if  ((NULL==strDest) || (NULL==strSrc))  //[1]
throw  "Invalid argument(s)" //[2]
char  * strDestCopy = strDest;  //[3]
while  ((*strDestCopy++=*strSrc++)!= '\0' );  //[4]
return  strDest;
}
错误的作法:
[1]
(A)不检查 指针的有效性,说明答题者不注重代码的健壮性。
(B)检查指针的有效性时使用((!strDest)||(!strSrc))或(!(strDest&&strSrc)),说明答题者对C语言中类型的隐式转换没有深入认识。在本例中char *转换为bool便是类型隐式转换,这种功能虽然灵活,但更多的是致使出错几率增大和维护成本升高。因此C++专门增长了bool、true、false三个 关键字以提供更安全的 条件表达式
(C)检查指针的有效性时使用((strDest==0)||(strSrc==0)),说明答题者不知道使用 常量的好处。直接使用字面常量(如本例中的0)会减小程序的可维护性。0虽然简单,但程序中可能出现不少处对指针的检查,万一出现笔误, 编译器不能发现,生成的程序内含逻辑错误,很难排除。而使用NULL代替0,若是出现拼写错误,编译器就会检查出来。
[2]
(A)return new string("Invalid argument(s)");,说明答题者根本不知道返回值的用途,而且他对 内存泄漏也没有警戒心。从函数中返回函数体内分配的内存是十分危险的作法,他把释放内存的义务抛给不知情的调用者,绝大多数状况下,调用者不会 释放内存,这致使内存泄漏。
(B)return 0;,说明答题者没有掌握异常机制。调用者有可能忘记检查返回值,调用者还可能没法检查返回值(见后面的链式表达式)。妄想让返回值肩负返回正确值和异常值的双重功能,其结果每每是两种功能都失效。应该以 抛出异常来代替 返回值,这样能够减轻调用者的负担、使错误不会被忽略、加强程序的可维护性。
[3]
(A)忘记保存原始的strDest值,说明答题者逻辑思惟不严密。
[4]
(A)循环写成while (*strDestCopy++=*strSrc++);,同[1](B)。
(B)循环写成while (*strSrc!='\0') *strDest++=*strSrc++;,说明答题者对边界条件的检查不力。循环体结束后,strDest字符串的末尾没有正确地加上'\0'。
⒉返回strDest的原始值使函数可以支持链式 表达式,增长了函数的“附加值”。一样功能的函数,若是能合理地提升的可用性,天然就更加理想。
链式表达式的形式如:
int iLength=strlen(strcpy(strA,strB));
又如:
char * strA=strcpy(new char[10],strB);
返回strSrc的原始值是错误的。其一,源字符串确定是已知的,返回它没有意义。其二,不能支持形如第二例的表达式。其三,为了保护源字符串, 形参用const限定strSrc所指的内容,把const char *做为char *返回,类型不符,编译报错。
http://baike.baidu.com/link?url=_q_nYQSrK6uYGTSFjM9Y0ts-Xj6I4cTgca4TDgDnFJsjIt5Udg6kDI6reH4EIcxseA-g2eOZkokQHKxlceeXo_
 
3. C++的多态性
多态性包含编译时的多态性、运行时的多态性两大类。 即:多态性也分静态多态性和动态多态性两种。

   静态多态性

  静态多态性是指定义在一个类或一个函数中的同名函数,它们根据参数表(类型以及个数)区别语义,并经过静态联编实现,例如,在一个类中定义的不一样参数的构造函数

——函数重载(区分函数重载、函数覆盖)

动态多态性

  动态多态性是指定义在一个类层次的不一样类中的重载函数,它们通常具备相同的函数,所以要根据指针指向的对象所在类来区别语义(不一样的子类对象由父类指针指向,提升代码复用),它经过动态联编实现。
 
  在用户不做任何干预的环境下,类的成员函数的行为能根据调用它的对象类型自动做出适应性调整,并且调整是发生在程序运行时,这就是程序的动态多态性。即,发出一样的消息被不一样类型的对象接收时,有可能致使彻底不一样的行为。
 
博主的总结挺好的:

C++多态性是经过虚函数来实现的,虚函数容许子类从新定义成员函数,而子类从新定义父类的作法称为覆盖(override),或者称为重写。(这里我以为要补充,重写的话能够有两种,直接重写成员函数和重写虚函数,只有重写了虚函数的才能算做是体现了C++多态性)而重载则是容许有多个同名的函数,而这些函数的参数列表不一样,容许参数个数不一样,参数类型不一样,或者二者都不一样。编译器会根据这些函数的不一样列表,将同名的函数的名称作修饰,从而生成一些不一样名称的预处理函数,来实现同名函数调用时的重载问题。但这并无体现多态性。模块化

  多态与非多态的实质区别就是函数地址是早绑定仍是晚绑定。若是函数的调用,在编译器编译期间就能够肯定函数的调用地址,并生产代码,是静态的,就是说地址是早绑定的。而若是函数调用的地址不能在编译器期间肯定,须要在运行时才肯定,这就属于晚绑定。函数

  那么多态的做用是什么呢,封装可使得代码模块化,继承能够扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。也就是说,不论传递过来的到底是那个类的对象,函数都可以经过同一个接口调用到适应各自对象的实现方法。url

  最多见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,能够根据指向的子类的不一样而实现不一样的方法。若是没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数自己,而没法调用到子类中被重写过的函数。由于没有多态性,函数调用的地址将是必定的,而固定的地址将始终调用到同一个函数,这就没法实现一个接口,多种方法的目的了。spa

虚函数理解:

对于虚函数调用来讲,每个对象内部都有一个虚表指针,该虚表指针被初始化为本类的虚表。因此在程序中,无论你的对象类型如何转换,但该对象内部的虚表指针是固定的,因此呢,才能实现动态的对象函数调用,这就是C++多态性实现的原理。 
总结(基类有虚函数): 
一、 每个类都有虚表。 
二、 虚表能够继承,若是子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现。若是基类3个虚函数,那么基类的虚表中就有三项(虚函数地址),派生类也会有虚表,至少有三项,若是重写了相应的虚函数,那么虚表中的地址就会改变,指向自身的虚函数实现。若是派生类有本身的虚函数,那么虚表中就会添加该项。 
三、 派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。

http://blog.csdn.net/youngchang06hpu/article/details/8715011

4. 引用与指针

http://blog.csdn.net/youngchang06hpu/article/details/8715011

参考:《c和指针》

1 引用是C++中的概念,C中没有引用,而只有&操做,即取地址符,其必须做用于一个左值。

2 引用是变量的一个别名,即创建引用并不会致使新内存的分配,引用在初始化时必须存在一个对应的被引用的内存对象,且此引用关系创建后,引用不能再被改变,而所引用的变量是能够改变的。

3 引用在参数传递中使用,下降了传值传递的消耗。若是引用做为函数的返回值,那么函数能够做为一个左值来使用。这一点在C++的流操做部分很常见。

对于数组名和对数组名取地址

 int a[5]={1,2,3,4,5};
 int *p1=(int*)(&a+1);
 int *p2=(int*)((int)a+1);
 int *p3=(int*)(&a)+1;
    // p3=p3+1;
 printf("%x,%x,%x",p1[-1],*p2,p3[-1]);

执行结果为 5,2000000,1。对于结果5仍是比较纠结。看了一下网上的帖子和几本书,总结了一下。

 

首先说一下关于对数组名取地址:
        关于对数组名取地址的问题,因为数组名是右值,原本&array 是不合法的,早期很多编译器就是指定&array 是非法的,但后来C89/C99认为数组符合对象的语义,对一个对象取地址是合理的,所以,从维护对象的完整性出发,也容许&array 。只不过,&array 的意义并不是对一个数组名取地址,而是对一个数组对象取地址,也正由于如此,array 才跟&array 所表明的地址值同样,同时sizeof(array )应该跟sizeof(&array )同样,由于sizeof(&array )表明取一个数组对象的长度。

         要注意到 array 和 &array 的类型是不一样的。array为一个指针,而&array是指向数组int [100]的指针。array 至关于 &array[0],而 &array 是一个指向 int[100] 的指针,类型是 int(*)[100]。


另外从步长的角度分析这个问题
执行以下语句:
printf("array=%p, array+1=%p/n", array, array+1); 
printf("&array=%p, &array+1=%p/n", &array, &array+1);

结果为:
array=0012FDF0, array+1=0012FDF4     //+sizeof(int)
&array=0012FDF0, &array+1=0012FF80  //+sizeof(&array)

在《C专家编程》书中关于数组一章P203,有以下解释:    不管指针仍是数组,在连续的内存地址上移动时,编译器都必须计算每次前进的步长。    编译器自动把下标值调整到数组元素大小,对起始地址进行加法操做以前,编译器都会负责计算每次增长的步长,这就是为何指针类型老是有类型限制,每一个指针只能指向一种类型的缘由所在,由于编译器须要知道对指针进行解除引用操做时应该取几个字节,以及每一个小标的步长应取几个字节。    另外步长的自动调整还和上下语句相关: int *p3=(int*)(&a);      p3=p3+1;    首先对P3指针变量赋初值,指向数组int [5]的指针,而后对指针进行加一的操做,其中P3定义为一个指向int类型的指针,所以最终P3的值等价P3+sizeof(int) int *p3=(int*)(&array+1);    &array+1,步长为1,其中步长的长度和&array的类型匹配,即&array是指向数组int [100]的指针,因此&array+1等价为&array+sizeof(&array)   最终p1[-1]等价为*(P1-1),所以等价为第二个int [5]的数据首地址(并不存在第二个数组显然当前指针已经越界了,另外数组元素在内存中是连续存贮的)减去一个为sizeof(int)的步长,因此指向了第一个数组的最后1个元素。

相关文章
相关标签/搜索