《程序员面试笔试宝典》学习笔记(五)程序设计基础

来源:互联网 发布:cad 网络拓扑图 编辑:程序博客网 时间:2024/05/18 02:40

十、变量

1、全局变量和静态变量有什么异同

全局变量的作用域是整个程序,它只需要在一个源文件中定义,就可以作用于所有的源文件,其他不包含全局变量定义的源文件需要用extern 关键字再次声明这个全局变量。若某一个局部重新定义了这个变量,则全局变量作用域是除了这个局部外的整个程序,它的生命期与程序生命期一样长。

静态局部变量具有局部的作用域,只被初始化一次,自从第一次被初始化直到程序运行结束都一直存在。它和全局变量的区别在于全局变量对所有的函数都是可见的,而静态局部变量只对定义自己的函数体始终可见。

静态全局变量也具有全局作用域每次调用都会进行初始化。它与全局变量的区别在于如果程序包含多个文件的话,它的作用域仅限于定义它的文件里,不能作用于其他文件里。

也就是:全局变量的作用域是整个程序,所有源文件可见;加static 变成静态全局变量作用于仅限于定义该变量的文件中;静态局部变量的作用域仅限于定义它的函数体中。

将局部变量改变为静态变量后改变了它的存储方式,即改变了它的生成期;把全局变量改变为静态变量后市改变了它的作用域,限制了它的适用范围。

全局变量、静态局部变量与静态全局变量都在静态存储区分配空间,而局部变量在栈上分配空间。

2、局部变量需要“避讳”全局变量吗

局部变量可以与全局变量重名,但是局部变量会屏蔽全局变量。

具体区别:
1)全局变量的作用域为这个程序块,而局部变量的作用域为当前函数
2)内存存储方式不同,全局变量分配在全局数据区后者分配在栈区
3)生命周期不同。全局变量随主程序创建而创建,随主程序销毁而销毁,局部变量在局部函数内部,甚至局部循环体等内部存在退出就不存在了。
d、使用方式不同。通过声明后全局变量程序的各个部分都可以使用到,局部变量只能在局部使用。

注意:局部变量不可以赋值为同名全局变量。

4、变量定义与变量声明有什么区别

定义为变量分配存储空间,还可以为变量指定初始值。在一个程序中,变量有且仅有一个定义。

声明是指向程序表明变量的类型和名字。可以多次声明。

5、不使用第三方变量,如何交换两个变量的值

1)算术法

a = a+b;  b=a-b; a=a-b;

2)异或法

a=a^b;  b=a^b; a=a^b

十一、字符串

1、不使用C/C++字符串库函数,如何自行编写strcpy()函数

char *strcpy(char *strDest, const char *strSrc)  {      assert((strDest != NULL) && (strSrc != NULL));      if(strDest == strSrc)   return strDest;      char *address = strDest;      while( (*strDest++ = *strSrc++ ) != '\0')      ;      return address;  }  

2、如何把字符串转换为数字

class Solution {public:    int myAtoi(string str) {        int n=str.size();        if(n==0) return 0;        int i=0;        while(i<n && str[i]==' ')        {//(1)过滤掉前面的空格            i++;            if(i==n-1) return 0;        }        int sign=1;        if(str[i]=='+' || str[i]=='-')        {//(2判断正负,只能处理一个正负号            sign = (str[i]=='-')?-1:1;            i++;        }        long long int num=0;//定义为long long int类型,后边进行溢出处理!!!        while(i<n)        {            if(str[i]>='0' && str[i]<='9')            {                int d=str[i] - '0';                num = num*10 + d;                if(num>INT_MAX)                {//溢出处理                    return sign>0?INT_MAX:INT_MIN;                }            }            else break;//其他字符            i++;        }        return num*sign;    }};

3、如何自定义内存复制函数memcpy()

char *my_memcpy(char *dst, const char* src, int cnt){    assert(dst != NULL && src != NULL);    char *ret = dst;    if (dst >= src && dst <= src+cnt-1) //内存重叠,从高地址开始复制    {        dst = dst+cnt-1;        src = src+cnt-1;        while (cnt--)            *dst-- = *src--;    }    else    //正常情况,从低地址开始复制    {        while (cnt--)            *dst++ = *src++;    }    return ret;}

十二、编译

1、编译和链接的区别是什么

一般由源代码变成可执行的程序需要经过三个过程:编译、链接、载入

1)编译:将预处理生成的文件,经过词法分析、语法分析、语义分析、以及优化后编译成若干个目标模块。可以理解为将高级语言翻译为计算机可以理解的二进制代码,即机器语言。

2)链接:由链接程序将编译后形成的一组目标模块以及它们所需要的库函数链接在一起,形成一个完整的载入模型。链接主要解决的模块间的相互引用问题,分为地址和空间分配,符号解析和重定位几个步骤。在编译阶段生成目标文件时,会暂时搁置那些外部引用,而这些外部引用就是在链接时进行确定的,链接器在链接时,会根据符号名称区相应模块中寻找对应符号。带符号确定后,链接器会重写之前那些未确定符号的地址,这个过程就是重定位链接一般分为静态链接、载入时动态链接以及运行时动态链接3种。

3)载入:由载入程序将载入模块载入内存。

十三、面向对象相关

1、面向对象与面向过程有什么区别

1)面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;
2)面向对象把构成问题事物分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

例如五子棋,面向过程的设计思路就是首先分析问题的步骤:1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤2,9、输出最后结果。把上面每个步骤用不同的方法来实现。

面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为1、黑白双方,这两方的行为是一模一样的,2、棋盘系统,负责绘制画面,3、规则系统,负责判定诸如犯规、输赢等。第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。

面向对象是以功能来划分问题,而不是步骤。同样是绘制棋局,这样的行为在面向过程的设计中分散在了多个步骤中,很可能出现不同的绘制版本,因为通常设计人员会考虑到实际情况进行各种各样的简化。而面向对象的设计中,绘图只可能在棋盘对象中出现,从而保证了绘图的统一。

2、面向对象的基本特征有哪些

1)封装:将客观事物抽象成类,每个类对自身的数据和方法实行保护,实现信息隐藏。

2)继承:可以使用现有类的所有功能,而不需要重新编写原来的类,它的目的是为了进行代码复用和支持多态。一般有三种形式:实现继承、可视继承、接口继承。

3)多态:指同一个实体同时具有多种形式,简单的说,就是允许子类类型的指针赋值给父类类型的指针。

3、什么是深拷贝?什么是浅拷贝

如果一个类拥有资源(堆或者其他系统资源),当这个类的对象发生复制过程时,资源重新分配,这个过程就是深拷贝

反之,对象存在资源,但复制过程中未复制资源的情况视为浅拷贝
更多解释见http://blog.csdn.net/will130/article/details/48897725

4、什么是友元

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

5、复制构造函数与赋值运算符的区别是什么

复制构造函数完成一些基于同一类额其他对象的构建及初始化工作,目的是建立一个新的对象实体,新创建的对象有独立的内存空间,而不是和先前对象共用。

具体而言,两者有以下不同:
1)复制构造函数生成新的类对象,赋值运算符不能。
2)由于复制构造函数是直接构造一个新的类对象,所以在初始化这个对象之前不用检验源对象是否和新对象相同。赋值运算符则需要这个操作,另外赋值运算中如果原来的对象有内存分配,要先把内存释放掉。
3)当类中有指针类型的成员变量时,一定要重写复制构造函数和赋值构造函数,不能使用默认的。

总结如下:
1)当进行一个类的实例初始化,也就是构造时,调用构造函数。
2)当用其他实例来初始化时,调用复制构造函数。
3)非初始化时对这个实例进行赋值调用的是赋值运算符。

6、基类的构造函数/析构函数是否能被派生类继承

既然子类扩充了自己的成员 那在构造的时候就要对自己的成员初始化,父类不会对子类初始化,所以子类要有自己的构造函数。同理析构。

1)构造函数和析构函数不能被继承。构造函数和析构函数是用来处理对象的创建和析构的,它们只知道对在它们的特殊层次的对象做什么。所以,在整个层次中的所有的构造函数和析构函数都必须被调用而不能被继承。
2)子类的构造函数会显示的调用父类的构造函数或隐式的调用父类的默认的构造函数进行父类部分的初始化。
3)析构函数也一样。它们都是每个类都有的东西,如果能被继承,那就没有办法初始化了。

7、初始化列表和构造函数初始化的区别是什么

1、初始化和赋值对内置类型的成员没有什么大的区别。
2、对于非内置类型的成员变量,推荐用构造函数初始化列表(避免2次构造。初始化参数列表在对象初始化时对成员变量赋值一次;构造函数对成员变量赋值两次,一次是对象构造是用默认值进行赋值,第二次是调用构造函数赋值)。

下列情况必须用带有初始化列表的构造函数:
(1) 成员类型是没有默认构造函数的类。若没有提供显式初始化时,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
(2) const成员或引用类型的成员。因为const对象或引用类型只能初始化,不能对他们赋值。
(3)基类的构造函数都需要初始化列表。

注意:还有一个赋值次数,效率上的区别,初始化参数列表在对象初始化时对成员变量赋值一次,构造函数内直接赋值,对成员变量赋值两次,一次是对象构造是用默认值进行赋值,第二次是调用构造函数赋值

8、类的成员变量的初始化顺序是按照声明顺序吗

c++中,类的成员变量的初始化顺序只与声明顺序有关,与初始化列表中的顺序无关。具体顺序如下:
1)基类的静态变量或全局变量
2)派生类的静态变量或全局变量
3)基类的成员变量
4)派生类的成员变量

9、当一个类为另一个类的成员变量时,如何对其进行初始化

对于类对象数据成员应使用成员初始化列表进行初始化。

10、C++ 能设计实现一个不能不继承的类吗

使用友元、私有构造函数、虚继承等方式可以使一个类不能被继承!!!,可是为什么必须是虚继承?背后的原理又是什么?

在Java 中定义了关键字final,被final修饰的类不能被继承。

在C++中,子类的构造函数会自动调用父类的构造函数。同样,子类的析构函数也会自动调用父类的析构函数。要想一个类不能被继承,只要把它的构造函数和析构函数都定义为私有函数。那么当一个类试图从它那继承的时候,必然会由于试图调用构造函数、析构函数而导致编译错误。

可是这个类的构造函数和析构函数都是私有函数了,怎样才能得到该类的实例呢?可以通过定义静态来创建和释放类的实例。基于这个思路,代码如下:

class FinalClass1{public :      static FinalClass1* GetInstance()      {            return new FinalClass1;      }       static void DeleteInstance( FinalClass1* pInstance)      {            delete pInstance;            pInstance = 0;      } private :      FinalClass1() {}      ~FinalClass1() {}};

这个类是不能被继承,但使用起来也有点不方便:只能得到位于堆上的实例,需要程序员手动释放,而得不到位于栈上实例。

若要实现一个和一般类除了不能被继承之外其他用法都一样的类,代码如下:

template <typename T> class MakeFinal{      friend T; private :      MakeFinal() {}      ~MakeFinal() {}}; class FinalClass2 : virtual public MakeFinal<FinalClass2>{public :      FinalClass2() {}      ~FinalClass2() {}};

FinalClass2这个类使用起来和普通类没有区别,可以在栈上、也可以在堆上创建实例尽管类MakeFinal<FinalClass2>的构造函数和析构函数都是私有的,但由于类FinalClass2是它的友元函数,因此在FinalClass2中调用MakeFinal<FinalClass2>的构造函数和析构函数都不会造成编译错误。

但当试图从FinalClass2继承一个类并创建它的实例时,却不能通过编译。!!!!!

class Try : public FinalClass2{public :      Try() {}      ~Try() {}};Try temp; 

由于类FinalClass2是从类MakeFinal<FinalClass2>虚继承(!!!!)过来的,在调用Try的构造函数的时候,会直接跳过FinalClass2而直接调用MakeFinal<FinalClass2>的构造函数。非常遗憾的是,Try不是MakeFinal<FinalClass2>的友元,因此不能调用其私有的构造函数。!!!!

基于上面的分析,试图从FinalClass2继承的类,一旦实例化,都会导致编译错误,因此是FinalClass2不能被继承。这就满足了设计要求。

十四、虚函数

1、什么是虚函数

指向基类的指针在操作它的多态类对象时,会根据不同的类对象调用相应的函数,这个函数就是虚函数,用virtual修饰。

虚函数的作用就是实现多态性:在程序的运行阶段动态的选择合适的成员函数。

可以在基类的派生类中对虚函数重新定义,且要保持相同的形参个数和类型,以实现统一接口。注意以下几点:
1)只需在声明函数的类体中使用virtual将函数声明为虚函数,定义时不需再使用。
2)基类声明虚函数后,派生类的同名函数自动为虚函数。
3)声明了某个成员函数为虚函数后,该类中不能再出现函数名、参数个数、类型和返回值都形同的非虚函数。其子类中也不能。
4)非类的成员函数不能定义为虚函数,全局函数,以及静态成员函数和构造函数也不能。
5)基类的析构函数应定义为虚函数,以防止实现多态时内存泄露。

虚函数通过一张虚函数表实现。该表是一个类的虚函数地址表(各表项为指向对应虚函数的指针),当父类指针操作子类对象时,这张虚表指明了实际所应该调用的函数(动态联编的过程)!!!

2、c++如何实现多态

c++通过虚函数实现多态。*C++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。这是一种迟绑定技术。*

3、c++中的多态种类

1)参数多态:采用参数化模板,通过给出不同的类型参数,使得一个结构有多种类型。
2)引用多态:又称包含多态,同样的操作可用于一个类型及其子类型。
3)过载多态:同一个名(操作符﹑函数名)在不同的上下文中有不同的类型。
4)强制多态:把操作对象的类型强行加以变换,以符合函数或操作符的要求。

0 0
原创粉丝点击