算法--基本问题小记1

说明:以下摘自《程序员面试宝典(第二版)》,《Linux C编程实战》,自己和同学的面试体验等

还有参照博客:http://zhedahht.blog.163.com/


1)float变量x与0比较

if((x >= -0.00001) && ( x <= 0.00001))

说明:不能用==或!= 0.0

 

2)随机数生成:   头文件stdlib.h, time.h

srand(time(NULL));

rand() % NUMBER;

 

3)静态变量static

限制变量的作用域

设置变量的存储域

详细解释:

静态变量(以static作为修饰符的变量)分为两种:全局静态变量和局部静态变量。全局静态变量是在所有函数之外定义的静态变量,局部静态变量是在某个函数内定义的变量。静态变量存储在内存的静态存储区(bss),静态存储区在程序的整个运行期间都存在。未经过初始化的静态变量会被程序自动初始化为0(自动对象的值是任意的,除非被显示初始化)。全局静态变量的作用域是从定义开始到文件结尾,全局静态变量对其他文件是不可见的。而局部静态变量只在定义它的函数内有效。 

4)strlen函数计算首地址到'\0'间的元素数目,不包括'\0';

因此malloc(strlen(str) + 1);

例子:

char a[10];

int len = strlen(a);    错误:因为a没有初始化,strlen找不到'\0'

 

sizeof输出字符串长度包括'\0'

32位机上:

int a[100], *p = a;

sizeof(a) = 400;

sizeof(p) = 4;

 

5)int a[5];

   int *ptr = (int *)(&a + 1);

ptr地址为a[5],&a+1加了一个数组的偏移

 

6)数组长度求法

sizeof(table)/sizeof(table[0])

 

7)#define MAX 255

unsigned char a[MAX], i;

for(i = 0; i <= MAX; i++)

    a[i] = i;

错误:

a)数组越界

b)i是unsigned char,255是最大值,死循环

 

8)int **p, arr[100];

p = &arr;

错误:

&arr是指向长度100的数组的指针,类型不符

修正:

int **p, *q;

int arr[100];

q = arr;

p = &q;

 

9)union data1{

double d;

int i;

char c1;

char c2[9];

}

sizeof(data1) = 16   以8对齐,因为double为8

如果data1是struct,为24.  8 + 4 + 1 + 9 = 22,以8对齐为24


struct inner{

char c1;

double d;

char c2;

}

sizeof(inner) = 24


struct inner1{

char c1;

char c2;

double d;

}

sizeof(inner1) = 16


struct inner2{

struct inner1 t1;

int i;

char c;

}

sizeof(inner2) = 32    是以8对齐


10)频繁使用的短小函数,最好用宏定义


11)运行如下的C++代码,输出是什么?

class A

{

public:

    virtual void Fun(int number = 10)

    {

        std::cout << "A::Fun with number " << number;

    }

};

 

class B: public A

{

public:

    virtual void Fun(int number = 20)

    {

        std::cout << "B::Fun with number " << number;

    }

};

 

int main()

{

    B b;

    A &a = b;

    a.Fun();

}


答案:输出B::Fun with number 10

由于a是一个指向B实例的引用,因此在运行的时候会调用B::Fun。但缺省参数是在编译期决定的。在编译的时候,编译器只知道a是一个类型a的引用,具体指向什么类型在编译期是不能确定的,因此会按照A::Fun的声明把缺省参数number设为10

这一题的关键在于理解确定缺省参数的值是在编译的时候,但确定引用、指针的虚函数调用哪个类型的函数是在运行的时候。


12)运行如下的C代码,输出是什么?

char* GetString1()

{

    char p[] = "Hello World";

    return p;

}

 

char* GetString2()

{

    char *p = "Hello World";

    return p;

}

 

 

int _tmain(int argc, _TCHAR* argv[])

{

    printf("GetString1 returns: %s. \n", GetString1());

    printf("GetString2 returns: %s. \n", GetString2());

 

    return 0;

}


答案:输出两行,第一行GetString1 returns: 后面跟的是一串随机的内容,而第二行GetString2 returns: Hello World. 两个函数的区别在于GetString1中是一个数组,而GetString2中是一个指针

当运行到GetString1时,p是一个数组,会开辟一块内存,并拷贝"Hello World"初始化该数组。接着返回数组的首地址并退出该函数。由于pGetString1内的一个局部变量,当运行到这个函数外面的时候,这个数组的内存会被释放掉。因此在_tmain函数里再去访问这个数组的内容时,结果是随机的

当运行到GetString2时,p是一个指针,它指向的是字符串常量区的一个常量字符串。该常量字符串是一个全局的,并不会因为退出函数GetString2而被释放掉。因此在_tmain中仍然根据GetString2返回的地址得到字符串"Hello World"


13)运行下图中C代码,输出的结果是什么

int _tmain(int argc, _TCHAR* argv[])

{

    char str1[] = "hello world";

    char str2[] = "hello world";

 

    char* str3 = "hello world";

    char* str4 = "hello world";

 

    if(str1 == str2)

        printf("str1 and str2 are same.\n");

    else

        printf("str1 and str2 are not same.\n");

 

    if(str3 == str4)

        printf("str3 and str4 are same.\n");

    else

        printf("str3 and str4 are not same.\n");

 

    return 0;

}


答案输出两行。第一行是str1 and str2 are not same,第二行是str3 and str4 are same

str1str2是两个字符串数组。我们会为它们分配两个长度为12个字节的空间,并把"hello world"的内容分别拷贝到数组中去。这是两个初始地址不同的数组,因此比较str1str2的值,会不相同str3str4是两个指针,我们无需为它们分配内存以存储字符串的内容,而只需要把它们指向"hello world“在内存中的地址就可以了。由于"hello world”是常量字符串,它在内存中只有一个拷贝,因此str3str4指向的是同一个地址。因此比较str3str4的值,会是相同的


14)运行下图中的C++代码,输出是什么

#include <iostream>

 

class A

{

private:

        int n1;

        int n2;

public:

        A(): n2(0), n1(n2 + 2)

        {

        }

 

        void Print()

        {

                std::cout << "n1: " << n1 << ", n2: " << n2 << std::endl;

        }

};

 

int _tmain(int argc, _TCHAR* argv[])

{

        A a;

        a.Print();

 

        return 0;

}


答案:输出n1是一个随机的数字,n20。在C++中,成员变量的初始化顺序与变量在类型中的申明顺序相同,而与它们在构造函数的初始化列表中的顺序无关。因此在这道题中,会首先初始化n1,而初始n1的参数n2还没有初始化,是一个随机值,因此n1就是一个随机值。初始化n2时,根据参数0对其初始化,故n2=0


15)编译运行下图中的C++代码,结果是什么?(A)编译错误;(B)编译成功,运行时程序崩溃;(C)编译运行正常,输出10。请选择正确答案并分析原因

#include <iostream>

 

class A

{

private:

        int value;

 

public:

        A(int n)

        {

                value = n;

        }

 

        A(A other)

        {

                value = other.value;

        }

 

        void Print()

        {

                std::cout << value << std::endl;

        }

};

 

int _tmain(int argc, _TCHAR* argv[])

{

        A a = 10;

        A b = a;

        b.Print();

 

        return 0;

}


答案:编译错误。在复制构造函数中传入的参数是A的一个实例。由于是传值,把形参拷贝到实参会调用复制构造函数。因此如果允许复制构造函数传值,那么会形成永无休止的递归并造成栈溢出。因此C++的标准不允许复制构造函数传值参数,而必须是传引用或者常量引用。在Visual StudioGCC中,都将编译出错。


16)运行下图中代码,输出的结果是什么?这段代码有什么问题

#include <iostream>

 

class A

{

public:

        A()

        {

                std::cout << "A is created." << std::endl;

        }

 

        ~A()

        {

                std::cout << "A is deleted." << std::endl;

        }

};

 

class B : public A

{

public:

        B()

        {

                std::cout << "B is created." << std::endl;

        }

 

        ~B()

        {

                std::cout << "B is deleted." << std::endl;

        }

};

 

int _tmain(int argc, _TCHAR* argv[])

{

        A* pA = new B();

        delete pA;

 

        return 0;

}


答案:输出三行,分别是:A is created. B is created. A is deleted。用new创建B时,回调用B的构造函数。在调用B的构造函数的时候,会先调用A的构造函数。因此先输出A is created. B is created.

接下来运行delete语句时,会调用析构函数。由于pA被声明成类型A的指针,同时基类A的析构函数没有标上virtual,因此只有A的析构函数被调用到,而不会调用B的析构函数。

由于pA实际上是指向一个B的实例的指针,但在析构的时候只调用了基类A的析构函数,却没有调用B的析构函数。这就是一个问题。如果在类型B中创建了一些资源,比如文件句柄、内存等,在这种情况下都得不到释放,从而导致资源泄漏。


17)运行下列C++代码,输出什么?

struct Point3D

{

        int x;

        int y;

        int z;

};

 

int _tmain(int argc, _TCHAR* argv[])

{

        Point3D* pPoint = NULL;

        int offset = (int)(&(pPoint)->z);

 

        printf("%d", offset);

        return 0;

}


答案:输出8。由于在pPoint->z的前面加上了取地址符号,运行到此时的时候,会在pPoint的指针地址上加z在类型Point3D中的偏移量8。由于pPoint的地址是0,因此最终offset的值是8

&(pPoint->z)的语意是求pPoint中变量z的地址(pPoint的地址0z的偏移量8),并不需要访问pPoint指向的内存。只要不访问非法的内存,程序就不会出错。


18)运行下列C++代码,输出什么?

class A

{

public:

        A()

        {

                Print();

        }

        virtual void Print()

        {

                printf("A is constructed.\n");

        }

};

 

class B: public A

{

public:

        B()

        {

                Print();

        }

 

        virtual void Print()

        {

                printf("B is constructed.\n");

        }

};

 

int _tmain(int argc, _TCHAR* argv[])

{

        A* pA = new B();

        delete pA;

 

        return 0;

}


答案:先后打印出两行:A is constructed. B is constructed. 调用B的构造函数时,先会调用B的基类及A的构造函数。然后在A的构造函数里调用Print。由于此时实例的类型B的部分还没有构造好,本质上它只是A的一个实例,他的虚函数表指针指向的是类型A的虚函数表。因此此时调用的PrintA::Print,而不是B::Print。接着调用类型B的构造函数,并调用Print。此时已经开始构造B,因此此时调用的PrintB::Print

同样是调用虚拟函数Print,我们发现在类型A的构造函数中,调用的是A::Print,在B的构造函数中,调用的是B::Print。因此虚函数在构造函数中,已经失去了虚函数的动态绑定特性。


19)我们可以用static修饰一个类的成员函数,也可以用const修饰类的成员函数(写在函数的最后表示不能修改成员变量,不是指写在前面表示返回值为常量)。请问:能不能同时用staticconst修饰类的成员函数?

分析:答案是不可以。C++编译器在实现const的成员函数的时候为了确保该函数不能修改类的实例的状态,会在函数中添加一个隐式的参数const this*。但当一个成员为static的时候,该函数是没有this指针的。也就是说此时static的用法和static是冲突的。

我们也可以这样理解:两者的语意是矛盾的。static的作用是表示该函数只作用在类型的静态变量上,与类的实例没有关系;而const的作用是确保函数不能修改类的实例的状态,与类型的静态变量没有关系。因此不能同时用它们。


20)运行下面的代码,输出是什么?

class A

{

};

 

class B

{

public:

        B() {}

        ~B() {}

};

 

class C

{

public:

        C() {}

        virtual ~C() {}

};

 

int _tmain(int argc, _TCHAR* argv[])

{

        printf("%d, %d, %d\n", sizeof(A), sizeof(B), sizeof(C));

        return 0;

}


分析:答案是1, 1, 4class A是一个空类型,它的实例不包含任何信息,本来求sizeof应该是0。但当我们声明该类型的实例的时候,它必须在内存中占有一定的空间,否则无法使用这些实例。至于占用多少内存,由编译器决定。Visual Studio 2008中每个空类型的实例占用一个byte的空间。

class Bclass A的基础上添加了构造函数和析构函数。由于构造函数和析构函数的调用与类型的实例无关(调用它们只需要知道函数地址即可),在它的实例中不需要增加任何信息。所以sizeof(B)sizeof(A)一样,在Visual Studio 2008中都是1

class Cclass B的基础上把析构函数标注为虚拟函数。C++的编译器一旦发现一个类型中有虚拟函数,就会为该类型生成虚函数表,并在该类型的每一个实例中添加一个指向虚函数表的指针。在32位的机器上,一个指针占4个字节的空间,因此sizeof(C)4


21)运行下面中的代码,得到的结果是什么

class A

{

private:

        int m_value;

 

public:

        A(int value)

        {

                m_value = value;

        }

        void Print1()

        {

                printf("hello world");

        }

        void Print2()

        {

                printf("%d", m_value);

        }

};

 

int _tmain(int argc, _TCHAR* argv[])

{

        A* pA = NULL;

        pA->Print1();

        pA->Print2();

 

        return 0;

}


分析:答案是Print1调用正常,打印出hello world,但运行至Print2时,程序崩溃。调用Print1时,并不需要pA的地址,因为Print1的函数地址是固定的。编译器会给Print1传入一个this指针,该指针为NULL,但在Print1中该this指针并没有用到。只要程序运行时没有访问不该访问的内存就不会出错,因此运行正常。在运行print2时,需要this指针才能得到m_value的值。由于此时this指针为NULL,因此程序崩溃了。


22)静态成员函数能不能同时也是虚函数

分析:答案是不能。调用静态成员函数不要实例。但调用虚函数需要从一个实例中指向虚函数表的指针以得到函数的地址,因此调用虚函数需要一个实例。两者相互矛盾。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值