C++——面向对象程序设计(3)

类和对象

  • 类的定义

1、 类的定义包括数据的定义和方法的定义。
2、类中数据的定义不允许直接进行初始化;
3、方法的定义可以通过类型 类名::方法名()放置类体外,但必须要在类内进行声明。

  • 数据成员访问

除静态成员外,数据成员的访问需要通过对象来实现,类的定义并不会为其分配内存空间,只有实例化后才有内存空间的分配。

1、public:公有成员,可被类内,子类,类外进行访问;
2、protected:保护成员,可被类内,子类进行访问;
3、private:私有成员,仅可被类内进行访问;

使用方式如下:
class User(){
private:
私有数据定义,私有方法定义;
public:
公有数据定义,公有方法定义;
protected:
保护数据定义,保护方法定义;

  • const

类函数后面使用const关键字,表示该方法不会修改类的数据成员,但对于指针来说,可以修改指向的数据,但不能修改地址。

格式:类型 函数名(参数)const{};

  • 指针对象

将类实例化成一个指针,访问数据将.改成->。
1、类名 *实例名
2、类名 *实例名 = new 类名 —— delete 实例名
3、类名 *实例名 = new 类名() —— delete 实例名

如果将类实例为常量指针,则对象只允许调用const方法。
const 类名 *实例名 = new 类名 —— delete (类名 *)实例名

  • 构造函数

定义对象使被调用,系统默认提供了构造函数,可以自定义多个构造函数。只有自定义了构造函数,系统就不会提供默认的构造函数。
功能:方便类的数据初始化。

格式要求如下:
1、构造函数名与类名相同;
2、参数个数没有限制,没有参数的构造函数为默认构造函数;
3、没有返回值
4、方法内可以进行类数据的初始化,通:数据成员(值)实现,放置在函数名和函数体中间。

默认的复制构造函数:
功能:将函数的实例化作为参数进行按值传递,编译器会调用该类的复制构造函数,来复制实际参数到被调用函数。
场景:
1、复制对象把它作为参数传递给函数;
2、通过使用另一个同类型的对象来初始化新创建的对象;
3、复制对象,并从函数返回这个对象;
格式:函数名与类同名,参数只有一个该类的常量引用类型。
建议:编写函数,尽量使用按引用方式传递参数,这样可以避免调用复制构造函数,极大提高程序的执行效率。

  • 析构函数

程序结束或使用delete释放对象时被调用。

格式如下:
1、函数名与类名相同,但函数名前需加~;
2、没有参数
3、没有返回值,不需要类型,连void也不需要加。

  • 内联成员函数

与内联函数类型,定义在类体中的成员函数默认是内联成员函数,定义在类体外需要在类体声明前加上inline关键字或者在类外实现部分加上inline。

一个较为合理的经验准则是, 不要内联超过 10 行的函数.

  • 静态类成员

格式:定义数据前加上static关键字

功能:
1、可直接通过类名进行初始化和访问,也可通过实例访问。
2、一个类的静态成员被类的所有实例共享,且只为它开辟一处内存空间,故只有有一个实例对其进行修改,其他实例的这个值也会随之改变。
可作为成员函数的默认参数,其他成员不行。

注意点:
1、静态成员可以是当前类的类型,其他普通数据成员只能是当前类的指针或引用类型。
2、静态函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。
3、静态函数不能定义为const函数。
4、在类体外定义静态函数,不能标识static关键字。
5、静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建。

  • 隐藏的this指针

定义:在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。
功能:使得类的非静态成员,不同实例之间的操作互不干扰。
机制:编译器在成员函数中自动添加了this指针用于对数据成员或方法的访问,并在函数调用时将实例地址隐含作为实参传递给this。
提示:友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。

  • 运算符重载

目的:为了让对象之间、对象和数据之间实现运算符的相关操作。

下面实现实例间的四则运算:

class User {
public:
	int age;
	User() {
		;
	}
	User(const int age_) {
		age = age_;
	}
	User operator+(const User& user) {
		User aa;
		aa.age =  age + user.age ;
		return aa;
	}
	User operator-(const User& user) {
		User aa;
		aa.age = age - user.age;
		return aa;
	}
	User operator/(const User& user) {
		User aa;
		aa.age = age / user.age;
		return aa;
	}
	User operator*(const User& user) {
		User aa;
		aa.age = age * user.age;
		return aa;
	}
};
User x, y(100), z(50);
x = y + z;
cout<< x.age << endl;
x = y - z;
cout << x.age << endl;
x = y / z;
cout << x.age << endl;
x = y * z;
cout << x.age << endl;

对于自加和自减, 默认情况下,重载符没有参数,表前置,有参数表后置

class User {
public:
	int age;
	User() {
		;
	}
	User(const int age_) {
		age = age_;
	}
	void operator++() {
		++age;
	}
	void operator++(int) {
		age++;
	}
};

下面实现将对象赋值给整型,以及将整型赋值给对象。

class User {
public:
	int age;
	User() {
		;
	}
	User(const int age_) {
		age = age_;
	}
	void operator=(int a) {
		age = a;
	}
	operator int() {
		return age;
	}
};
User x, y(100);
int a(66);
x = a;
cout << x.age << endl;
a = y;
cout <<a << endl;
  • 友元类和友元方法

目的:使得规定的全局函数或者其他类可以访问该类的私有数据和方法。定义友元类可以使得指定类可以访问改类该的私有成员,定义定义友元方法可以指定类中的某一方法可以访问改类的私有成员。

设定友元类:在类的public内加入下面一句,设定类名1为自身类的友元类。
friend class 类名1;
设定友元方法:在类的public内加入下面一句,设定类名1的方法1为自身类的友元方法。
friend 类型 类名1:方法1(参数);
友元方法不仅可以是类的成员函数,也可以是一个全局函数。
friend 类型 函数名(参数);

由于友元方法没有this指针,要访问非static成员时,需要对象做参数;

  • 类的继承

定义:当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
一般格式:
class 子类名: public 基类
一个类从另一个类继承,有三种派生类型,公有型、保护型、私有型。继承导致成员的变换如下:

子类的重写的父类的方法,将会隐藏父类的方法,要想在子类中使用父类的方法,可通过子类.父类::方法名调用即可。

类的继承不包括继承父类的构造函数、解析函数、复制构造函数、友元函数、重载运算符函数。

  • 虚函数

我们可以定义一个父类的类型指针,但指向子类的构造函数创建的实例。
父类 *实例名 = new 子类();

如果此刻通过实例名调用一个父子类共有的方法,那么执行的是谁的方法呢?
答:答案是调用父类的方法,因为编译器对此方法进行的是静态绑定,即根据对象定义时的类型来确定调用哪个类的方法。
那有没有方法让其调用子类的方法呢?
答:有一种成员函数叫“虚方法”,定义前使用virtual关键字,可实现该方法的“动态绑定”,即根据对象运行时的类型来确定调用哪个类的方法,而不是根据对象定义时的类型来确定。

父类的虚函数,即使子类重写了此函数,但也是虚函数。这是因为覆盖的函数放置在了原来父类在虚表的位置。可参考

正常的继承,一般时先调用父类的构造函数、子类的构造函数、子类的析构函数,最后到父类的析构函数。
但如果定义的是父类类型的指针,但指向子类的实例,则正常情况下是不会调用子类的析构函数,这将导致子类中分配内存的数据成员无法被释放,从而出现内存泄漏。但如果析构函数是虚函数,则会先调用子类的析构函数再调用父类的析构函数。
所以,在编写类的析构函数通常是虚函数。

  • 纯虚方法

格式:在虚方法基础上,结尾添加=0;,没有函数体,如下所示:
virtual 类型 函数名(参数) = 0;
作用:包含纯虚方法的类称为抽象类,不能被实例化,通常充当父类。

抽象类的子类如果实现了父类所有的纯虚方法,那么子类就可以进行实例化。

  • 多继承

一般格式:
class 子类名: public 基类1,public 基类2,public 基类3

如果出现不同基类存在同名的方法,而你恰好需要调用它。如果直接调用会出现编译错误,需要通过::指定调用哪个基类。实例名.基类::方法()

  • 虚继承

出现原因:多继承中,如果发生了如:类B继承类A,类C继承类A,类D同时继承了类B和类C。最终在类D中就有了两份类A的成员,这在程序中是不能容忍的。

一般格式:
class 类名: virtual 继承方式 父类名
作用:C++编译系统在实例化D类时,只会将虚基类的构造函数调用一次,忽略虚基类的其他派生类(class B,class C)对虚继承的构造函数的调用,从而保证了虚基类的数据成员不会被多次初始化。

  • 嵌套类和局部类

类中嵌套一个类,嵌套类只能被外围类访问,其他地方是不可见的。
局部类定义在函数体内,这个类只能被函数体内访问,函数体外是不能访问的。

类模板

下面以单向链表为例,通过类模板实现对不同数据类型的结点进行相同操作。

#include <iostream>
using namespace std;
class Snode {
	/*定义结点,存放数据和指向下一个结点的指针*/
public:
	int data;
	Snode* next;
	Snode() {
		next = NULL;
	}
};
class Cnode {
	/*定义结点,存放数据和指向下一个结点的指针*/
public:
	char data;
	Cnode* next;
	Cnode() {
		next = NULL;
	}
};
template<class Type>
class SList {
	/*定义一个链表类,用于创建链表,包含添加、遍历、释放数据的方法*/
private:
	Type* head;  //定义头节点
	int node_sum;  //结点数量
public:
	SList() {
		head = NULL;
		node_sum = 0; //初始化数据
	}
	Type* Movetrail() {
		/*移动结点到链表尾部,方便插入数据*/
		Type* temp = head;  //定义一个临时结点指向头节点
		for (int i = 1; i < node_sum; i++) //有n个结点,就移动n-1次
		{
			temp = temp->next;
		}
		return temp; //返回尾结点
	}
	void Add_node(Type* pnode) {
		/*移动到最后一个结点,添加数据,一次添加一个,需要判断链表是否为空,若为空直接将参数结点赋值给头节点*/
		if (node_sum == 0)head = pnode;
		else {
			Type* temp = Movetrail();
			temp->next = pnode;
		}
		node_sum++;
	}
	void Passlist() {
		/*遍历链表,依次输出结点数据*/
		Type* temp = head;
		if (node_sum == 0)cout << "链表为空" << endl;
		else {
			for (int i = 0; i < node_sum; i++) {
				cout << temp->data << '\t';
				temp = temp->next;
			}
		}
	}
	~SList() {
		/*释放除头结点外的所有结点的内存空间,并将头节点置空。从前往后释放*/
		if (node_sum > 0) {
			Type* pdelete = head;
			Type* temp;
			for (int i = 0; i < node_sum; i++)//n个结点,正常释放n-1个,算上自定义的pdelete结点,共释放n个结点
			{
				temp = pdelete->next;
				delete pdelete;
				pdelete = temp;
			}
			node_sum = 0;
			pdelete = NULL;
			temp = NULL;	
		}
		head = NULL;
	}
};
int main() {
	SList<Snode> list;
	for (int i = 1; i <= 5; i++) {
		Snode* node = new Snode();
		node->data = i;
		list.Add_node(node);
	}
	list.Passlist();
	cout << endl;
	SList<Cnode> list1;
	for (int i = 97; i <= 101; i++) {
		Cnode* node1 = new Cnode();
		node1->data =i;
		list1.Add_node(node1);
	}
	list1.Passlist();
	return 1;
}

类模板内也可以自定义静态数据,不同类型的类模板实例的静态数据是不同的,无法共享。但同类型的模板实例,静态数据是共享的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章