Skip to content

Latest commit

 

History

History
763 lines (611 loc) · 19.2 KB

01.md

File metadata and controls

763 lines (611 loc) · 19.2 KB

文章内容

一.C++中面向对象思想

二.C++中的类和对象

三.构造函数

四.析构函数

五.类的总结

标题C++取名为CPlusPlus是因为C++带符号没法在文章内容中实现跳转,为了方便读标题里的C++改为CPlusPlus。

CPlusPlus中的面向对象思想

面向对象编程(OOP)。要理解这一点,可以想象一本书有序列号和若干页。现在,你的科学书是一本书,就像你的电脑书一样。假设科学图书的序列号为SC12,计算机图书的序列号为CO34,页数分别为200和250。如下图所示:

把书抽象成一个类

因此,在这里,'book'是一个类,它有属性'Page'和'Serial number', 'Science'和'Computer'是它的对象(实例)。

现在你明白了,这个类就像一个基底,有一些定义。对象是遵循这些定义的类的实例。 您可以想到的另一个类和对象的示例是Account(把它想象成一个银行账户)。把Account看作一个类,它的属性是min_balance(最低余额)和rate_of_interest(利息), 我们实例化这两个对象Saving和Current。它们关系如下图:

把银行账户抽象成类

接下来我们要记住几个重要概念。

1.1成员函数

成员函数即是在类中定义的函数。例如,在'Student'类中可以有一个名为'getStrength'的函数来获取学生总数。这里,“getStrength”是“Student”类的成员函数。

1.2继承

很多时候,我们会创建类本身的子类。其子类已被创建的类称为父类。 例如,“Student”类可以包含“BTech”、“MTech”和“Phd”作为子类。在这里,所有三个子类都具有父类的属性以及它们的一些新属性。 另一个例子是“Book”作为父类,“ScienceBook”和“MathsBook”作为子类。 超类的所有方法和属性都将由它的子类显示。子类可能还有一些不同的属性。父类和子类更加常见的说法是基类派生类

CPlusPlus中的类和对象

几个例子去学习

一个例子

#include <iostream>

using namespace std;

class Rectangle
{
	public:
		int length;         //矩形的长度
		int breadth;        //矩形的宽度
		/* 成员函数声明 */
		void setLength( int l );
		void setBreadth( int b );
		int getArea();
};

/* 定义成员函数 */
void Rectangle::setLength( int l )
{
	length = l;
}
void Rectangle::setBreadth( int b )
{
	breadth = b;
}
int Rectangle::getArea()
{
	return length * breadth;
}

int main()
{
	Rectangle rt;
	rt.setLength(7);
	rt.setBreadth(4);
	int area = rt.getArea();
	cout << "Area : " << area << endl;
	return 0;
}
//OUTPUT:
//Area: 28

我们通过创建一个矩形类并通过这段代码能理解如何创建自己的类。 记住,任何程序的执行都是从主函数开始的。因此,在执行时,主函数的第一个语句将首先执行。(分析代码先分析主函数) Rectangle rt;此语句声明rt为类Rectangle的对象。这和说rt是一个矩形是一样的。为此,必须定义“Rectangle”。 class Rectangle我们已经定义了自己的类名为'Rectangle'。'class'是一个关键字,意思是'Rectangle'是一个类。 在Rectangle类中,我们声明了两个变量和三个函数。这些变量和函数属于Rectangle类,因为它们是在类内部声明的,因此称为类的成员。一个类有两种类型的成员:

  • 成员数据-在类Rectangle中,长度和宽度是数据成员,因为它们存储类对象的信息(长度和宽度)。

  • 成员函数-setLength()、setwidth和getArea()是Rectangle类的成员函数。

在此,说明一下定义的Rectangle类相当于C++关键字中的int。

int rt;
Rectangle rt;

注意,在定义成员函数时,我们在函数名之前写了Rectangle::。这是为了告诉编译器函数属于类矩形。 rt.setLength (7);-该语句将调用参数值为7的setLength函数。要调用任何函数,我们使用.点在对象后面,然后调用那个函数。 因为'rt'是'Rectangle'类的对象,所以'rt.setLength'将调用'Rectangle'类的'setLength'函数。这断的意思是将“rt”的length值置为7。 类似地,rt. setwidth(4)将调用函数setwidth并将width的值设置为4。

int area = rt.getArea();“rt”将调用函数getArea(),该函数将返回长度宽度,即28(因为length的值是7,braedth的值是4)。

public:我们声明类的所有成员都是public。public是一个修饰符,它允许从类外部直接访问类的成员。 和public一样,也有其他修饰词,如private和protected。稍后我们将学习更多关于修饰符的内容。 还可以在类声明成员方法时定义成员方法(这样做不符合C++的编码风格慎用),如下面的示例所示。

#include <iostream>

using namespace std;

class Rectangle
{
	public:
		int length;         //矩形的长度
		int breadth;        //矩形的宽度
		/* 成员函数声明 */
		void setLength( int l )
		{
			length = l;
		}
		void setBreadth( int b )
		{
			breadth = b;
		}
		int getArea()
		{
			return length * breadth;
		}
};

int main()
{
	Rectangle rt;
	rt.setLength(7);
	rt.setBreadth(4);
	int area = rt.getArea();
	cout << "Area : " << area << endl;
	return 0;
}
// OUTPUT:
// Area : 28

2.2访问控制修饰符

访问修饰符决定如何访问类的成员。在c++中有三种类型的访问修饰符。

  • public
  • private
  • protected

2.2.1public

当我们将任何类成员声明为public时,该变量在程序中的任何地方都可用,甚至在声明它的函数之外也是如此。让我们看一个例子来理解这一点。

#include <iostream>

using namespace std;

class Rectangle
{
	public:
		int length;         //
		int breadth;        //
		int getArea();
};

int Rectangle::getArea()
{
	return length * breadth;
}

int main()
{
	Rectangle rt;
	rt.length = 7;
	rt.breadth = 4;
	int area = rt.getArea();
	cout << "Area : " << area << endl;
	return 0;
}
// OUTPUT:
// Area : 28

由于我们将变量length和width声明为public,所以我们直接访问它们并为它们赋值。 rt.length = 7;-我们直接通过矩形类的对象访问数据成员'length'。类似地,我们通过直接访问矩形对象来分配宽度。 rt.getArea ();-我们还直接访问getArea(),因为它也被声明为public。 因此,我们可以从任何地方访问声明为public的成员。

2.2.2private

声明为私有的成员只能在声明它的类中访问。因此,类的对象不能像在public中那样直接访问它的成员。

注:默认情况下,类的所有成员都是私有的。 如果我们试图从类外部访问任何类的私有成员,我们将得到一个编译时错误。我们来看一个例子。

using namespace std;

class Rectangle
{
	int length;
	public:
		int breadth;
		void setLength( int l );
		int getArea();
};

void Rectangle::setLength( int l )
{
	length = l;
}
int Rectangle::getArea()
{
	return length * breadth;
}

int main()
{
	Rectangle rt;
	rt.setLength(7);
	rt.breadth = 4;//这一行如果改为rt.length出错了,length为私有成员变量
	int area = rt.getArea();
	cout << "Area : " << area << endl;
	return 0;
}
// OUTPUT:
// Area : 28

在本例中,width和函数setLength和getArea被声明为public。数据成员length是私有的,因为默认情况下所有成员都是私有的。 由于我们不能直接访问任何私有成员,因此不能直接访问length。因此,我们声明另一个成员函数'setLength'为public,它为length赋值7。 其余的成员可以直接从主函数访问。

声明私有最好还是如下,(未定义行为可读性低且又出错的可能)

class Rectangle
{
private:
	int length;
public:
	 int breadth;
	 void setLength( int l);
	 int getArea();
	
};

2.2.3protected

类似于private做个比较的话如下:

protected:可以被1.该类中的函数、2.子类的函数、以及3.其友元函数访问。 但不能被该类的对象访问。

private:只能由1.该类中的函数、2.其友元函数访问。 不能被任何其他访问,该类的对象也不能访问。

private 属性不能够被继承。 使用private继承,父类的protected和public属性在子类中变为private; 使用protected继承,父类的protected和public属性在子类中变为protected; 使用public继承,父类中的protected和public属性不发生改变;

构造函数

3.1构造函数定义

构造函数是一个类的特殊成员函数,它在调用该类的对象时被自动调用。它与类的名称相同,没有返回类型。 构造函数是一种特殊类型的函数,用于初始化对象。它在对象创建时调用。

#include <iostream>

using namespace std;
//构造函数往往声明在内部
class Rectangle
{
	public:
		int length;
		int breadth;
		Rectangle()
		{
			length = 10;
			breadth = 10;
		}
};
//我们还可以在构造函数的主体中不包含任何内容。如:
//Rectangle(){};
int main()
{
	Rectangle rt;
	cout << "length = " << rt.length << endl;
	cout << "breadth = " << rt.breadth << endl;
	return 0;
}
// OUTPUT:
// length = 10
// breadth = 10

在本例中,当我们创建类Rectangle的对象rt时,将自动调用构造函数Rectangle()并初始化对象“rt”的数据成员。它将rt的length和breadth初始化为10。

3.2带参数的构造函数(常用)

#include <iostream>

using namespace std;

class Rectangle
{
	private:
		int length;
		int breadth;
	public:
		Rectangle( int l, int b )
		{
			length = l;
			breadth = b;
		}
		int getArea();
};

int Rectangle::getArea()
{
	return length * breadth;
}

int main()
{
	Rectangle rt( 7, 4 );
	cout << "Area : " << rt.getArea() << endl;
	return 0;
}
// OUTPUT:
// Area : 28

在本例中,构造函数中有参数。如前所述,构造函数也是在创建对象时执行的函数,其名称与其所属类相同。因此,它将像函数一样工作并分配通过Rectangle rt(7,4)传递的值;将7和4赋给length和breadth。

它将创建类'Rectangle'的对象'rt',并将7传递给'l',将4传递给'b' ('l'和'b'用于类'Rectangle'的构造函数中)。

皆是类的成员

3.3C++的构造函数初始化方法

声明如下类

class Rectangle
{
private:
    int length;
    int breadth;
public:
	Rectangle()
	{
		length = 7;
		breadth = 4;
	}
};

在上面的构造函数中,我们分别为数据成员的length和breadth赋值7和4。注意,这里我们将值赋给了这些变量,而不是初始化。 对于具有参数的构造函数,我们可以使用如下方法直接初始化数据成员。 代码如下:

class Rectangle
{
private:
	int length;
	int breadth;
public:
	Rectangle() : length(7),breadth(4){}
};

构造函数名称及其参数之后开始,以冒号(:)开始,后面跟着要初始化的变量列表,变量之间用逗号分隔,变量的值在花括号中。

对于带参数的构造函数初始化如下:

#include <iostream>

using namespace std;

class Rectangle
{
private:
	int length;
	int breadth;
public:
	Rectangle() : length(7), breadth(4){}
	int printArea();
};

int Rectangle::printArea()
{
	return length * breadth;
}

int main()
{
	Rectangle rt();
	cout << rt.printArea() << endl;
	return 0;
}
// OUTPUT:
// 28

这里,我们使用初始化列表直接初始化变量length和width,其中length和width分别初始化为7和4。

另一个参数化构造函数的初始化列表示例。

#include <iostream>

using namespace std;

class Rectangle
{
	int length;
	int breadth;
	public:
		Rectangle( int l, int b ) : length(l), breadth(b)
		{

		}
		int printArea();
};

int Rectangle::printArea()
{
	return length * breadth;
}

int main()
{
	Rectangle rt( 7, 4 );
	cout << rt.printArea() << endl;
	return 0;
}
// OUTPUT:
// 28

在本例中,初始化器列表直接使用l和b的值(分别为7和4)初始化变量length和width。

这与下面的代码相同,唯一的区别是在上面的示例中,我们直接初始化变量,而在下面的代码中,变量被赋参数化值。

class Rectangle
{
    int length;
    int breadth;
    public:
        Rectangle( int l, int b )
        {
            length = l;
            breadth = b;
        }
};
3.3补充:必须用C++初始化构造函数方式的情况

如果我们将某一变量声明为const,那么该变量可以初始化,但不能赋值。

在数据类型之前使用const关键字声明的任何变量都是const变量。 要初始化一个const变量,我们使用C++初始化方式。因为我们不允许给const变量赋值,所以我们不能在构造函数体中赋值。这就是我们需要C++初始化方式的情况。

比如:

#include <iostream>

using namespace std;

class Rectangle
{
private:
	const int length;
	const int breadth;
public:
	Rectangle( int l, int b ) : length(l), breadth(b){}
	int printArea();
}

int Rectangle::printArea()
{
	return length * breadth;
}



int main()
{
	Rectangle rt( 7, 4 );
	cout << rt.printArea() << endl;
	return 0;
}
// OUTPUT:
// 28

在本例中,变量length和width被声明为常量,因此不能在构造函数体中赋值。所以,我们在初始化器列表中初始化它们。 如果我们尝试给一个const变量赋值,我们会得到一个错误。

需要初始化器列表的另一种情况是,我们希望通过创建子类的对象来初始化基类成员。继承中会有详细讨论。

可能还有一些其他情况下,我们需要初始化列表,但我们总是应该总是使用它,即使它不是必要的。

3.4在类的成员函数中使用static

“static”用于访问任何数据变量或函数,而不创建该类的对象。它意味着使用“static”,这样我们就可以访问任何数据变量或函数,而不需要创建该类的对象。

#include <iostream>

using namespace std;

class Rectangle
{
	public:
		static void printArea( int l, int b );
};

void Rectangle::printArea(int l,int b)//这里有个坑,声明在外不用加static
{
	cout<< l*b <<endl;
}

int main()
{
	Rectangle::printArea(4,7);
	return 0;
}
// OUTPUT:
// 28

函数“printArea”是静态的。因此,我们直接在'Rectangle'类中使用它,而没有创建任何对象。

注意,通过类本身调用成员函数时,我们必须使用::来代替点(.)。

在本例中,我们使成员函数为静态的。现在让我们看另一个数据成员是静态的例子。

#include <iostream>

using namespace std;

class A
{
	public:
		static int a;
};

int A::a = 8;

int main()
{
	cout << A::a << endl;
	return 0;
}
// OUTPUT:
// 8

在这里,变量a是静态的,因此可以在主函数中直接访问它。

3.5在函数中返回和传递对象

可以返回或传递对象给一个函数。我们来看一个例子。

#include <iostream>

class Account
{
public:
	int balance;
	Account():balance(0);
	static Account getAcc(Account a, Account b);
};

Account Account::getAcc(Account a, Account b)
{
	Account ac;
	ac.balance = a.balance + b.balance;
	return ac;
}
using namespace std;
int main()
{
	Account a1;
	a1.balance = 50;
	Account a2;
	a2.balance = 60;
	Account b = Account::getAcc(a1,a2);
	cout << b.balance << endl;
	return 0;
}
// OUTPUT:
// 110

正如您所看到的,我们的方法'getAcc'通过获取两个'Account'对象并返回它来创建一个'Account'对象。 b = Account.getAcc ();- 'getAcc'将创建并返回一个新的'Account'对象,'b'将等于该对象。

这里,我们直接在类“Account”(Account .getAcc())上使用“getAcc”方法,因为“getAcc”是一个静态方法 (static Account getAcc(Account a, Account b))。

如果类中没有构造函数,编译器会自动创建一个默认的公共构造函数。

即使我们没有为一个类声明任何构造函数,编译器也会自动声明一个构造函数,这个构造函数在创建该类的任何对象并初始化其数据成员时被调用。 假设我们创建了一个没有任何构造函数的类Vehicle,那么它的构造函数将自动在其主体中不包含任何内容,如下所示。

class Vehicle{
    public:         Vehicle(){ }
}

析构函数

析构函数是与构造函数相反的函数。

构造函数是用来初始化对象的函数。另一方面,析构函数是在对象超出作用域时销毁该对象的函数。

它的名称与前面带有波浪号(~)的类的名称相同。

class A
{
    public:
        ~A();
};

这里,~A()是类A的析构函数。

何时调用析构函数?

当对象超出范围时,析构函数被自动调用。在创建类的对象时,会自动调用非参数化构造函数。所以当对象超出作用域并销毁对象时,就会调用析构函数。

如果对象是用一个新表达式创建的,那么当将delete操作符应用到对象指针时调用析构函数。

析构函数用于释放对象在作用域(生存期)期间获得的内存,以便这些内存可以进一步使用。

#include <iostream>

using namespace std;

class Rectangle
{
private:
	int length;
	int breadth;
public:
	void setDimension(int l, int b);
	int getArea();
	//最好只有析构函数和构造函数写在内部,当然全写外部更好
/*	Rectangle()              // Constructor
	{
		cout << "Constructor" << endl;
	}
	~Rectangle()             // Destructor
	{
		cout << "Destructor" << endl;
	}
	*/
	Rectangle();
	~Rectangle();
};

Rectangle::Rectangle()
{
	cout<<"Constructor"<<endl;
}

Rectangle::~Rectangle()
{
	cout << "Destructor" << endl;
}

void Rectangle::setDimension(int l, int b)
{
	length = l;
	breadth = b;
}

int Rectangle::getArea()
{
	return length * breadth;
}
int main()
{
	Rectangle rt;
	rt.setDimension(7, 4);
	cout << rt.getArea() << endl;
	return 0;
}
// OUTPUT:
// Constructor
// 28
// Destructor

在本例中,当创建了类Rectangle的对象'rt'时,无论我们在类中以何种顺序定义它,它的构造函数都将被调用。在那之后,它的对象称为函数'setDimension'和'getArea'并打印区域。最后,当对象超出范围时,它的析构函数被调用。

请注意,即使我们没有在类中显式地定义析构函数,它也会被自动调用。

类的总结

一个完整的类应该包含四部分:

  1. 成员变量
  2. 成员函数
  3. 构造函数
  4. 析构函数

这里多提一点,构造函数可以有多个比如下面代码可以同时有两个构造函数 A(int t) 或 A() 一个有参数一个无参数以适用不同情况。而析构函数仅能有一个,这是因为析构函数必无参数C++编译器通过函数名和函数参数识别调用哪一个函数,如果函数名相同而参数也相同则无法重载函数故析构函数只能有一个。

#include <iostream>
using namespace std;
class A
{
private:
	int time; // 成员变量
public:
	A(int t):time(t){} // 构造函数
	~A()			   // 析构函数
	{
		//pass
	}
	// 成员函数
	int SetTime(int tt);
	int PrintTime();
};

int A::SetTime(int tt)
{
	time = tt;
}

int A::PrintTime()
{
	return time;
}

int main()
{
	A a(3);
	a.SetTime(9);
	cout<<a.PrintTime()<<endl;
	return 0;
}
// OUTPUT:
// 9