c++面向对象程序设计教程(第四版)_c++面向对象程序设计教程(第4版) 陈维兴 pdf电子版第八章-程序员宅基地

技术标签: c++  

c++面向对象程序设计教程(第四版)

1.面向对象程序设计

程序=对象+消息

程序一般由类的定义和类的使用两部分组成
程序中的操作都是通过想对象发送消息来实现的,对象接收到消息后,启动有关方法完成操作

对象=属性+行为(数据+函数)

一组具有相同属性和行为的对象的抽象
类和对象之间的关系:抽象和具体的关系
一个对象是类的一个实例
在面向对象程序设计中,先声明类,再由类生成其对象

消息与方法

对象的交互:对象之间的联系
消息传递机制:允许一个对象与另一个对象的交互
消息的性质:

  1. 同一个对象可以接受不同形式的多个消息,做出不同的响应
  2. 相同形式的消息可以传递给不同的对象,做出的响应可以是不同的
  3. 对消息的响应不是必须的,可以不响应
    方法包括:界面,方法体
    方法的界面:给出了 方法名 和 调用协议(成员函数的函数名和参数变)
    方法体:实现某种操作的一系列计算步骤,也就是一段程序(成员函数的函数体)
基本体征

抽象:通过特定的实例抽取共同性质后形成概念的过程
封装:把数据和实现操作的代码集中起来放在对象内部,并尽可能隐蔽对象的内部细节
C++对象中的函数名就是对象的对外接口
好处:降低了人们操作对象的复杂程度,隐藏了复杂性,提供了代码重用性,减轻了开发软件系统的难度
各个对象相互独立、互不干扰
信息隐蔽:对外界隐蔽的做法,有利于数据安全,防止无关人员访问和修改数据
继承:某个类可以继承另一个类的特征和能力
若类之间具有继承关系,则它们具有以下特征:
1.类间具有共享特征
2.类间具有差别或新增部分
3.类间具有层次结构
作用:避免公用代码的重复开发,减少代码和数据冗余,增强一致性来减少模块间的接口和界面
多态:不同的对象收到相同的消息时执行不同的操作
增强了软件的灵活性和重用性,为软件开发与维护提供了极大的便利

优点

可提高程序的重用性
可控制程序的复杂性
可改善程序的可维护性
能更好地支持大型程序设计
增强了计算机处理信息的范围
能很好地适应新的硬件环境

2.C++概述

注释:1. //开始,到行尾结束 2. /**/,可以嵌套//
C++源程序文件扩展名.cpp

头文件: iostream:声明了程序所需的输入和输出操作的有关信息

using namespace std 使用命名空间std,可以保证对C++标准库操作的每一个特性都是唯一的,不至于发生命名冲突
使用、等都需要加using namespace std ,若采用带.h的头文件时,则不需要加
两种头文件不可混用

命名空间

见7.6

输入输出

标准输入流对象cin(c+in)标准输出流对象cout(c+out)更安全更方便
cin:在程序中用于代表标准输入设备,通常指键盘
>>在c++中仍保持右移功能,用于输入时表示将从标准输入流对象cin读取的数值传送给右方指定的变量
当输入字符串(即类型为string的变量)时,提取运算符“>>”的作用是跳过空白字符,读入后面的非空白字符,直到遇到另一个空白字符为止,并在串尾放一个字符串结束标志‘\0’。
cin>>x 必须加>>,x必须是基本数据类型不能是,void类型
允许输入一连串数据cin>>a>>b>>c,两个数据间用空白符(空格、回车、Tab键)分隔
cout:在程序中用于代表标准输出设备,通常指屏幕
<<在c++中仍保持左移功能,用于输入时表示将右方变量的值写到标准输出流对象cout中
cin<<y 必须加<<,y必须是基本数据类型不能是,void类型
允许输入一连串数据cout<<a+b<<c
I/O格式:设置转换基数的操作符:dec(十进制)、hex(十六进制)、oct(八进制)
换行操作符endl,作用与\n一样
cout<<hex<<x<<’ '<<dec<<x<< ’ '<<endl

灵活的局部变量说明

允许变量声明与可执行语句在程序中交替出现
允许在代码块中的任何地方说明局部变量,从说明点到该变量所在的最小分程序末的范围有效,且符合先定义后使用

结构名、联合名、枚举名可直接作为类型名

在定义变量时,不必在结构名、联合名、枚举名前加struct、union、enum

enum Bool {FALSE,TRUE};
struct String
{	char * ptr;
	int length;
};
Bool done; //定义时不加struct、union、enum
String str; //c语言中必须加struct、union、enum	

const修饰符

#define:#define PI 3.14 将程序中的PI替换为3.14,编译后程序中不再出现PI这个标识符,PI不是变量,无类型,不占存储单元,易出错
const:const int i=10 这个常量i是有类型的,占用存储单元,有地址,可以用指针指向它,但是不可修改,消除了#define的不安全性
const和指针:

  1. 指向常量的指针:指向常量的指针变量
    const char * name="chen";
    name[3]='a'; //出错,不允许改变指针所指的常量
    name="zhang" //合法,可以改变指针所指的地址```
    
  2. 常指针:把指针所指的地址声明为对象
	char * const name="chen";
	name[3]='a'; //合法,允许改变指针所指的常量
	name="zhang" //出错,不允许改变指针所指的地址
  1. 指向常量的常指针:这个指针本身不能改变,它所指向的地址中的数据也不能改变
	const char * const name="chen";
   name[3]='a'; //出错,不允许改变指针所指的常量
   name="zhang" //出错,不允许改变指针所指的地址 	

说明:

  1. 用const定义int型常量,可以省略int
  2. 常量一旦建立,在程序的任何地方都不可改
  3. const定义的常量可以有自己的数据类型,c++的编译程序可以进行更严格的类型检查,具有连号的编译式的检查性
  4. 函数的形参也可以用const说明,用于保证形参在该函数内部不被改动

函数原型

在C++中,如果函数调用的位置在函数定义之前,则要求在函数调用之前必须对所调用的函数作函数原型声明,以说明函数的名称、参数类型与个数,以及函数返回值的类型,其主要目的是让c++编译程序进行检查,以确定调用函数的参数以及返回值类型与事先定义的原型是否相符,以保证程序的正确选。

  1. 函数原型的参数表中可以不包含参数的名字,只包含类型
  2. 函数定义由函数说明和函数体两个部分构成。函数说明部分与函数原型基本一样,但函数说明部分中的参数必须给出参数的名字,而且不能包含结尾的分号
  3. 主函数 main 不必进行原型说明,因为它被看成一个自动说明原型的函数。主函数是第1 个被执行的函数,而且不存在被别的函数调用的问题。
  4. 标准C++要求 main 函数必须声明为 int 型,即要求主函数带回一个整型函数值,如果函数执行不正常,则返回数值-1。
  5. 如果一个函数没有返回值,则必须在函数原型中注明返回类型为 void
  6. 如果函数原型中未注明参数,C++假定该函数的参数表为空(void)。
    在c++中,f();与f(void);完全一样,表示该函数不带任何参数
    在C语言中,f(void);表示该函数不带任何参数,f();的参数信息没有给出,可能有参数

void型指针

void通常表示无值,但将void作为指针的类型时,它却表示不确定的类型。这种void型指针是一种通用型指针,也就是说任何类型的指针值都可以赋给void类型的指针变量。

需要指出的是,这里说void型指针是通用指针,是指它可以接受任何类型的指针的赋值,但对已获值的void型指针,对它进行再处理,如输出或者传递指针值时,则必须再进行显式类型转换,否则会出错。

    void* pc;
    int i = 123;
    char c = 'a';
    pc = &i;
	cout << pc << endl;         //输出指针地址006FF730
	cout << *(int*)pc << endl;  //输出值123
    pc = &c;
	cout << *(char*)pc << endl; //输出值a

内联函数

在函数说明前冠以关键字“inline”,该函数就被声明为内联函数,又称内置函数。每当程序中出现对该函数的调用时,C++编译器使用函数体中的代码插入到调用该函数的语句处,同时用实参代替虚参,以便在程序运行时不再进行函数调用。

引入内联函数主要是为了消除调用函数时的系统开销,以提高运行速度。
说明:

  1. 内联函数在第一次被调用之前必须进行完整的定义,否则编译器将无法知道应该插入什么代码
  2. 在内联函数体内一般不能含有复杂的控制语句,如for语句和switch语句等
  3. 是一种空间换时间的措施,若内联函数较长,较复杂且调用较频繁时会使程序加长很多(一般1~5条语句且使用频繁才定义为内联函数)
  4. 使用内联函数替代宏定义(#define),能消除宏定义的不安全性

带有默认参数的函数

C++允许实参个数与形参个数不同
当进行函数调用时,编译器按从左到右的顺序将实参与形参结合,若未指定足够的实参,则编译器按顺序用函数原型中的默认值来补足所缺少的实参。

  1. 在函数原型中,所有取默认值的参数都必须出现在不取默认值的参数的右边。 int fun(int a, int b, int c = 111);
  2. 在函数调用时,若某个参数省略,则其后的参数皆应省略而采取默认值。不允许某个参数省略后,再给其后的参数指定参数值。
  3. 定义在调用前,应在函数定义中指定默认值,定义在调用后,必须在声明中给出默认值,此时定义中不再给默认值(可能会报错)

函数的重载

在同一作用域内,只要函数参数的类型不同,或者参数的个数不同,或者二者兼而有之,两个或者两个以上的函数可以使用相同的函数名。
说明:

  1. 调用重载函数时,函数返回值类型不在参数匹配检查之列。若函数的参数个数和类型都相同,而只有返回值类型不同,则不允许重载。
  2. 函数的重载与带默认值的函数一起使用时,有可能引起二义性。
    void Drawcircle(int r = 0, int x = 0, int y = 0);
    void Drawcircle(int r);
    Drawcircle(20);
    
  3. 在调用函数时,如果给出的实参和形参类型不相符,C++的编译器会自动地做类型转换工作。如果转换成功,则程序继续执行,在这种情况下,有可能产生不可识别的错误。
void f_a(int x);
void f_a(long x);
int c=f_a(20.83);	//无法确定转换为int还是long		

作用域运算符 ::

通常情况下,如果有两个同名变量,一个是全局的,另一个是局部的,那么局部变量在其作用域内具有较高的优先权,它将屏蔽全局变量。

如果希望在局部变量的作用域内使用同名的全局变量,可以在该变量前加上“::”,此时::value代表全局变量value,“::”称为作用域标识符。

无名联合

C++中的一种特殊联合,在关键字union后没有给出联结名,可使得一组数据成员共享同一内存地址

union
{	int i;
	double d;
}x;

在访问无名变量时,不能访问无名联合变量,而是应该访问联合变量中的成员,如 x.d x.i

强制类型转换

C语言

int i=10;
double x=(double)i;

C++

int i=10;
double x=double(i);			

运算符new和delete

程序运行时,计算机的内存被分为4个区:程序代码区、全局数据区、堆和栈。其中,堆可由用户分配和释放。C语言中使用函数malloc()和free()来进行动态内存管理。C++则提供了运算符new和delete来做同样的工作,而且后者比前者性能更优越,使用更灵活方便。
说明:

  1. 用运算符new分配的空间,使用结束后应该用也只能用delete显式地释放,否则这部分空间将不能回收而变成死空间。
  2. 在使用运算符new动态分配内存时,如果没有足够的内存满足分配要求,new将返回空指针(NULL)。
  3. 使用运算符new可以为数组动态分配内存空间,这时需要在类型后面加上数组大小。
  4. new 可在为简单变量分配空间的同时,进行初始化
指针变量名=new 类型;
int * p;
p=new int;

指针变量名 = new 类型名[下标表达式];
int * p = new int[10];
delete []指针变量名;

指针变量名 = new 类型名(初值);
int *p;
p = new int(99)

delete 指针变量名;  //指针变量保存着new分配的内存的首地址
delete p;

使用malloc函数时必须使用sizeof函数来计算所需要的字节数,new可以根据数据类型自动计算所要分配内存的大小,减少错误发生
new能够自动返回正确的指针类型,mall哦草必须在程序中进行强制数据类型转换

引用

建立引用的作用:为变量另起一个名字,变量的引用被认为变量的别名

  1. 引用与其所代表的变量共享同一内存单元,系统并不为引用另外分配存储空间。编译系统使引用和其代表的变量具有相同的地址。
  2. 引用并不是一种独立的数据类型,它必须与某一种类型的变量相联系。在声明引用时,必须立即对它进行初始化,不能声明完成后再赋值。声明一个引用时,必须同时用另一个变量的名字将它初始化
  3. 为引用提供的初始值,可以是一个变量或者另一个引用。
  4. 引用在初始化后不能再被重新声明为另一个变量的引用
  5. 指针是通过地址间接访问某个变量,而引用则是通过别名直接访问某个变量。
  6. 每次使用引用时,可以不书写 间接运算符*
  7. 不允许建立void类型的引用
  8. 不能建立引用的数组
  9. 不能建立引用的引用。不能建立指向引用的指针。引用本身不是一种数据类型,所以没有引用的引用,也没有引用的指针。
  10. 可以将引用的地址赋值给一个指针,此时指针指向的是原来的变量。
  11. 可以用const对引用加以限定,不允许改变该引用的值,但是它不阻止引用所代表的变量的值。
    引用作为函数参数、使用引用作为函数值
#include <iostream>
using namespace std;

void swap(int &a, int &b)
{
	int t = a;
	a = b;
	b = t;
}

int a[] = {1, 3, 5, 7, 9};

int & index(int i)
{
	return a[i];
}

int main() 
{
	int a = 5, b = 10;
	//交换数字a和b
	swap(a, b);
	cout << "a = " << a << " b = " << b << endl;
	cout << index(2) << endl;   //等价于输出元素a[2]的值
	index(2) = 100;             //等价于将a[2]的值赋为100;
	cout << index(2) << endl;
	
	return 0;
}

3.类和对象

1.基本概念

类的声明

比结构体类型更安全有效的数据类型——类

class 类名{
    public:
        公有数据成员;
        公有成员函数;
    protected:
        保护数据成员;
        保护成员函数;
    private:
        私有数据成员;
        私有成员函数;
};		//最后一定要加分号				//从 { 到 } 是类体
  • 一般情况下,一个类的数据成员应该声明为私有成员,成员函数声明为共有成员。这样,内部的数据整个隐蔽在类中,在类的外部根本就无法看到,使数据得到有效的保护,也不会对该类以外的其余部分造成影响,程序之间的相互作用就被降低到最小。
  • 类声明中的关键字private、protected、public可以任意顺序出现。
  • 若私有部分处于类的第一部分时,关键字private可以省略。这样,如果一个类体中没有一个访问权限关键字,则其中的数据成员和成员函数都默认为私有的。结构体默认公有
  • 不能在类声明中给数据成员赋初值。
  • 数据成员可以是任何数据类型,但是不能用 自动(auto)、寄存器(register)、外部(extern)进行说明

成员访问限定符:(声明各成员的访问属性)

  • private:只能由本类的成员函数访问
  • public:既可以本类的成员函数访问,也可以被类外的对象访问
  • protected:只能由本类及其派生类的成员函数访问

成员函数的定义

可以访问本类中的任何组成员

  • 在类的声明中只给出成员函数的原型,而成员函数的定义写在类的外部。在类外定义的一般形式是:
返回值类型 类名::成员函数名(参数表)
{   
	 函数体
}
  • 隐式定义:将成员函数直接定义在类的内部,没有inline,隐含地定义为内联
  • 显式定义:在类声明中只给出成员函数的原型,而将成员函数的定义放在类的外部。加 inline
    声明和定义时可以都加inline,也可以只在一处加
    使用inline定义内联函数时,必须将类的声明和内联成员函数的定义放在同一个文件或头文件中,否则编译时无法进行代码置换

对象的定义及使用

一个类也就是用户声明的一个数据类型
声明一个类,并不接受和存储具体的值
对象的定义:

  • 在声明类的同时,直接定义对象。在}后直接写出属于该类的对象名表 }op1, op2;
  • 声明了类之后,在使用时再定义对象 ` Score op1, op2;

对象中成员的访问:

  • 通过对象名和对象选择符 . (简称点运算符) 对象名.数据成员名 对象名.成员函数名[ (实参表) ]
  • 通过指向对象的指针(在定义对象时,定义的是指向此对象的指针) ->操作符
class date{
		public:
			int year;
		};
		date d,*p;
		p=&d;
		cout<<p->year;	//        d.year      (* p).year		p->year	三者等价

类的作用域和类成员的访问属性

类的作用域:在类的声明中的一对花括号所形成的作用域。在类的作用域内,任何成员函数可以不受限制地访问该类中的其他成员
一般,公有成员是类的对外接口,私有成员是类的内部数据和内部实现不希望外界访问
将类的成员划分为不同的访问级别好处:信息隐蔽,数据保护

2.构造函数和析构函数

对象的初始化和构造函数

类声明中不能给数据成员赋初值

class Complex{
	double real=0;/**/错误的**
	};

如果一个类中的所有成员都是公有的,可以在定义对象时对数据成员进行初始化

class Complex{
public:
	double real;
	double imag;
	};
	Complex c1={1.1,2.2};

如果类中包含私有成员或保护成员,可以采用类中的公有成员函数来对对象中的数据成员赋初值

  • 构造函数是是属于某一个类的,一种特殊的成员函数,它主要用于为对象分配空间,进行初始化。
  • 构造函数的名字必须与类名相同,而不能由用户任意命名。否则编译程序将把它当做一般的成员函数来处理。
  • 它可以有任意类型的参数,但不能具有返回值。可以由用户提供,也可以由系统生成。
  • 编译系统自动生成的默认构造函数,不带任何参数,函数体是空的,只能为对象开辟数据成员存储空间,不能赋初值
  • 与普通的成员函数一样,构造函数的函数体可以写在类体内,也可写在类体外。
  • 当构造函数直接定义在类内时,系统将构造函数作为内联函数处理
  • 构造函数一般声明为公有成员,但它不需要也不能像其他成员函数那样被显式地调用,它是在定义对象的同时被自动调用,而且只执行一次。
  • 构造函数可以不带参数,此时对对象的初始化是固定的,
    Complex(){				//不带参数的构造函数
    	real=0;
    	imag=0;
    	}
    

在建立对象时采用构造函数给数据成员赋初值,有两种形式:

  • 类名 对象名 [ (实参表) ];

  • 类名 * 指针变量名=new 类名 [ (实参表) ]; //这个对象没有名字,称为 无名对象 // delete 指针变量名

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

在函数首部实现

类名 ::构造函数名 (	  [参数表]	) :数据成员名1(初始值1),数据成员名2(初始值2), ......
{		//构造函数体
}

对于用const修饰的数据成员,或是引用类型的数据成员,不允许用赋值语句直接赋值,可以用成员初始化列表

构造函数的重载

#include <iostream>
using namespace std;
class Date
{	public:
		Date ();						//声明1个无参数的构造函数
		Date (int y,int m,int d);	//声明1个带有3个参数的构造函数
		void showDate();
	private:
		int year;
		int month;
		int day;						//一个类中可以包含多个构造函数,但对每个对象而言,建立对象时只执行其中一个构造函数
};
Date::Date ()						//只要类中定义了一个构造函数,系统将不再提供默认构造函数
{	year=2000;	month=4;	day=28;
}
Date::Date (int y,int m,int d)
{	year=y;	month=m	day=d;
}
inline void showDate()
{	cout<<year<<"."<<month<<"."<<day<<endl;
}
int main()
{	Date date1;	//使用无参构造函数创建对象时,用这个Date date1;	不能用Date date1();表示声明一个函数名为date1返回值类型为Date的普通函数	
	date1.showDate();
	Date date2(2002,11,14);
	date2.showDate();
	return 0;
}

带默认参数的构造函数

Complex (double r=0.0,double i=0.0);	//在声明构造函数时指定默认参数
Complex::Complex(double r,double i)	//在类外定义构造函数时,可以不再指定参数的默认值
{real=r; imag=i}
  • 构造函数在类的声明外定义,默认参数必须在声明时指定
  • 如果构造函数的全部参数都指定了默认值,则在定义对象时可以指定0个或1个或几个实参,这时的构造函数属于默认构造函数
  • (一个类中只能有一个默认构造函数)
  • 如果构造函数的全部参数都指定了默认值不能再定义重载构造函数

析构函数

  • 通常用于撤销对象时的一些清理任务,如释放分配给对象的内存空间等。
  • 析构函数与构造函数名字相同,但它前面必须加一个波浪号(~)。
  • 析构函数没有参数和返回值,
  • 不能被重载,因此只有一个。
  • 当撤销对象时,编译系统会自动调用析构函数。
  • 每个类必须有析构函数,如果没有显式的定义,编译系统会自动生成一个默认析构函数(函数体是空的)
  • 除了主函数结束时,对象被撤销,系统会自动调用析构函数,在以下情况下,析构函数也会被调用
    1. 一个对象被定义在一个函数体内,当该函数被调用结束时,该对象会被释放
    2. 对象是使用new运算符动态创建的,在使用delete释放它时

3.对象数组和对象指针

对象数组

每一个数组元素都是对象的数组
有几个数组元素就调用几次构造函数

类名 数组名[下标表达式]

exam ob1 [4] = {89, 97, 79, 88};	//用只有1个参数的构造函数给对象数组赋值

Exam ob2 [4] = {89, 90};	//用不带参数和带1个参数的构造函数给对象数组赋值,
//先调用带1个参数的构造函数初始化ob2[0]和ob2[1],再调用不带参数的构造函数初始化ob2[2]和ob2[3],

Complex com [3]={
	Complex (1,2),
	Complex (3,4),
	Complex (5,6)
};	//用带有多个参数的构造函数给对象数组赋值

对象指针

每一个对象在初始化后都会在内存中占有一定的空间。因此,既可以通过对象名访问对象,也可以通过对象地址来访问对象。对象指针就是用于存放对象地址的变量。声明对象指针的语法形式为:

类名 * 对象指针名;
用指针访问单个对象成员   exe ob,*p;    p=&ob;		p->show();		(*p).show();
用对象指针访问对象数组	exe ob[2],*p;	p=ob; 

this指针

实际上,给对象赋值就是给对象的数据成员赋值
C++的编译系统只用了一段空间来存放共同的函数代码,
每一个对象的存储空间都只是数据成员所占用的存储空间,函数代码是存储在对象空间之外
称为自引用指针
每当创建一个对象时,系统把this指针初始化为指向该对象,this指针的值是当前调用成员函数的对象的起始地址
this指针是隐式使用的,是作为参数被传递给成员函数的

void disp()	{cout<<"x="<<x<<endl;}
实际使用时,c++编译系统把它处理为:
void disp(*this)	{cout<<"x="<<this->x<<endl;}

4.string类

C++支持两种类型的字符串,第一种是C语言中介绍过的、包括一个结束符’\0’(即以NULL结束)的字符数组,标准库函数提供了一组对其进行操作的函数,可以完成许多常用的字符串操作。如:strcpy、ctrcat、strlen等
C++仍保留了这种格式字符串;
C++标准库中声明了一种更方便的字符串类型,即字符串类string,类string提供了对字符串进行处理所需要的操作。使用string类必须在程序的开始包括头文件string,即要有以下语句:#include <string>

string 对象1,对象2,......;
strnig str1,	str2("china"),	str3="administrator"

在这里插入图片描述
在表达式中,可以string类对象和以‘\0’结束的字符串混在一起使用

5.向函数传递对象

  • 使用对象作为函数参数: void show(类名 对象名)
    对象可以作为参数传递给函数,其方法与传递其他类型的数据相同。在向函数传递对象时,是通过“传值调用”的方法传递给函数的。因此,函数中对对象的任何修改均不影响调用该函数的对象(实参本身)。
  • 使用对象指针作为函数参数:
    对象指针可以作为函数的参数,使用对象指针作为函数参数可以实现传值调用,即在函数调用时使实参对象和形参对象指针变量指向同一内存地址,在函数调用过程中,形参对象指针所指的对象值的改变也同样影响着实参对象的值。
  • 使用对象引用作为函数参数:
    在实际中,使用对象引用作为函数参数非常普遍,大部分程序员喜欢使用对象引用替代对象指针作为函数参数。因为使用对象引用作为函数参数不但具有用对象指针做函数参数的优点,而且用对象引用作函数参数将更简单、更直接。

6.对象的赋值和复制

对象赋值语句

B=A; 将对象A的数据成员逐位复制给B

  1. 两个对象的类型必须相同
  2. 当类中存在指针时,可能会出错

拷贝构造函数

  • 形参是本类对象的引用,作用是:在建立一个对象时使用一个已经存在的对象去初始化这个新对象 Point p2(p1);
  • 因为拷贝构造函数也是一种构造函数,所以其函数名与类名相同,并且该函数也没有返回值。
  • 拷贝构造函数只有一个参数,并且是同类对象的引用。
  • 每个类都必须有一个拷贝构造函数。可以自己定义拷贝构造函数,用于按照需要初始化新对象;如果没有定义类的拷贝构造函数,系统就会自动生成一个默认拷贝构造函数,用于复制出与数据成员值完全相同的新对象。
自定义:
类名::类名(const 类名 &对象名)
{	
		//函数体     通过自定义可以 有选择有变化地复制,如乘2后赋值
}
调用:
类名 对象2(对象1);//代入法
类名 对象2=对象1; //赋值法

当类中有指针类型时,调用默认拷贝构造函数有时会产生错误

调用拷贝构造函数的三种情况:

  1. 当用类的一个对象去初始化该类的另一个对象时;
  2. 当函数的形参是类的对象,调用函数进行形参和实参结合时;
  3. 当函数的返回值是对象,函数调用完毕将返回值(对象)带回函数调用处时。

7.静态成员

为了实现一个类的多个对象之间的数据共享
可以全局变量
将一个数据成员说明为static,静态数据成员。无论建立多少个类的对象,都只有一个静态数据成员的拷贝

static 数据类型 数据成员名;
  1. 静态数据成员的定义与普通数据成员相似,但前面要加上static关键字。
  2. 静态数据成员初始化应在类外单独进行,而且应在定义对象之前进行。一般在main()函数之前、类声明之后的特殊地带为它提供定义和初始化。数据类型 类名::静态数据成员名=初始值;
  3. 静态数据成员属于类(准确地说,是属于类中对象的集合),而不像普通数据成员那样属于某一对象,因此,可以使用“类名::”访问静态的数据成员。格式如下:类名::静态数据成员名
  4. 静态数据成员与静态变量一样,是在编译时创建并初始化。它在该类的任何对象被建立之前就存在。因此,公有的静态数据成员可以在对象定义之前被访问。对象定以后,公有的静态数据成员也可以通过对象进行访问。对象名.静态数据成员名; 对象指针->静态数据成员名;
  5. 私有静态数据成员不能在类外直接访问

在类定义中,前面有static说明的成员函数称为静态成员函数。静态成员函数属于整个类,是该类所有对象共享的成员函数,而不属于类中的某个对象。静态成员函数的作用不是为了对象之间的沟通,而是为了处理静态数据成员。定义静态成员函数的格式如下:

static 返回类型 静态成员函数名(参数表);
类名::静态成员函数名(实参表);
对象.静态成员函数名(实参表);
对象指针->静态成员函数名(实参表);
  1. 静态函数成员主要用来访问静态成员函数。当它与静态数据成员一起使用时,达到了对同一个类中对象之间共享数据的目的。
  2. 私有静态成员函数不能被类外部的函数和对象访问。
  3. 使用静态成员函数的一个原因是,可以用它在建立任何对象之前调用静态成员函数,以处理静态数据成员,这是普通成员函数不能实现的
  4. 编译系统将静态成员函数限定为内部连接,也就是说,与现行文件相连接的其他文件中的同名函数不会与该函数发生冲突,维护了该函数使用的安全性,这是使用静态成员函数的另一个原因。
  5. 静态成员函数是类的一部分,而不是对象的一部分。如果要在类外调用公有的静态成员函数,类名::静态成员函数名()
  6. 非静态成员函数有this指针。静态成员函数没有this指针,可以直接访问本类中的静态数据成员,一般不访问非静态成员,只能通过对象名(或对象指针,对象引用)访问对象的非静态成员

8.友元

类的主要特点之一是数据隐藏和封装,即类的私有成员(或保护成员)只能在类定义的范围内使用,也就是说私有成员只能通过它的成员函数来访问。但是,有时为了访问类的私有成员而需要在程序中多次调用成员函数,这样会因为频繁调用带来较大的时间和空间开销,从而降低程序的运行效率。为此,C++提供了友元来对私有或保护成员进行访问。友元包括友元函数和友元类。

友元函数

友元函数既可以是不属于任何类的非成员函数,也可以是另一个类的成员函数。友元函数不是当前类的成员函数,但它可以访问该类的所有成员,包括私有成员、保护成员和公有成员。

  • 在类中声明友元函数时,需要在其函数名前加上关键字friend。
  • 此声明可以放在公有部分,也可以放在保护部分和私有部分。
  • 友元函数可以定义在类内部,也可以定义在类外部。
将非成员函数声明为友元函数
class Girl{
	public;
	......
	friend void disp(Girl &);
	}
void disp(Girl &x)
{ cout<<x.name<<endl; }
  1. 友元函数虽然可以访问类对象的私有成员,但他毕竟不是成员函数。因此,在类的外部定义友元函数时,不必像成员函数那样,在函数名前加上“类名::”。
  2. 因为友元函数不是类的成员,所以它不能直接访问对象的数据成员,也不能通过this指针访问对象的数据成员,它必须通过作为入口参数传递进来的对象名(或对象指针、对象引用)来访问该对象的数据成员。
  3. 友元是对类的封装机制的补充,友元函数提供了不同类的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。尤其当一个函数需要访问多个类时,友元函数非常有用,普通的成员函数只能访问其所属的类,但是多个类的友元函数能够访问相关的所有类的数据。
class Girl{
	public;
	......
	friend void prdata(const Girl &,const Boy &);
	}	
	class Boy{
	public;
	......
	friend void prdata(const Girl &,const Boy &);
	}
void prdata(const Girl &g,const Boy &b)
{ cout<<g.name<<endl; 
  cout<<b.name<<endl; }
将成员函数声明为友元函数
  • 一个类的成员函数可以作为另一个类的友元,它是友元函数中的一种,称为友元成员函数。
  • 友元成员函数不仅可以访问自己所在类对象中的私有成员和公有成员,
  • 还可以访问friend声明语句所在类对象中的所有成员,这样能使两个类相互合作、协调工作,完成某一任务。
  • 一个类的成员函数作为另一个类的友元函数时,必须先定义这个类。并且在声明友元函数时,需要加上成员函数所在类的类名;
class Girl;	//为Girl类的提前引用声明
	class Boy{
	public;
	......
	void prdata(Girl &);
	}
class Girl{
	public;
	......
	friend void Boy::prdata(Girl &);	//声明类Boy的成员函数prdata为类Girl的友元函数
	}	

void prdata(Girl &x)
{ cout<< name<<endl; 
  cout<<x.name<<endl; }
  int main ()
  {	Boy b("李华",11);
  	Girl g("小红",22);
  	b.prdata(g);
  	return 0; }	//结果是两个对象的内容

友元类

friend 类名;	//此类名为友元类的,这条语句可以放在公有部分,也可在私有部分
class Y{......};
class X{
	....
	friend Y;
	};

当一个类被说明为另一个类的友元类时,它所有的成员函数都成为另一个类的友元函数,这就意味着作为友元类中的所有成员函数都可以访问另一个类中的所有成员。

友元关系单向的,不具有交换性和传递性。

9.类的组合

在一个类中内嵌另一个类的对象作为数据成员,称为类的组合。该内嵌对象称为对象成员,又称为子对象。

class A{
    ···
};
class B{
    A a;
   public:
    ···
};
class X{
	类名1 对象成员1;
	类名2 对象成员2;
	...
	};
类X的构造函数的定义形式:
X::X(形参表0) : 对象成员1(形参表1),对象成员2(形参表2),.......
{	函数体	}				//对象成员1(形参表1),对象成员2(形参表2),.......是 初始化表,作用是对对象成员进行初始化

当调用构造函数X::X()时,首先按各内嵌对象成员在类声明中的顺序依次调用它们的构造函数进行初始化,最后再执行类X的构造函数体,初始化其他成员。析构函数的调用顺序与构造函数相反

10.常类型

常类型的引入就是为了既保护数据共享又防止数据被改动。
常类型是指使用类型修饰符const说明的类型,
常类型的变量或对象成员的值在程序运行期间是不可改变的。

常引用

如果在说明引用时用const修饰,则被说明的引用为常引用。常引用所引用的对象不能被更新。如果用常引用做形参,便不会产生对实参的不希望的更改。

const 类型 & 引用名;

经常用作函数的形参,常参数`

常对象

如果在说明对象时用const修饰,则被说明的对象为常对象。常对象的数据成员在对象的整个生存期内不能改变,为常量且必须要有初值。

类名 const 对象名[	参数表	];	
或者
const 类名 对象名[	参数表	];

在定义对象是必须进行初始化

常对象成员

  • 类的数据成员可以是常量或常引用,
  • 使用const说明的数据成员称为常数据成员。
  • 如果在一个类中说明了常数据成员,那么构造函数就只能通过成员初始化列表对该数据成员进行初始化,而任何其他函数都不能对该成员赋值。
类型说明符 函数名(参数表) const;
  • const是函数类型的一个组成部分,因此在声明函数和定义函数时都要有关键字const。在调用时不必加const。
  • 关键字const可以被用于对重载函数进行区分。
  • 常成员函数可以访问常数据成员,也可以访问普通数据成员。
  • 常数据成员可以被常成员函数访问,也可以被普通成员函数访问
  • 常对象只能调用它的常成员对象,而不能调用普通成员函数。常成员函数是常对象唯一的对外接口。
  • 常对象函数不能更新对象的数据成员,也不能调用该类的普通成员函数,这就保证了在常成员函数中绝不会更新数据成员的值。在这里插入图片描述

4.派生类与继承

继承可以在已有类的基础上创建新的类,新类可以从一个或多个已有类中继承成员函数和数据成员,而且可以重新定义或加进新的数据和函数,从而形成类的层次或等级。其中,已有类称为基类或父类,在它基础上建立的新类称为派生类或子类。
从已有类产生新类的过程,类的派生

1.派生类的概念

派生类的声明

class 派生类名: 继承方式 基类名{
	派生类新增的数据成员和成员函数
};		//继承方式:public或private或protected	默认为私有继承

派生类的构成

派生类中的成员包括从基类继承过来的成员和自己增加的成员
1)派生类从基类接受成员
2)调整从基类接受来的成员
一方面是改变基类成员在派生类中的访问属性
另一方面是派生类可以对基类的成员进行重定义:
在派生类中声明一个与基类成员同名的成员,则派生类中的新成员会覆盖基类中的同名成员
3)在派生类中增加新成员
基类中的构造函数和析构函数是不能被继承的

基类成员在派生类中的访问属性

基类中的成员 在公有派生类中的访问属性 在私有派生类中的访问属性 在保护派生类中的访问属性
私有成员 不可直接访问 不可直接访问 不可直接访问
公有成员 公有 私有 保护
保护成员 保护 私有 保护

派生类对基类成员的访问规则

内部访问:有派生类中的新增成员函数对基类继承来的成员的访问
对象访问:在派生类外部,通过派生类的对象对从基类继承来的成员的访问

  • 私有继承的访问规则
基类中的成员 私有成员 公有成员 保护成员
内部访问 不可访问 可访问 可访问
外部访问 不可访问 不可访问 不可访问
  • 公有继承的访问规则
基类中的成员 私有成员 公有成员 保护成员
内部访问 不可访问 可访问 可访问
外部访问 不可访问 可访问 不可访问
  • 保护继承的访问规则
基类中的成员 私有成员 公有成员 保护成员
内部访问 不可访问 可访问 可访问
外部访问 不可访问 不可访问 不可访问

2.派生类的构造函数和析构函数

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

当创建派生类对象时,首先执行基类的构造函数,后调用派生类的构造函数的顺序执行。析构函数的调用顺序与构造函数的调用顺序正好相反,先调用派生类的析构函数,后调用基类的析构函数。

派生类的构造函数和析构函数的构造规则

  • 当基类的构造函数没有参数,或没有显示定义构造函数,派生类可以不向基类传递参数,可以不定义构造函数
  • 当基类含有带参数的构造函数时,派生类必须定义构造函数,以提供把参数传递给基类构造函数的途径
  • 若基类使用默认构造函数或不带参数的构造函数,则在派生类中定义构造函数时可略去“:基类构造函数名(参数表)”,此时若派生类也不需要构造函数,则可不定义构造函数。
  • 如果派生类的基类也是一个派生类,每个派生类只需负责其直接基类数据成员的初始化,依次上溯。
派生类名 (参数总表): 基类名(参数表)
{
	派生类新增数据成员的初始化语句
}
派生类名 (参数总表)中包括参数的类型和参数名
基类名(参数表)中只有参数名,没有类型,因为这里是调用基类构造函数,这些参数是实参,可以为派生类构造函数总参数中的参数,也可以是常量或全局部变量
  • 派生类中可以根据需要定义析构函数,用于对派生类中的新增成员清理,基类的清理仍有基类的析构函数负责
  • 析构函数是不带参数的,派生类中是否要自定义析构函数与它所属的基类的析构函数无关
当派生类中含有内嵌的对象成员(子对象)时:
派生类名 (参数总表): 基类名(参数表0),对象成员名1(参数表1),......
{
	派生类新增数据成员的初始化语句
}

定义派生类对象时,,构造函数的执行顺序:

  • (1)调用基类的构造函数,进行初始化
  • (2)调用内嵌的对象成员的构造函数,进行初始化
  • (3)执行派生类的构造函数体,进行初始化
  • 派生类中含有多个内嵌的对象成员时,调用内嵌的对象成员的构造函数顺序由在类中声明的顺序确定
  • 如果一个派生类的基类也是一个派生类,每个派生类只需负责其直接基类数据成员的初始化,依次上溯

3.调整基类成员在派生类中的访问属性的其他方法

同名成员

派生类可以声明与基类成员同名的成员。在没有虚函数的情况下,如果在派生类中定义了与基类成员同名的成员,则称派生类成员覆盖了基类的同名成员,在派生类中使用这个名字意味着访问在派生类中声明的成员。为了在派生类中使用与基类同名的成员,必须在该成员名之前加上基类名和作用域标识符“::”,即

基类名::成员名
class X{
	public:
		int f();
};
class Y:public X{
	public:
		int f();
		void q();
};
void Y::q()
{	f();		//表示访问派生类中的f(),即Y::f()
	X::f();   //表示访问基类中的f()
}
Y obj;
obj,f();		//表示访问派生类中的f(),即Y::f()
obj.X::f();

访问声明

访问声明的方法就是把基类的保护成员或共有成员直接写在私有派生类定义式中的同名段中,同时给成员名前冠以基类名和作用域标识符“::”。利用这种方法,该成员就成为派生类的保护成员或共有成员了。

class B:private A{
	public:		//
		.....
		A::print;		//printA中的函数
		......
};
  • 数据成员也可以使用访问声明
  • 访问声明中只包含不带类型和参数的函数名或变量名
  • 不能改变成员在基类中的访问属性
  • 对于基类中的重载函数名,访问声明将对基类中所有同名函数起作用

4.多重继承

多重继承派生类的声明

class 派生类名:继承方式1  基类名1,.......,继承方式n  基类名n{
	派生类新增的数据成员和成员函数
};
//不写继承方式则默认是private

多重继承派生类的构造函数和析构函数

派生类名 (参数总表): 基类名1(参数表1),基类名2(参数表2),......,基类名n(参数表n)
{
	派生类新增成员的初始化语句
}

  • 多重继承派生类构造函数必须同时负责该派生类所有基类构造函数的调用。
  • 多继承构造函数的调用顺序与单继承构造函数的调用顺序相同,也是遵循先调用基类的构造函数,再调用对象成员的构造函数,最后调用派生类构造函数的原则。析构函数的调用与之相反。
  • 处于同一层次的各个基类构造函数的执行顺序,取决于声明派生类时所指定的各个基类的顺序,与派生类构造函数中所定义的成员初始化列表的各项顺序没有关系

虚基类

  • 虚基类的作用: 如果一个类有多个直接基类,而这些直接基类又有一个共同的基类,则在最低层的派生类中会保留这个间接的共同基类数据成员的多份同名成员。在访问这些同名成员时,必须在派生类对象名后增加直接基类名,使其唯一地标识一个成员,以免产生二义性。
  • 在C++中,如果想使这个公共的基类只产生一个复制,可以将这个基类说明为虚基类
虚基类在派生类中的声明	//用在中间的类,base,base1,base2,derved,在base1,base2需要
class 派生类名:virtual 继承方式 基类名{
...}
#include <iostream>
#include <string>
using namespace std;

class Base{
   
    
protected:
	int a;
public:
	Base(){
   
    
		a = 5;
		cout << "Base a = " << a << endl;
	}
};

class Base1: public Base{
   
    
public:
	Base1() {
   
    
		a = a + 10;
		cout << "Base1 a = " << a << endl;
	}
};

class Base2: public Base{
   
    
public:
	Base2() {
   
    
		a = a + 20;
		cout << "Base2 a = " << a << endl;
	}
};

class Derived: public Base1, public Base2{
   
    
public:
	Derived() {
   
    
		cout 
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/csdn159357258456/article/details/125374114

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签