C++期末复习知识点

来源:互联网 发布:python spark 环境搭建 编辑:程序博客网 时间:2024/05/18 01:37

1.  面向对象技术有哪些特点?(7条)

a)   模块性:对象是一个功能和数据独立的单元,相互间只能通过对象认可的途径进行通信,可重用。

b)   封装性:为信息隐蔽提供具体的实现手段,用户只需了解功能,不必清楚内部细节

c)   代码共享:可以避免代码的重复设计

d)   灵活性:对象可以根据自身的特点进行功能实现

e)   易维护性:对象实现抽象和封装后,使可能出现的错误基本限制在自身内部,易于检错和修改

f)   增量性设计:面向对象系统可以提供继承机制不断扩充功能,而不影响原有软件的运行

g)   局部存储与分布处理:每个对象通过数据抽象和数据隐蔽将其内容和状态置于自身独立的存储结构里。对象的处理是自治的,由对象构成的系统处理是分布式的

2.  面向对象程序设计与大型程序设计有哪些关系?

•    大型程序是根据待解决问题的复杂度来判定。

–  大型程序必须由多人合作完成,因此进行大型程序设计的管理具有复杂性

–  大型程序有大量的系统状态,这对测试系统的正确性带来极大的困难

•    大型程序的实现要求

–  正确性

–  易维护性

–  可读性

–  可重用性

•    模块分解

–  基于功能的模块分解(横向)。依据流程图,以数据为模块的界面

–  基于数据抽象的模块分解(纵向)。依据信息隐蔽,用数据上的操作为界面

•     软件系统设计=大型程序设计+小型系统设计

前者解决模块界面复杂,后者控制模块内部的复杂

•    面向对象的设计方法

软件系统设计=面向对象设计+面向对象程序设计

3.  面向对象设计方法与其它设计方法的比较?

横向比较:

–  函数程序设计:将计算过程看作函数作用过程

–  逻辑程序设计:将计算过程看作推演过程

–  面向对象程序设计:将计算过程看作分类加状态变换的过程

纵向比较:

      和结构化程序设计比较。

结构化程序设计强调功能抽象和模块性,将解决问题的过程看作是一个处理过程;而面向对象程序设计综合了功能抽象和数据抽象,将解决问题的过程看作是一个分类演绎过程。

–  模块与对象:

–  过程调用与消息传递:

–  类型与类:

–  静态连接与动态连接:

 

4.  解释以下概念:

对象:对象就是我们认识世界的基本单元,整个世界就是形形色色的对象组成。它是逻辑实体,包括数据和完成处理所需要的代码。程序=对象+消息

消息:就是对象之间相互请求或相互协作的途径。消息只说明操作的功能而不说明操作如何实现

封装:对象是类的实例,对象的所有数据和对这些数据的操作封装在一起,外部对象只有通过给它发消息来改变或得到这些数据。

协议:协议由一个对象能够接受并且愿意接受的所有消息构成的对外接口。其它对象只能向该对象发协议中所提供的消息;

类:对一组相似对象的共同抽象描述,它将该组对象所具有的的共同特征(包括操作特征和存储特征)结合在一起,用于说明该组对象的能力和性质。

继承:是一种现实世界对象之间独特的关系,它使得某类对象可以继承另外一类对象的特征和能力.

5.  对象的特点?(5条)

答:自主性:对象具有处理能力

•     封闭性:对象具有信息隐蔽能力

•     交互性:对象之间具有通信能力

•     被动性:对象的处理工作是由外部对象发送消息来启动

•     动态性:对象可以动态创建和取消,对象状态是不断变化的

  五大特征中,前三项是对象的能力,被动性刻化了对象的活动特性,动态性指出了对象的生存特性。

6.  什么是实例?

答:任何单个对象都是某个类的实例。一个类的所有实例都采用同样的方法处理消息,但每个实例又有自己的私有存储单元

类与实例的关系是什么?

答:是抽象和具体的关系:

–  类的所有实例能响应的消息模式相同,且采用同样的方法完成消息所要求的操作

–  类的所有实例具有相同的数据结构,且采用相同的名字来引用

        实例是类的具体化

7.  对象间的关系有哪几种?

答:对象间的创建关系:一个对象可以通过方法创建一个或多个对象

对象间的聚合关系:实际是一种包含关系

对象通讯关系:消息流图描述系统中对象间的消息流,外向消息流,内向消息流

实例化关系:对象是一个类的实例

8.  解释以下概念:

多态:意味着一个对象有着多重特征,可以在特定的情况下,表现不同的状态,从而对应着不同的属性和方法。多态即一名多用,也即同一消息可以根据发送消息对象的不同采用多种不同的行为方式。

作用域和语景:作用域和语景是对同一问题的两个不同观点。

–  作用域是限定一个名字的可用性代码范围,就是这个名字的作用域,提高程序逻辑的局部性,增强程序的可靠性,减少名字冲突

–  语景是程序中某处所有可访问对象的集合

深拷贝:是得到一个对象的副本的操作,有新对象产生

浅拷贝:只复制对象的基本类型,对象类型,仍属于原来的引用,是得到一个对象的指针的操作

9.  强类型与弱类型的区别在哪?它们的代表语言?

弱类型系统的目标是让所有信息的语景尽可能一般化,而强类型系统的目标是让所有信息的语景尽能特殊化。编译时,弱类型系统不做变量的类型检查,到运行时才进行类型匹配;强类型则要对所有变量和表达式进行类型匹配检查。

优缺点:弱类型使得系统非常灵活,且易扩充,但又易导致草率编码,还会导致整个程序很难高效。强类型依靠编译器检查代码的合法性,程序的所有代码操作的数据都有清晰的定义,但其生成的程序灵活性差不易修改。

区别:强类型,意味着必须事先声明变量,并且该变量只可用于表示一种类型的数据(例如或者一个整数或者一个字符串)。弱类型,即一个变量不必声明其类型,一个被用于表示字符串的变量在后面的程序中也可用来表示数字;

代表语言:C ,c#,java是强类型语言,VBScript, PHP弱类型语言

10. 引用:引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。引用必须要初始化。

int a; int &ra=a; //定义引用ra,它是变量a的引用,即别名

ra=1; 等价于 a=1;

且不能再把该引用名作为其他变量名的别名。它本身不是一种数据类型,引用本身不占存储单元

不能建立数组的引用。因为数组是一个由若干个元素所组成的集合,所以无法建立一个数组的别名

常引用声明方式:int a ;

constint &ra=a;// 不能通过引用对目标变量的值进行修改

ra=1; //错误

a=1; //正确

假设有如下函数声明:

stringfoo( );

voidbar(string & s);

  那么下面的表达式将是非法的:

bar(foo());

bar("helloworld");

  原因在于foo( )和"hello world"串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。

 

const:常量限定修饰符。const对象必须初始化而且是在定义的同时。初始化后的const对象(或指针)是不能修改的。

可修饰常数量、变量(包括指针常量和引用变量)和函数。

常量指针:intconst *p; 或 const int *p 指针p可变,指针指向的值*p不可变

指针常量:int *const p 指针p不可变,指针所指向的值*p可变

指向常量的常量指针:int const *const p

const修饰成员函数, 这个成员函数不会改变类的状态(即类的私有数据) int Fun() const;

 

静态成员:是那些与类本身有关的成员数据和成员函数,而不是与该类对象相关的成员数据和成员函数。

静态数据成员只有一个实例存在,静态方法与类相关

静态数据成员不能在类中初始化,也不能在类的构造函数中初始化该成员

一般形式:

    数据类型类名::静态数据成员名=初值 如:inttest::num = 10;

注意:不能用参数初始化表对静态成员初始化。一般系统缺省初始为0。

静态成员函数不能调用非静态数据成员,要通过类的对象来调用,非静态成员函数可以任意地访问静态成员函数和静态数据成员

静态成员函数在类外实现时不能加static关键字,否则是错误的

int test::Getnum()

{

       .........

}

静态成员仍然遵循public,private,protected访问准则

静态成员函数没有this指针,它不能返回非静态成员,因为除了对象会调用它外,类本身也可以调用。

调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数

 

11. 友元:为了使其他类的成员函数直接访问该类的私有变量。允许外面的类或函数去访问类的私有变量和保护变量,从而使两个类共享同一函数。

下面两种情况需要使用友元函数:(1)运算符重载的某些场合需要使用友元。(2)两个类要共享数据的时候

友元函数没有this指针,友元函数不能被继承

运算符重载:C++中预定义的运算符的操作对象只能是基本数据类型,对于许多用户自定义类型(例如类),也需要类似的运算操作

(1) 除了类属关系运算符"."、成员指针运算符".*"、作用域运算符"::"、sizeof运算符和三目运算符"?:"以外,C++中的所有运算符都可以重载。

(2)运算符重载实质上是函数重载,因此编译程序对运算符重载的选择,遵循函数重载的选择原则。

(3)重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符操作数的个数及语法结构

(4)运算符重载不能改变该运算符用于内部类型对象的含义。它只能和用户自定义类型的对象一起使用,或者用于用户自定义类型的对象和内部类型的对象混合使用时

(5)重载的功能应当与原有功能相类似,避免没有目的地使用重载运算符

运算符函数重载一般有两种形式:重载为类的成员函数和重载为类的非成员函数。非成员函数通常是友元。

(1) 一般情况下,单目运算符最好重载为类的成员函数;双目运算符则最好重载为类的友元函数。

(2)以下一些双目运算符不能重载为类的友元函数:=、()、[]、->。

>>、<< 重载为友元函数

(3)若一个运算符的操作需要修改对象的状态,选择重载为成员函数较好。

(4)当需要重载运算符具有可交换性时,选择重载为友元函数

12.函数重载的概念

指函数名相同,但是它的参数表列个数或顺序,类型不同。但是不能靠返回类型来判断

13.   构造函数:作用:初始化对象的数据成员

          有参数表,可以重载。无返回值,不能继承

         缺省构造函数可以有形参,没有实参

析构函数:主要是释放对象占有的资源。一个类最多只能有一个析构函数,无参数,无返回值

初始话列表:初始化列表在构造函数主体开始执行前初始化成员对象。它的作用是改善程序的性能。而且类里的引用数据成员必须在初始化列表里初始化。

class CExample {
public:
    int a;
    float b;
    //构造函数初始化列表
    CExample(): a(0),b(8.8)
    {}
    //构造函数内部赋值
    CExample()
    {
        a=0;
        b=8.8;
    }
};

14. 公有继承的含义(替代原则Liskov Substitution Principle)

公有继承意味着“is a kind-of”的关系,简称isa。class D : public B,这就是告诉大家每个类型为D的对象也是一个类型为B的对象,反之不然。也就是说“B对象派得上用场的任何对方,D对象也可以派上用场”

15.C++几种继承方式的比较

1. 公有继承(public)

公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的,不能被这个派生类的子类所访问。

2. 私有继承(private)

私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。

3. 保护继承(protected)

保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。

下面列出三种不同的继承方式的基类特性和派生类特性。

class B :public A{};

 

public

protected

private

共有继承

public

protected

不可见

私有继承

private

private

不可见

保护继承

protected

protected

不可见

16. 虚函数:被virtual关键字修饰的成员函数,虚函数的作用是实现多态性(Polymorphism),虚函数必须实现。纯虚函数:virtualReturnType Function()=0;  在派生类中必须给予重写以实现多态性,含有纯虚函数的类称为抽象类,不能生成对象。

函数覆盖:发生在父类与子类之间,其函数名、参数类型、返回值类型必须同父类中的相对应被覆盖的函数严格一致,覆盖函数和被覆盖函数只有函数体不同。基类函数必须有virtual关键字

动态绑定动态绑定是将一个过程调用与相应代码链接起来的行为。是指与给定的过程调用相关联的代码,只有在运行期才可知的一种绑定,他是多态实现的具体形式。

17.函数重载和覆盖的比较

函数重载是指函数名相同,而函数的参数个数或类型不同;

覆盖是指在派生类中成员函数与基类成员函数的函数名、参数个数、类型与返回值均相同;

C++中正是通过虚函数的覆盖,实现多态的功能。

a.成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
b.覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。

 

18. 抽象基类: 就是类里定义了纯虚成员函数的类

19. 虚基类:派生类虚继承它的时候,它叫做虚基类,本身可以是普通类,解决多重多级继承造成的二义性问题

20. 虚继承: 解决多继承出现重复的基类这种情况,

在任何派生类中的virtual基类总用同一个(共享)对象表示

class FinalClass: virtual public MakeFinal{}

21. 异常: (exception)是程序可能检测到的,运行时刻不正常的情况(除0、数组越界访问、内存耗尽等),出现这些情况希望程序立即处理。C++提供一些内置的语言特性来产生并处理异常,这些语言特性会激活一种运行时刻的机制(异常处理机制),提供这种机制可以在程序的两个无关的部分进行异常通信

异常处理机制是一种非局部的控制结构,基于栈回退(stack unwiding),因此也可以看做另一种返回机制

如没有匹配的catch子句处理该异常,程序的执行权将交给C++标准库定义的terminate()。

 

名字空间基本概念: 实际上就是一个由程序设计者命名的内存区域,程序设计者可以根据需要指定一些有名字的空间域,把一些全局实体分别放在各个命名空间中,从而与其他全局实体分隔开来。

namespace ns1 //指定命名中间nsl

      { int a;

        doubleb; }

程序中要使用变量a和b,nsl::a,nsl::b

命名空间里可以包含:变量(可以带有初始化);常量;数(可以是定义或声明);结构体;类;模板;命名空间(嵌套命名空间)

在声明类时在右花括号的后面有一分号,而在定义命名空间时,花括号的后面没有分号。

22. 模板基本概念: 模板是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数,从而实现了真正的代码可重用性。

支持参数化多态的工具

使用模板的目的就是能够让程序员编写与类型无关的代码。

模板的声明或定义只能在全局,命名空间或类范围内进行。不能在局部范围,函数内进行

比如swap的模板函数形式为 template <class T> voidswap(T& a, T& b){}

一但声明了类模板就可以用类模板的形参名声明类中的成员变量和成员函数,即可以在类中使用内置类型的地方都可以使用模板形参名来声明。比如

template<class T> classA{public: T a; T b; T hy(T c, T &d);};

在类A中声明了两个类型为T的成员变量a和b,还声明了一个返回类型为T带两个参数类型为T的函数hy。

23. 设计模式概念:设计模式使人们可以更加简单方便地复用成功的设计和体系结构

Abstract Factory(抽象工厂,对象创建型模式):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

Adapter(适配器,类对象结构型模式):将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

Singleton(单例,对象创建型模式):保证一个类仅有一个实例,并提供一个访问它的全局访问点。

Iterator(迭代器,对象行为型模式):提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。

Proxy(代理,对象结构型模式):为其他对象提供一个代理以控制对这个对象的访问。

Singleton模式的C++实现

懒汉式,访问量较小时用,以时间换空间

classCSingleton

{

private:

    CSingleton(){};

    staticCSingleton * m_pInstance;

 

public:

staticCSingleton* GetInstance()

{

     if( m_pInstance == NULL ) 

         m_pInstance= newCSingleton();

     returnm_pInstance;

}

};

 

饿汉式,线程安全,空间换时间,访问量比较大时用

 

classCSingleton 

private: 

    CSingleton()   

    { 

    } 

public: 

    staticCSingleton * GetInstance() 

    { 

        staticCSingleton instance;  

        return&instance; 

    } 

};

 

 

class Complex

{

public:

 Complex( double real, double imaginary = 0 )错误[1],见下面1

    :_real(real), _imaginary(imaginary)

  {

  }

 void operator+ ( Complex other ) 错误[2],(此处存在两个问题)见下面2。

  {

   _real = _real + other._real;

   _imaginary = _imaginary + other._imaginary;

  }

 void operator<<( ostream os ) 错误[3],见下面3

  {

   os << "(" << _real << "," <<_imaginary << ")";

  }

 Complex operator++()错误[4],见下面4

  {

   ++_real;

   return *this;

  }

 Complex operator++( int ) 错误[5],见下面5

  {

   Complex temp = *this;

   ++_real;

   return temp;

  }

private:

 double _real, _imaginary;

};

………………………………….…修改………………………………………….

class Complex

{

public:

explicit Complex( double real, doubleimaginary = 0 ) [1]此处构造函数加explicit,通知编译器不提供隐式转换。

    :real_(real), imaginary_(imaginary)

  {

  }

 Complex& operator+=( const Complex& other )  [2]要考虑用户的直觉,一般用+=去实现+,

  {

   real_ += other.real_;

   imaginary_ += other.imaginary_;

   return *this;

  }

 Complex& operator++() [4]此处把返回值类型改为引用,是为了与return *this匹配。

  {

   ++real_;

   return *this;

  }

const Complex operator++( int )

[5]此处加const(使用户不能修改返回的参数),主要是为避免用户无意去修改,出现i++++这种连加的情况。

  {

   Complex temp( *this );

   ++*this;

   return temp;

  }

 ostream& Print( ostream& os ) const

  {

   return os << "(" << real_ << ","<< imaginary_ << ")";

  }

 friend const Complex operator+( const Complex& lhs, constComplex& rhs );[2]+必须重载为友元函数

 friend ostream& operator<<( ostream& os, constComplex& c );

 [3]对于二元运算符(操作数有两个)如:+,<<,通常重载为友元函数而不是重载为成员函数。具体函数的实现在类外实现。

 private:

   double real_, imaginary_;

  };

 const Complex operator+( const Complex& lhs, const Complex& rhs)

  {

   Complex ret( lhs );

   ret += rhs;//会自动调用Complex& operator+=( const Complex& other ),实现两复数加。

   return ret;

  }

 ostream& operator<<( ostream& os, const Complex& c )

  {

   return c.Print(os);

}

 

2

class Array

{

private:

       intptr[100];

public:

       Array(constArray &init);

       ~Array();

};

Array::~Array() //析构函数

{

              delete  ptr; 此处错误,应改为delete []ptr,因为ptr类型为数组。

}

 

3、

Class Base

{

Public:

 Base();

 ~Base();此处错误,应改为virtual ~Base(),用于基类指针来释放派生类对象时,为了能调到派生类的析构函数。如果此处不加虚析构函数,会造成派生类的析构函数调不到,从而可能出现内存释放不掉。

}

Class A:public Base

{

 public:

 A();

 ~A();

}

void main()

{

   Base * p=new A;//使基类指针指向派生类对象

       deletep;//通过释放基类指针来释放派生类对象

}

0 0