C++中的类可以被简单的理解为一种自定义数据类型。
对于一种数据类型,我们不难理解的是,数据类型有数据和其特定的行为。例如整数类型int
,我们知道其中的数据以整数的形式存储,并且这些整数之间有加减乘除等等具体的计算方式。
同样的,C++中的类将数据与行为封装在一起,成为了一种由程序员设计的、可以更好地满足程序设计需求的数据类型。
声明一个类的语法如下例所示:
class MyClass{
public:
//公有...
int getmaxnum();
int getnum1();
int getnum2();
private:
//私有...
int num1 = 10, num2 = 20;
protected:
//受保护...
};
类中包含了三种“成员访问控制属性”:public
,private
,protected
。
public
:公共的访问接口,允许在类外访问的内容。在这个例子中,我们可以简单地在main()
函数中调用类中的getmaxnum()
函数。
private
:私有成员。私有成员只能由类本身的成员函数访问。在类外部访问任何的私有成员都是非法的。
protected
:保护成员,与私有成员相似,也不能在类外部访问,但是在继承的派生类中可以被派生类的成员函数访问。(关于什么是派生类,我们可以以后再了解)
上面这个类中含有两个成员变量(也叫属性):num1
和num2
。以及三个成员函数:getmaxnum()
,getnum1()
,getnum2()
。
下面展示在主函数中使用我们刚刚所定义的类MyClass
:
#include
class MyClass{
public:
//公有...
int getmaxnum();
int getnum1();
int getnum2();
private:
//私有...
int num1 = 10, num2 = 20;
protected:
//受保护...
};
int MyClass::getmaxnum() {
return num1>num2 ?num1 :num2 ;
}
int MyClass::getnum1() {
return num1;
}
int MyClass::getnum2() {
return num2;
}
int main()
{
MyClass object;
std::cout << object.getmaxnum();
return 0;
}
运行后会看到输出的20
。尽管这个例子没有什么实际用处,但足以说明类的一般框架了。
下面我们分别看看这个类的实现过程:
MyClass
这个类中含有两个成员变量(也叫属性):num1
和num2
。以及三个成员函数:getmaxnum()
,getnum1()
,getnum2()
。
类的概念类似于类型,所以类比于int a;
我们可以通过MyClass object;
来将MyClass
实例化为一个对象object
。
之后,通过点操作符我们调用了其成员函数object.getmaxnum()
并打印出来。MyClass
中的三个成员函数都是公有成员,因此都可以在类之外进行访问。这三个函数在MyClass
之外被定义,所以我们需要作用于解析符::
来表面它是MyClass
中的函数。就像这样:int MyClass::getmaxnum()
当然也可以直接在类的定义之内定义其成员函数:
class MyClass{
public:
//公有...
int getmaxnum() {
return num1>num2 ?num1 :num2 ;
}
int getnum1();
int getnum2();
private:
//私有...
int num1 = 10, num2 = 20;
protected:
//受保护...
};
将函数体直接放到类体内,会将这种成员函数隐式声明为内联函数inline
,这意味着程序执行是会直接将其函数定义复制到函数被调用的位置,这会使编译之后的程序体积增加,但会提高其运行效率,因为避免了调用开销。也可以显式声明内联函数,只需要加一个inline
:
inline int MyClass::getmaxnum() {
return num1>num2 ?num1 :num2 ;
}
使用类实例化一个对象时,需要对其属性(成员变量)的值进行设置。对象进行初始化的过程即为构造函数。
在刚才的例子中,我们并没有为MyClass
单独写一个构造函数。所以编译器实际上单独生成了一个隐患的默认构造函数,但它的函数形参列表和函数体都是空的。这是因为,在实例化对象时调用构造函数是C++的必然行为。
构造函数自行定义的方式与其他成员函数并没有什么太大差别。但是构造函数没有返回值类型,并且构造函数的函数名必须与类名一致。因此语法类似于:
MyClass (int n1,int n2);
/*
*
*/
//函数的实现
MyClass :: MyClass (int n1,int n2){
num1 = n1;
num2 = n2;
}
此处的构造函数,接受两个int
,因此只需要在声明时这样使用:
MyClass object(10,20);
而析构函数,其行为与构造函数相反,会进行一些对象的销毁工作,例如释放被动态分配的内存一面内存溢出。有关这方面的内容可以在深入了解C++的动态内存分配方法之后再了解。
复制构造函数当然也是构造函数,但它用于“复制”一个对象。
什么时候是一种“复制”?
MyClass a(10,20);
MyClass b = a;
在实例化b
的时候会立即调用复制构造函数。
void func(MyClass b){}
复制构造函数拥有与类名相同的函数名,它只有一个形参,是该类对象的引用类型。
MyClass (MyClass &ob);
/*
*
*/
MyClass :: MyClass(MyClass &ob){}
如果没有显式编写复制构造函数,编译器生成的默认构造函数的功能是将初始对象的每一个数据类型都复制到新建立的对象中去。通过自行编写复制构造函数,我们可以使对象的复制具有更多自定义的行为。
在一个类中的数据成员常常会涉及到其他类。例如在刚才的MyClass
类中运用到了int
类的对象作为数据成员。更复杂的可能性是,在定义一个类当中使用了其他由程序编写者定义的类。
在构造一个对象时,如果这个类具有内嵌的对象成员,那么这些内嵌的对象成员会优先被构造。
此时往往会在函数头部分优先调用这些内嵌对象的复制构造函数。
class Point{
public:
Point(int xx,int yy);
Point(Point &p);
private:
int x,y;
};
Point :: Point(int xx,int yy){
x = xx;
y = yy;
}
Point :: Point(Point &p){
x = p.x;
y = p.y;
}
class Line{
public:
Line(Point a,Point b);
Line(Line &l);
double getlength(){return length;}
private:
Point p1,p2;
double length;
};
//Line类的复制构造函数,接受两个Point对象作为形参
Line :: Line(Point a,Point b):p1(a),p2(b){
double xlen = p1.x - p2.x;
double ylen = p1.y - p2.y;
length = sqrt(xlen*xlen + ylen*ylen);
}
//Line类的复制构造函数,接受引用作为形参
Line :: Line(Line &l):p1(l.p1),p2(l.p2){
length = l.length;
}
组合类的构造函数通常定义为
类名 :: 类名(形参表):内嵌对象1(形参表),内嵌对象2(形参表)...{
构造函数体
}
例如
Line :: Line(Point a,Point b):p1(a),p2(b){
double xlen = p1.x - p2.x;
double ylen = p1.y - p2.y;
length = sqrt(xlen*xlen + ylen*ylen);
}
此处p1(a),p2(b)
优先调用了Point
类的构造函数,构造了p1
和p2
。
基本数据类型也是一种预先定义好的类。因此上面Point
的构造函数也可以写作:
Point :: Point(int xx,int yy):x(xx),y(yy){}
本系列博客为我本人原创的学习笔记,尽量勤更新,如有错误欢迎各位大佬指出,Thanks♪(・ω・)ノ