C++计算机高级程序设计语言

基本内容

面向过程与面向对象的区别 ※※

链接

一、区别

  1. 编程思想不同
    面向过程:是一种以过程为中心的编程思想。都是以什么正在发生为主要目标进行编程。
    面向对象:是一类以对象作为基本程序结构单位的程序设计语言,指用于描述的设计是以对象为核心,而对象是程序运行时刻的基本成分。

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

  3. 优势不同
    面向过程:不支持丰富的“面向对象”特性(比如继承、多态),并且不允许混合持久化状态和域逻辑。
    面向对象:在内部被表示为一个指向一组属性的指针。任何对这个对象的操作都会经过这个指针操作对象的属性和方法。

二、优缺点

面向过程(Procedure Oriented Programming)
面向过程是具体化的,流程化的,解决一个问题,你需要一步一步的分析,一步一步的实现。
优点:性能比面向对象好,因为类调用时需要实例化,开销比较大,比较消耗资源。比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
缺点:不易维护、不易复用、不易扩展。

面向对象(Object Oriented Programming)
面向对象是模型化的,你只需抽象出一个类,这是一个封闭的盒子,在这里你拥有数据也拥有解决问题的方法。需要什么功能直接使用就可以了,不必去一步一步的实现,至于这个功能是如何实现的,管我们什么事?我们会用就可以了。
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护 。可维护性表现在三个方面:可理解性。可测试性和可修改性。
缺点:性能比面向过程低。

三、小结

  1. 面向对象就是高度实物抽象化、面向过程就是自顶向下的编程!
  2. 面向对象是以功能来划分问题,而不是步骤。
  3. 面向对象的底层其实还是面向过程,把面向过程抽象成类,然后封装,方便我们使用的就是面向对象了。

C与C++的区别

链接
一、结构不同

  1. C语言:C语言结构只有成员变量,而没成员方法。
  2. C++:C++结构中可以有自己的成员变量和成员函数。

二、设计不同

  1. C语言:C语言进行过程化、抽象化的通用程序设计。
  2. C++:C++既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行以继承和多态为特点的面向对象的程序设计。

三、函数库不同

  1. C语言:C语言有标准的函数库,它们松散的,只是把功能相同的函数放在一个头文件中。
  2. C++:C++对于大多数的函数都是有集成的很紧密,是一个集体。

C++的数据类型

  1. 基本类型
    1. 整型
      1. 短整型(short int)
      2. 整型(int)
      3. 长整型(long)
    2. 字符型(char)
    3. 浮点型
      1. 单精度型(float)
      2. 双精度型(double)
      3. 长双精度型(long double)
    4. 布尔型(bool)
  2. 派生类型
    1. 指针类型(*)
    2. 枚举类型(enum)
    3. 数组类型([ ])
    4. 结构体类型(struct)
    5. 共用体类型(union)
    6. 类类型(class)
  3. 空类型(void)

++i 与 i++ 的区别

链接
++i的效率高些,++i在运算过程中不产生临时对象,返回的就是i,是个左值,类似++i=1这样的表达式是合法的,而i++在运算的过程中会产生临时对象,返回的是零时对象的值,是个右值,像i++=1这样的表达式是非法的。
对于内置类型,单独的i++和++i语句,现在的编译器基本上都会优化成++i,所以就没什么区别了

链接
++i是先对i进行加一操作然后再返回i的值,而i++是先返回i的值再对i进行加一操作。

全局变量和局部变量在内存中是否有区别?如果有,是什么区别?

链接

  1. 生存周期不同。
    1. 全局变量存储在静态存储区,全局变量和静态变量是存储在一起的,初始化过的全局变量和静态变量在同一块区域,未初始化的全局变量和静态变量存放在一块相邻的区域内。此区域由系统在程序结束后释放。
    2. 局部变量存放在堆栈中。由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈
  2. 作用范围不同。
    1. 全局变量具有全局作用域。全局变量只需在一个源文件中定义,就可以作用于所有的源文件。当然,其他不包含全局变量定义的源文件需要用extern 关键字再次声明这个全局变量。
    2. 局部变量也只有局部作用域,它是自动对象(auto),它在程序运行期间不是一直存在,而是只在函数执行期间存在,函数的一次调用执行结束后,变量被撤销,其所占用的内存也被收回
  3. 静态变量分为 静态外部变量、静态局部变量,都是在编译时分配内存

内存中的供用户使用的存储空间的划分

谭浩强 P109
链接

  1. 程序区
  2. 静态存储区(存储:全局变量、静态变量)
  3. 动态存储区(存储:函数的形式参数、函数中定义的变量(未加static声明的局部变量)、函数调用时的现场保护和返回地址等)
  4. 栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。在一个进程中,位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数的调用。
  5. 堆,就是那些由 new 分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个 new 就要对应一个 delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。堆可以动态地扩展和收回。

形参传递的形式

谭浩强 P184
链接

  1. 值传递
  2. 指针传递
  3. 引用传递

函数

说明函数原型声明和函数定义的区别。

函数原型声明只是说明了该函数应该如何使用,函数调用时应该给它传递哪些数据,函数调用的结果又应该如何使用。
函数定义除了给出函数的使用信息外,还需要给出了函数如何实现预期功能,即如何从输入得到输出的完整过程。

函数原型中,参数名可写可不写,但编译系统不检查参数名。因此参数名是什么都无所谓。

float add(float, float);
float add(float x, float y);

重载函数

重载函数的参数个数、参数类型或参数顺序三者中必须至少有一种不同,函数返回值类型可以相同也可以不同。

什么是函数模板?什么是模板函数?函数模板有什么用途?

函数模板(function template)实际上是建立一个通用函数,其函数类型(返回值类型)和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就成为函数模板。

① 函数模板就是函数中的某个参数或返回值的类型是不确定的,是可变的,这些不确定的类型称为模板参数。
② 如果给函数模板的模板参数指定了一个具体的类型,就得到了一个可以执行的函数,这个函数称为模板函数。
③ 函数模板可以节省程序员的工作量,若干个被处理的数据类型不同,但处理流程完全一样的函数可以写成一个函数模板。

③ 在函数体相同、函数的参数个数相同而类型不同时,函数模板比函数重载更方便,程序更简洁。

template <typename T>
T max(T a, T b)
{
    
    
	XXX
}
template <class T>
T max(T a, T b)
{
    
    
	XXX
}

指针

引用与指针有什么区别 ※※

链接

  1. 指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。
  2. 引用使用时无需解引用(* 指针运算符,或称 间接访问运算符),指针需要解引用。
  3. 引用只能在定义时被初始化一次,之后不可变;指针可变。
  4. 引用没有 const,指针有 const,const 的指针不可变;
  5. 引用不能为空,指针可以为空;
  6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;typeid(T) == typeid(T&) 恒为真,sizeof(T) == sizeof(T&) 恒为真,但是当引用作为成员时,其占用空间与指针相同(没找到标准的规定)。
  7. 就后置自增(i++)操作而言,对引用的操作直接反应到所指向的对象,而不是改变指向;而对指针的操作,会使指针指向下一个对象,而不是改变所指对象的内容

指向常对象的指针和指向对象的常指针

谭浩强 P177
链接

指向对象的常指针

定义指向对象的常指针的一般形式为
类名 * const 指针变量名 = 对象地址;

将指向对象的指针变量声明为const型,并使之初始化,指针值始终保持为其初始值,不能改变,即其指向不能变,但可以改变其所指向对象中的数据成员(非const型)的值

Time t1(10,12,15), t2;
Time * const ptr1 = &t1;
ptr1 = &t2;

注意:应该在定义指针变量时使之初始化。

指向常对象的指针

定义指向常对象的指针变量的格式
const 类名 * 指针变量名;
类名 const * 指针变量名;

如果一个变量已经被声明成常变量,则只能用指向常变量的指针变量指向它,而不能用一般的(非const型的)指针变量指向它。不允许通过指针变量改变它指向的对象的值,但是指针变量p的值(即p的指向)是可以改变的。
指向常对象的指针最常用于函数的形参,以防止指针形参所指对象的值改变影响实参。

const int a;//定义常变量a
const int *p;//定义指向常变量的指针
p = &a;

对于对象也是如此,如果声明一个对象为常对象,则只能用指向常对象的指针指向它,而不能用一般的(非const型的)指针变量指向它。

const Time t1;//定义常对象
const Time *p;//定义指向常对象的指针
p=t1;

类和对象

构造函数

谭浩强 P246

简介

C++提供了构造函数(constructor)来处理对象的初始化。构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在建立对象时自动执行
构造函数的名字必须与类名同名,而不能任意命名,以便编译系统能识别它并把它作为构造函数处理。它不具有任何类型,不返回任何值。
在建立对象时系统为该对象分配存储单元,此时执行构造函数。

注意
可以用一个类对象初始化另一个类对象,如:

Time t1;			// 建立对象t1,同时调用构造函数 t1.Time()
Time t2 = t1;	//建立对象t2,并用一个 t1 初始化 t2

此时,把对象 t1 的各数据成员的值复制到 t2 相应各成员,而不调用构造函数 t2.Time()。

用参数初始化表对数据成员初始化

Box::Box(int h, int w, int len): height(h), width(w), length(len)
{
    
    
	XXX
}

如果数据成员是数组,则应当构造函数的函数体中用语句对其赋值,而不能在参数初始化表中对其初始化。

构造函数的重载

编译系统是根据函数调用的形式去确定对应哪一个构造函数。
在建立对象时不必给出实参的构造函数,称为默认构造函数(default constructor)。一个类只能有一个默认构造函数。

若在建立对象时选用的是无参构造函数,则:

Box box1;	//建立对象的正确形式
Box box2();	//建立对象的错误形式,不应该有括号

在程序中不应出现调用无参构造函数(如Box()),注意:构造函数是不能被用户显示调用的。

尽管在一个类中可以包含多个构造函数,但是对于每一个对象来说,建立对象则只执行其中一个构造函数,并非每个构造函数都被执行。

使用默认参数的构造函数

class Box
{
    
    
public:
	Box(int h = 10, int w = 10, int len = 10);	//在声明构造函数时指定默认参数
};

在声明构造函数时,形参名可以省略:

class Box
{
    
    
public:
	Box(int = 10, int = 10, int = 10);	//在声明构造函数时指定默认参数
};

如果构造函数的全部参数都指定了默认值,则在定义对象时可以给一个或几个实参,也可以不给出实参。由于不需要实参也可以调用构造函数,因此全部参数都指定了默认值的构造函数也属于默认构造函数。 一个类只能有一个默认构造函数,也就是说,可以不用参数而调用的构造函数,一个类只能有一个。
若同时定义了下面两个构造函数,则错误:

Box();
Box(int = 10, int = 10, int = 10);

在一个类中定义了全部是默认参数的构造函数后,不能再定义重载构造函数。(会出现歧义)

C++中的构造函数的分类

(1)默认构造函数。
以Student类为例,默认构造函数的原型为
Student();//没有参数
(2)初始化构造函数
Student(int num,int age);//有参数
(3)复制(拷贝)构造函数
Student(Student&);//形参是本类对象的引用
(4)转换构造函数
Student(int r) ;//形参时其他类型变量,且只有一个形参

请说明析构函数和构造函数的作用

  1. 构造函数是在新建对象时自动执行,尽心对象初始化。析构函数是对象销毁时自动执行,做一些善后工作。
  2. 构造函数的名字就是类名,析构函数的名字是波浪号加类名。
  3. 构造函数和析构函数都不需要写函数的返回类型。
  4. 对象可能有不同的构造方法,所以类可以有一组重载的构造函数,但析构函数只能有一个。构造函数还可以有一个初始化列表。

复制构造函数(compy constructor)

谭浩强 P277

复制构造函数的作用是将实参对象的各成员值一一赋给新的对象中对应的成员。
一般约定加const声明,使参数不能改变,以免在调用此函数时因不慎而使实参对象被修改。

链接

复制构造函数是构造函数的一种,也称拷贝构造函数,它只有一个参数,参数类型是本类的引用。
复制构造函数的参数可以是 const 引用,也可以是非 const 引用。 一般使用前者,这样既能以常量对象(初始化后值不能改变的对象)作为参数,也能以非常量对象作为参数去初始化其他对象。一个类中写两个复制构造函数,一个的参数是 const 引用,另一个的参数是非 const 引用,也是可以的。
如果类的设计者不写复制构造函数,编译器就会自动生成复制构造函数。 大多数情况下,其作用是实现从源对象到目标对象逐个字节的复制,即使得目标对象的每个成员变量都变得和源对象相等。编译器自动生成的复制构造函数称为“默认复制构造函数”。
注意,默认构造函数(即无参构造函数)不一定存在,但是复制构造函数总是会存在。

默认的复制构造函数:

#include<iostream >
using namespace std;
class Complex
{
    
    
public:
    double real, imag;
    Complex(double r, double i) {
    
    
        real= r; imag = i;
    }
};
int main(){
    
    
    Complex cl(1, 2);
    Complex c2 (cl);  //用复制构造函数初始化c2
    cout<<c2.real<<","<<c2.imag;  //输出 1,2
    return 0;
}

自定义的复制构造函数:

#include<iostream>
using namespace std;
class Complex{
    
    
public:
    double real, imag;
    Complex(double r,double i){
    
    
        real = r; imag = i;
    }
    Complex(const Complex & c){
    
    
        real = c.real; imag = c.imag;
        cout<<"Copy Constructor called"<<endl ;
    }
};

int main(){
    
    
    Complex cl(1, 2);
    Complex c2 (cl);  //调用复制构造函数
    cout<<c2.real<<","<<c2.imag;
    return 0;
}

复制构造函数的使用情况(什么时候用到复制构造函数)

  1. 程序中需要新建立一个对象,并用另一个同类的对象对它初始化。
  2. 当函数的参数为类的对象时。 在调用函数时需要将实参对象完整地传递给形参,也就是需要建立一个实参的拷贝,这就是按实参复制一个形参,系统是通过调用复制构造函数来实现的,这样能保证形参具有和实参完全相同的值。
  3. 函数的返回值是类的对象。 在函数调用完毕将返回值待会函数调用处时。

在什么情况下必须定义类自己的复制构造函数?

如果类的数据成员中含有指针,而指针指向的是一个动态变量,必须自己定义复制构造函数。或对复制构造有其他特殊的要求也需要定义复制构造函数

链接
对于凡是包含动态分配成员或包含指针成员的类都应该提供拷贝构造函数

链接
当你的类需要深拷贝的时候,需要自定义复制构造函数,这样也可以防止一些意外的事情发生,而且一般来说最好自定义一个拷贝构造函数,并且实现深拷贝。因为程序自动生成的复制构造函数是浅拷贝,这样临时对象就会和原对象指向同一块内存,而当临时对象在作用域外被释放时,那么原对象的内存空间也就同时被释放了,之后再次引用原对象就会出问题,这是由于浅拷贝而造成的很普遍的一个问题。

浅拷贝和深拷贝 ※※

链接

  1. 在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的;但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象,所以,此时,必须采用深拷贝。
  2. 深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。

友元

友元函数

将普通函数声明为友元函数

如果在本类以外的其他地方定义了一个函数(这函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数),在类体中用 friend 对其进行声明,此函数就称为本类的友元函数。友元函数可以访问这个类中的私有成员。

class Time
{
    
    
public:
	friend void display(Time &);
private:
	int hour,
	int minute;
	int sec;
};
void display(Time &)
{
    
    
	cout << t.hour << ":" << t.minute << ":" << t.sec << endl;
}

友元成员函数

这里用到类的 提前引用声明

#include<iostream>
using namespace std;
class Date;				//对 Date 类的提前引用声明
class Time
{
    
    
public:
	void display(Date &);
};
class Date
{
    
    
public:
	friend void Time::display(Date &);		//声明Time 中的 display 函数为本类的友元成员函数
};
void Time::display(Date Td)
{
    
    
	cout << d.month << endl;			// 引用Date类对象中的私有数据
	cout << hour << endl;					// 引用本类对象中的私有数据
}

友元类

若 B 类是 A 类的友元类,则友元类 B 中的所有函数都是 A 类的友元函数,可以访问 A 类中的所有成员。

在 A 类的定义体中用以下的语句声明 B 类为其友元类:
friend B;

注意:

  1. 友元的关系是单向的而不是双向的。
  2. 友元的关系不能传递。

类模板及其实现

谭浩强 P290

在有两个或多个类时,若其功能是相同的,仅仅是数据类型不同,可以用一个类模板来简化程序设计。
由于类模板包含类型参数,因此又称为参数化的类。
类模板是类的抽象,类是类模板的实例。

template<class T>
class Compare
{
    
    
public:
	Compare(Ta, T b)
	{
    
    
		x = a;
		y = b;
	}
	T max();
private:
	T x, y;
};

template<class T>
T Compare<T> :: max()
{
    
    
	return x > y ? x : y;
}

继承与派生

基类和派生类的构造函数和析构函数的执行顺序

谭浩强 P350

在建立一个对象时,执行构造函数的顺序是:派生类构造函数先调用基类构造函数,再执行派生类构造函数本身(即派生类构造函数的函数体)。
在派生类对象释放时,先执行派生类析构函数,再执行其基类析构函数。

如果派生类有子对象(subobject,即对象中的对象),则执行派生类构造函数的顺序是:

  1. 调用基类构造函数,对基类数据成员初始化
  2. 调用子对象构造函数,对子对象数据成员初始化
  3. 再执行派生类构造函数本身,对派生类数据成员初始化

多继承

C++允许一个派生类同时继承多个基类,这种行为称为 多重继承(multiple inheritance)。

class D: public A, private B, protected C
{
    
    
	xxx
};

上例中构造函数的调用顺序:A B C, D

多态与虚函数

多态性及其如何实现

谭浩强 P379

简介

多态性(polymorphism)是面向对象程序设计的一个重要特征。如果一种语言只支持类而不支持多态,是不能称为面向对象语言的,只能说是基于对象的。
向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法)。
在C++中,多态性表现形式之一是:具有不同功能的函数可以用同一个函数名,这样就可以实现用一个函数名调用不同内容的函数。

分类

静态多态性
通过函数重载实现。由函数重载和运算符重载(运算符重载实质上也是函数重载)形成的多态性属于静态多态性,要求在程序编译时就知道调用函数的全部信息,因此,在程序编译时系统就能决定要调用哪个函数。静态多态性又称编译时的多态性
优缺点:函数调用速度快、效率高,但缺乏灵活性,在程序运行前就已决定了执行的函数和方法。
动态多态性
通过虚函数(virtual function)实现。在程序运行过程中才动态地确定操作所针对的对象,又称运行时的多态性

为什么要定义虚析构函数?

将析构函数定义成虚函数可以防止内存泄漏。

链接
如果基类的析构函数不是虚析构函数,那么在销毁派生类对象时,只会调用基类的析构函数,派生类对象无法释放,导致内存泄漏。则定义基类的析构函数是虚析构函数,那么派生类的析构函数会自动成为虚函数,在销毁派生类对象时,会首先调用派生类的析构函数,再调用基类的派生函数,让父类对象和子类对象都完全释放。

什么是抽象类?定义抽象类有什么意义?抽象类在使用上有什么限制?

不用来定义对象而只作为一种基本类型用作继承的类,称为抽象类(abstract class),由于它常用作基类,通常称为抽象基类(abstract base class)。
包含有纯虚函数的类称为抽象类。因为纯虚函数是不能被调用的,包含纯虚函数的类是无法建立对象的。
定义抽象类的主要用途是规范从这个抽象类派生的这些类的行为。
在使用时,不能定义抽象类的对象,只能定义抽象类的指针变量。

运算符重载

不能重载的运算符

(1). (成员访问运算符)

(2).* (成员指针访问运算符)

(3)∷(域运算符)

(4)sizeof(长度运算符)

(5)?: (条件运算符)

运算符重载的意义

谭浩强 P301

把运算符重载和类结合起来,可以在C++程序中定义出很有实用意义而使用方便的新的数据类型。运算符重载使C++具有更好的扩充性和适应性。这是C++功能强大和最吸引人的一个特点。

矩阵相加

#include <iostream>
using namespace std;
class Matrix                                          //定义Matrix类
{
    
    
public:
	Matrix();                                          //默认构造函数
	friend Matrix operator+(Matrix&, Matrix&);        //重载运算符“+”
	void input();                                      //输入数据函数
	void display();                                    //输出数据函数
private:
	int mat[2][3];
};

Matrix::Matrix()                                      //定义构造函数
{
    
    
	for (int i = 0; i < 2; i++)
		for (int j = 0; j < 3; j++)
			mat[i][j] = 0;
}

Matrix operator+(Matrix& a, Matrix& b)                //定义重载运算符“+”函数
{
    
    
	Matrix c;
	for (int i = 0; i < 2; i++)
	{
    
    
		for (int j = 0; j < 3; j++)
		{
    
    
			c.mat[i][j] = a.mat[i][j] + b.mat[i][j];
		}
	}
	return c;
}
void Matrix::input()                                   //定义输入数据函数
{
    
    
	cout << "input value of matrix:" << endl;
	for (int i = 0; i < 2; i++)
		for (int j = 0; j < 3; j++)
			cin >> mat[i][j];
}

void Matrix::display()                                //定义输出数据函数
{
    
    
	for (int i = 0; i < 2; i++)
	{
    
    
		for (int j = 0; j < 3; j++)
		{
    
    
			cout << mat[i][j] << " ";
		}
		cout << endl;
	}
}

int main()
{
    
    
	Matrix a, b, c;
	a.input();
	b.input();
	cout << endl << "Matrix a:" << endl;
	a.display();
	cout << endl << "Matrix b:" << endl;
	b.display();
	c = a + b;                                         //用重载运算符“+”实现两个矩阵相加
	cout << endl << "Matrix c = Matrix a + Matrix b :" << endl;
	c.display();
	return 0;
}

输入输出流

ASCII文件和二进制文件有什么不同?

ASCII文件是将存储在文件中的每个字节解释成一个ASCII字符,
二进制文件是将文件内容解释成一个二进制的比特流,由程序解释这些比特流的意义。

猜你喜欢

转载自blog.csdn.net/qq_41286942/article/details/123767392