《程序员面试宝典》读书笔记

C与C++的各自特点

C是结构化语言,重点在于算法和数据结构。C程序的设计首先考虑的是如何通过过程,对输入进行运算处理得到输出。对于C++,首先考虑如何构建对象模型,让模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程控制。

头文件中ifndef/define/endif作用

阻止该头文件被重复引用。

C++调用C编译后函数加上extern C

C++支持函数重载,而C语言不支持函数重载。函数被C++编译后在库中的名字与C语言有所不同。假设函数原型为 void foo(int i,int j),则C语言编译后在库中的名字为_foo,而在C++编译后的库中名字为_foo_int_int的名字。C++提供了连接交换指定符号extern“C”解决名字匹配问题。

malloc/free和new/delete

malloc与free是C/C++语言的标准函数库,new/delete是C++运算符。他们都可以动态申请和释放内存。对于非内部数据类型的对象,只用malloc无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡前自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限内,不能够把执行构造函数和析构函数的任务强加于malloc/free。

指针和引用的差别

  • 非空区别:在任何情况下都不能使用指向空值的引用。引用必须总是指向某个对象。不存在指向空值的引用意味着引用的代码效率比使用指针的效率要高。
  • 合法性区别:使用引用前不需要检测它的合法性。指针总是被检测,防止其为空。
  • 可修改区别:指针可以被重新赋值以指向另一个不同对象,但是引用总是在初始化时被指定的队形,以后不能改变,但是其内容可以改变。
  • 应用区别:在以下情况下应该使用指针:1、存在不指向任何对象的可能(指针设置为空)2、需要能够在不同时刻指向不同对象(改变指针指向)。

指针函数与函数指针

指针函数:指针函数是带指针的函数,即本质是一个函数。函数返回类型是某一类型指针。函数返回值必须用同类型的指针变量来接受。指针函数一定有函数返回值,而且在主调函数中,函数返回值必须赋给同类型的指针变量。

类型标识符 *函数名(参数表)//int* f(int x,int y)    

示例

    int * GetDate(int wk,int dy);

    main()
    {
        int wk,dy;
        do
        {
            printf(Enter week(1-5)day(1-7)\n);
            scanf(%d%d,&wk,&dy);
        }
        while(wk<1||wk>5||dy<1||dy>7);
        printf(%d\n,*GetDate(wk,dy));
    }

    int * GetDate(int wk,int dy)
    {
        static int calendar[5][7]=
        {
           {1,2,3,4,5,6,7},
           {8,9,10,11,12,13,14},
           {15,16,17,18,19,20,21},
           {22,23,24,25,26,27,28},
           {29,30,31,-1}
        };
        return &calendar[wk-1][dy-1];
    }

函数指针:函数指针是指向函数的指针变量,即本质是一个指针变量。

类型标识符 (*函数名)(参数表)//int (*f)(int x,int y)

指向函数的指针包含了函数的地址,可以通过它来调用函数。把函数地址赋值给指针,可以采用下面两种形式:

void (*fptr)();
fptr=&Function;
fptr=Function;

取地址运算符&不是必需的,因为单单一个函数标识符就标号表示了它的地址,如果是函数调用,还必须包含一个圆括号括起来的参数表。可以采用如下两种方式来通过指针调用函数:

x=(*fptr)();
x=fptr();

示例

    void (*funcp)();
    void FileFunc(),EditFunc();

    main()
    {
        funcp=FileFunc;
        (*funcp)();
        funcp=EditFunc;
        (*funcp)();
    }

    void FileFunc()
    {
        printf(FileFunc\n);
    }

    void EditFunc()
    {
        printf(EditFunc\n);
    }

    程序输出为:
        FileFunc
        EditFunc

参考来源:http://www.cnblogs.com/gmh915/archive/2010/06/11/1756067.html

32位机器与64位机器

32位编译器:
      char :1个字节
      char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
      short int : 2个字节
      int:  4个字节
      unsigned int : 4个字节
      float:  4个字节
      double:   8个字节
      long:   4个字节
      long long:  8个字节
      unsigned long:  4个字节
64位编译器:
      char :1个字节
      char*(即指针变量): 8个字节
      short int : 2个字节
      int:  4个字节
      unsigned int : 4个字节
      float:  4个字节
      double:   8个字节
      long:   8个字节
      long long:  8个字节
      unsigned long:  8个字节

*与++运算符优先级

pointer=*P++ 等价于pointer=*P;P++; pointer=*++P等价于P++;pointer=*P;

float value[] = {1,2,3,4,5};
float *vp;
vp = value;
for (int i = 0; i < 5;++i)
    cout << *vp++ << ", ";

程序的输出结果为1,2,3,4,5,若修改为*++vp,则结果为2, 3, 4, 5, -1.07374e+008

指针运算与数组

数组a[]的内存分配在栈上,可以通过数组名或指向数组的指针进行修改。而下面的指针p指向的是文字常量区的字符串,是不允许修改的,故通过指针修改错误。但是可以使用p[0]访问相应的元素。

char a[]="hello";
a[0]='x';
char* q=a;
q[0]='b';
char *p="hello";//将字符串的首地址装入指针变量,而不是将整个字符装入指针变量
p[0]='x';

指针数组与数组指针

指针数组:array of pointers,即用于存储指针的数组,也就是数组元素都是指针

数组指针:a pointer to an array,即指向数组的指针

int* a[4] 指针数组

表示:数组a中的元素都为int型指针

元素表示:*a[i] *(a[i])是一样的,因为[]优先级高于*

int (*a)[4] 数组指针

表示:指向数组a的指针

元素表示:(*a)[i]

注意:在实际应用中,对于指针数组,我们经常这样使用:

typedef int* pInt;
pInt a[4];

参考来源:http://www.cnblogs.com/Romi/archive/2012/01/10/2317898.html

指针运算在高维数组中的应用

事实上,C++没有提供高维数组类型。以二维数组(int a[4][5])为例,用户创建的二维数组其实是每个元素本身都是数组的数组。

  • a: int(*)[5],即a为指向数组a第0个元素a[0]的指针,且a为常量,不可进行赋值运算,a+i的类型也同为int(*)[5],指向a[i]; &a+1如图中所示,跳过4行5列共20个元素。
  • *a或a[0]: 类型为int*a为指向数组a[0]*首元素a[0][0]的指针。
  • *(a+1)或a[1]:类型为int*,因a的类型为int(*)[5],即a指向一个有5个元素的一维数组,故a+1跳过5个元素。即 *(a+1)或a[1]为指向数组a[1]首元素a[1][0]的指针。
  • *(*(a+1)+2):类型为int。(*(a+1)+2)为指向数组a[1]第二个元素a[1][2]的指针,即为数组a[1]的第2个元素a[1][2]

总结:

  • &a ==== int(*)[4][5]
  • a+i ==== int(*)[5]
  • *(a+i) ==== int*;
  • *(*(a+i)+j)=====int
  • *(a+i)=a[i];
  • *(*(a+i)+j)=*(a[i]+j)=a[i][j]

在下面的程序片段中

int a[]={1,2,3,4,5};
int *ptr=(int*)(&a+1);
cout<<*(ptr-1);//结果为5

由于&a+1的类型是int(*)[5],不是a+1。故&a+1使得指针跳过整个数组a的大小(5个int的大小)。ptr经过强制转换实际是&(a[5]),即a+5,所以ptr-1指向数组a的最后一个元素。输出结果为5。

this指针

  • this指针本质是一个函数参数,只是编译器隐藏起形式的,语法层面上的参数。this只能在成员函数中使用,全局函数、静态函数都不能使用this。
  • this在成员函数开始前构造,在成员的结束后清除。
  • this指针并不占用对象的空间。this相当于非静态成员函数的一个隐函的参数,不占对象空间。它跟对象之间没有包含关系,只是当前调用函数的对象被它指向而已。所有成员函数的参数,不管是不是隐含的,都不会占用对象的空间,只会占用参数传递时的栈空间,或者直接占用一个寄存器。
  • this指针只有在成员函数中才有定义。在获取一个对象后,也不能通过对象使用this指针。所以,我们无法知道一个对象的this指针位置(只有在成员函数里才有this指针的位置,可以通过&this获得)。

static

static关键字作用 - 函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只能被分配一次,因此其值在下次调用时仍维持上次的值。

  • 在模块内的static全局变量可以被模块内所有函数访问,但不能被模块外其他函数访问。
  • 在模块内的static函数只能被该模块内的其他函数调用,这个函数的使用范围被限制在它申明的模块内。
  • 在类中的static成员变量属于整个类所有,对类的所有对象只有一份拷贝。
  • 在类中的static成员函数属于整个类所有,该函数不接收this指针,因而只能访问类的static成员变量。

面向对象基本概念

面向对象设计的三原则:封装、继承、多态

  • Open-close principle (开闭原则)是面向对象设计的重要特性之一:软件对扩展是开放的,对修改应该是关闭的。
  • Defensive programming (防御式编程)只是一种编程技巧,与面向对象设计无关。主要思想是:子程序应该不因传入错误数据而被破坏,哪怕是由其他子程序产生的错误数据。这种思想是将可能出现的错误造成的影响控制在有限的范围内。

虚函数

  • 虚函数采用虚调用的方法。虚调用是一种可以在只有部分信息的情况下工作的机制,特别允许我们调用一个只知道接口而不知道其准确对象类型的函数。但是如果要创建一个对象,必须知道对象准确类型,因此构造函数不能为虚。
  • 虚函数是有代价的。由于每个虚函数的对象都必须维护一个V表,因此在使用虚函数的时候会产生一个系统开销。如果仅是一个很小的类,且不想派生出其他类,根本没必要使用虚函数。

虚函数在vtable中占了一个表项,保存着一条跳转到它的入口地址的指令。当一个包含虚函数的对象被创建时,它的头部附加一个指针,指向vtable中相应的位置。调用虚函数的时候,不管用什么指针调用,根据vtable找到入口地址再执行,从而实现“动态联编”。而不像普通函数那样简单地跳转到一个固定地址。

友元

友元是一种定义在类外部的普通函数,但它需要在类体内进行说明,为了与该类的成员函数加以区别,在说明时前面加以关键字friend。友元的作用在于提高程序的运行效率,但是它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。

虚继承

虚拟继承是为解决多重继承而出现的。

虚指针

虚指针或虚函数指针是一个虚函数的实现细节。带有虚函数的类中的每一个对象都有一个虚指针指向该类的虚函数表。

C++额外开销

  • 编译时开销:模板、类型层次结构、强类型检查等新特性,以及大量使用了这些新特性的C++模板、算法库都明显的增加了C++编译器的负担。但是应当看到,这些新技能在不增加程序执行效率的前提下,明显降低广大C++程序员的工作量。
  • 运行时开销:
    • 虚基类
    • 虚函数
    • RTTI(Runtime Type Information) (dynamic_cast和typeid)
    • 异常
    • 对象的构造和析构
posted @ 2016-04-02 10:37  弦断  阅读(860)  评论(0编辑  收藏  举报