【C++】类的有关基础概念(类的术语,基类子类,构造析构,static静态,虚函数,纯虚函数)
这几天,作者学习了c++有关类的一些知识,在这篇文章中,我会用简练直白的语言描述知识点。c++中“类”(class)给我初见的印象是与c中的“结构”(struct)极为类似,都是对不同类型的成员进行统一封装。但是,c++的类更加复杂多变。①类的基本定义以及术语:(1)类:class Classname { }; 与结构struct相似,封装一定的变量和函数。(2)类的对象:即使用这个类的东西,将这
这几天,作者学习了c++有关类的一些知识,在这篇文章中,我会用简练直白的语言描述知识点。c++中“类”(class)给我初见的印象是与c中的“结构”(struct)极为类似,都是对不同类型的成员进行统一封装。但是,c++的类更加复杂多变。
①类的基本定义以及术语:
(1)类:class Classname { }; 与结构struct相似,封装一定的变量和函数。
(2)类的对象:即使用这个类的东西,将这个类实体化了。
(3)类的属性:不同数据类型的变量。比如unsigned int age , string name等
(4)类的方法:即其中存放的函数,称之为方法,通过这些函数,可以对类中的属性(不一定)进行某些操作。比如void countnumber(int x),state int pls(int x)等
(5)类的成员:即属性和方法的统称。(由于结构体不能封装方法,所以结构体的成员就是属性,说到底就是变量。但是类还可以封装方法,所以就要区分。)
(6)成员的特征:包含public,protected和private。声明成员之前(包括构造和析构函数的创建,一般来说,构造和析构都是public的),必须先写其特征。
<1>public:子类和main函数均可以调用,是一个完全开放的成员。
<2>protected:只有子类可以调用,main函数不可以调用,是一个被保护起来的成员。
<3>private:子类和main函数均不可以调用,是一个完全隐私化的成员。
class example
{
public:
int x;
std::string str;
void getname();
protected:
std::string data;
private:
std::string name;
};
②构造器和析构器(构造函数和析构函数):
(1)构造函数:构造这个类必不可少的部分,表达形式为Classname( );当然,括号里是可以填东西的。
(2)析构函数:对象使用完这个类以后,程序要销毁这些数据防止内存泄漏,这个过程叫析构,写为~Classname( );显然,析构函数不能传入参数。
【注意:构造器和析构器一般置于public中】
class example
{
public:
example();
~example();
};
程序开始运行时,会先执行构造函数example();将这个类创建出来,当执行完这个程序时(或者执行完{ }段落的程序),会激发析构函数,释放空间。
我们将这个过程可视化:
class example
{
public:
example()
{
cout<<"Creating..."<<endl;
}
~example()
{
cout<<"Deleting..."<<endl;
}
};
随意编写一个main函数,运行后即会输出两行:
Creating…
Deleting…
③基类和子类
***子类可以继承基类的所有成员,在此基础上,还可以个性化地添加属性和方法,还可以进行函数的覆盖等操作。
→子类的声明:class A :public B { }; 。A是B的一个子类。
→子类构造函数的声明:
A::A( ):B( ) { } 。需要理解的是,B( )是基类的构造器,A::A( )表示对子类构造器A( )进行书写。而A::A( ):B( ) { }的意思是,用基类已经构造好的构造器去初始化子类的构造器。
也就是说,基类先构造,子类再构造。
那么反过来,子类先析构,基类再析构。这就非常显然。
→子类析构器的声明:
~A( ) = default; (C++11允许添加“=default”说明符到函数声明的末尾,以将该函数声明为显示默认构造函数。这就使得编译器为显示默认函数生成了默认实现,它比手动编程函数更加有效。)
【当然,对于简单的程序,构造函数和析构函数根本没有必要写出,以上内容即本人理解,看看便罢。】
*我来创建一个最基础的基类和子类:
class A
{
public:
int x;
void Print();
};
class B : public A
{
public:
std::string name;
};
④类中的静态属性和方法(static):
若要将一个变量或函数设置为静态,就要在前面加上修饰词static。比如:static int x; static void function( );
(1)静态属性:
在C++的类中,若赋予一个属性“静态”的特点,就表明这个属性被这个类的所有对象共用。如何理解?一般的,我们创建一个类,相当于提供了一个模板,多个对象使用这个类时,它们各自的属性都是单独存储,互不影响的。但如果是静态属性,这个属性便只有单独这一份,大家共用这个属性。
【注】
<1>静态属性必须在类之外再做初始化。比如在class A中声明static int a; 在类A之外就要再写一行初始化int A::a;
<2>默认用0初始化。
(2)静态方法:静态方法只能调用(访问)静态变量,不能调用非静态变量。
如果还不太理解,请看下面的代码:
#include<iostream>
using namespace std;
class A
{
public:
static int a; //A的所有对象共用同一个地址的变量a
int b;
static void Print();
};
int A::a; //在类的外部对静态变量进行申明是必须的。(这个时候不需要带static修饰词)
void A::Print() //静态函数Print只能调用静态变量a,不可以调用非静态变量b
{
cout<<a<<endl;
//cout<<b<<endl;会报错
}
int main(void)
{
A x; x.a=1;
A y; y.a=2;
cout<<x.a<<endl;
A::Print();
return 0;
}
运行输出:
y.a=2将静态属性a的值改变了,所以即便输出x.a,值也是2,而不是1。
static void Print( ) 函数可以调用static int a,但是不能调用int b。
⑤子类函数覆盖基类函数:
***即子类修改了继承于基类的某些函数,实现更为开放的个性化操作。
此时,最好将要被覆盖的函数在基类中定义为虚方法virtual type function( )。
若要用指针完成有关的类操作,这就是一个必要的工作。
(虚方法在⑥中讲解)
⑥虚函数:
在子类中覆写基类中的函数时,基类中的函数需要定义为虚函数virtual type function( );
即在函数类型名前加上关键修饰词virtual。
如果通过指针实现类成员的调用,这个工作就是必要的。
(虚函数的工作原理是让编译器生成一个虚表vtl,找到对应的覆写函数进行正确的覆写,这一块我也不太明白,就先一知半解这样子用着。。。)
请看下面的代码示例:
#include<iostream>
using namespace std;
class A
{
public:
virtual string Print()
{
return "A";
}
};
class B : public A
{
public:
string Print() override
{
return "B";
}
};
int main(void)
{
A *p=new A();
A *q=new B();
cout<<p->Print()<<endl;
cout<<q->Print()<<endl; //我们预期的输出是A B,但如果不使用虚函数,输出就是A A,子类B的Print函数没有成功覆写
delete p;
delete q;
return 0;
}
运行输出:
子类B覆写了基类A的函数string Print( ); 在main函数中创建类的对象时使用了指针,如果不讲基类的被覆写函数设置为虚函数,那么就会覆写失败,输出A A。
【有关C++关键词new的知识,可以参考另一篇博客:------------(暂未更新)】
⑦纯虚函数:
纯虚函数是一种极为特殊的虚函数,作用非常广大,其达到的效果与Java、c#中的“接口”类似。
***纯虚函数指在基类中定义没有实现的函数,然后我们强制子类去实现它。
这相当于基类给出了若干方法的目录,这些目录的实现就交给子类去完成。使用时,也必须通过子类去实现。
(即创建一个类,只由未实现的方法组成,并强制让子类去实现他们,这在面向对象的编程中非常常见!)
(1)声明形式:virtual type function( ) = 0;
即在一般虚函数声明之后加上= 0 ,并且对于这个基类的函数,我们不写明实现的方法。
(2)我们要处理的第二个问题就是如何在main函数中实现纯虚函数类似“接口”的作用。我们只需要在对应的子类中对纯虚函数进行覆写(override)即可。
*请看一段示例代码:
#include<iostream>
using namespace std;
class A
{
public:
virtual string Print() = 0;
};
class B :public A
{
public:
string Print() override
{
return "interface_B";
}
};
int main(void)
{
A *p = new B(); //指针的类型是class A,但是由于B是继承于A的,开辟指针指向的空间可以是class B的大小
cout<<p->Print()<<endl;
return 0;
}
以上是C++中类的一些基础性的问题。笔者初学类,并不是很了解底层的运行原理,只是对表层的语法和知识进行了一些归纳总结,也有一些心得理解。希望可以对和我一样初学C++的朋友有一些帮助。
更多推荐


所有评论(0)