C++学习基础版(二)
目录
五、继承与派生
这一章主要介绍C++继承和派生、C++三种继承方式、C++派生类的构造函数、C++派生类的析构函数以及C++虚基类及虚基类的定义使用。
1、继承和派生
在C++中,比如有两个类,新类拥有原有类的全部属性叫做继承!原有类产生新类的过程叫做派生!
把原有的这个类称之为父类或基类,由基类派生出的类叫做派生类或者叫做子类。
继承和派生的好处:
- 1. 体现面向对象的编程思想,更好的表达各类型之间的关系。
- 2. 派生类除了可以继承基类的全部信息外,还可以添加自己的那些不同的、有差异的信息,就像生物进化的道理一样,派生类在拥有基类的全部基础之上还将更强大。
- 3. 派生类继承到基类的成员是自动、隐藏的拥有,即不需要我们重新定义,这就节省了大量的代码,体现了代码重用的软件工程思想。
例如AlartClock类继承Clock类:
#include<stdio.h>
#include<iostream>
using namespace std;
class Clock{
private:
int H,M,S;
public:
int settime(int h,int m,int s){
this->H=h;
this->M=m;
this->S=s;
return 0;
}
int showtime(){
cout<<H<<":"<<M<<":"<<S<<endl;
return 0;
}
};
class AlarmClock:public Clock{
private:
int AM,AH;
public:
int setalarm(int AH,int AM){
this->AH=AH;
this->AM=AM;
return 0;
}
int showalarm(){
cout<<"Alarttime:"<<AH<<":"<<AM<<endl;
return 0;
}
};
int main(){
AlarmClock A;
A.settime(19,15,20);
A.showtime();
A.setalarm(5,30);
A.showalarm();
return 0;
}
继承关系是一个“:”,前面错了一直写的是“::”。
因此基类和派生类之间的关系是:
class AlarmClock:public Clock
通过冒号表示继承。有公有继承、私有继承和保护继承几种,其中public表示公有继承。
这时候在主函数中定义了派生类的对象A后,就可以直接调用来自基类的settime和showtime方法。
2、三种继承方式
分别是公有继承、私有继承、保护继承。主要区别在于基类中不同访问权限的成员在派生类中的访问权限变化情况。
(1)公有继承【public】
在公有继承的模式下,其特点如下:
- 基类中的公有成员,在派生类中仍然为公有成员,当然无论派生里的成员函数还是派生类对象都可以访问。
- 基类中的私有成员,无论在派生类的成员还是派生类对象都不可以访问。
- 基类中的保护成员,在派生类中仍然是保护类型,可以通过派生类的成员函数访问,但派生类对象不可以访问。
(2)私有继承【private】
在私有继承的情况下,公有类型、私有类型、受保护类型三种成员的访问权限如下:
- 基类的公有和受保护类型,被派生类私有继承吸收后,都变为派生类的私有类型,即在类的成员函数里可以访问,不能在类外访问。
- 而基类的私有成员,在派生类无论类内还是类外都不可以访问。
如果为私有派生,则基类的私有成员在派生类甚至再派生出的子类中,都无法再使用,没有什么存在意义,故这种使用情况比较少。
(3)保护继承【protected】
保护类型的继承,特点如下:
- 基类的公有成员和保护类型成员在派生类中为保护成员。
- 基类的私有成员在派生类中不能被直接访问。
可以看的出来,派生类里的成员函数可以访问基类的公有成员和保护成员,但在类外通过派生类对象则无法访问它们。同样,无论派生类里的成员函数还是通过类对象都无法访问基类中的私有成员。
并根据派生的权限、基类中定义的权限,在派生类的类内和类外不同访问时的组合情况,列出下表:
3、派生类的构造函数
由于派生类包含基类的原因,在创建一个派生类的时候,系统会先创建一个基类。需要注意的是,派生类会吸纳基类的全部成员,但并不包括构造函数及后面讲的析构函数,那么就意味着创建派生类在调用自己的构造函数之前,会先调用基类的构造函数。
#include<stdio.h>
#include<iostream>
using namespace std;
class Clock{
private:
int H,M,S;
public:
Clock(){
cout<<"Clock's constructor called!"<<endl;
}
};
class AlarmClock:public Clock{
private:
int AM,AH;
public:
AlarmClock(){
cout<<"AlarmClock's constructor called!"<<endl;
}
};
int main(){
AlarmClock A;
return 0;
}
从运行结果可以看出,仅仅定义了一个派生类对象,派生类和基类的构造函数会自动调用,调用顺序是先调用基类的构造函数再调用派生类的构造函数。
带参数的基类构造函数调用
需要显示写出来,指定参数传递,来告诉编译器。
一般的写法格式为:
派生类构造函数名(总形参表列):基类构造函数(实参表列)
#include<stdio.h>
#include<iostream>
using namespace std;
class Clock{
private:
int H,M,S;
public:
Clock(){
cout<<"Clock's constructor called!"<<endl;
}
Clock(int h,int m,int s){
this->H=h;
this->M=m;
this->S=s;
cout<<"Clock's Constructor with parameter Called!"<<endl;
}
};
class AlarmClock:public Clock{
private:
int AM,AH;
public:
AlarmClock(){
cout<<"AlarmClock's constructor called!"<<endl;
}
AlarmClock(int h,int m,int s):Clock(h,m,s){
cout<<"AlarmClock's Constructor with parameter Called!"<<endl;
}
};
int main(){
AlarmClock A(8,30,20);
AlarmClock B;
return 0;
}
派生类的构造函数后面通过冒号跟基类的传参,且基类里的参数里为实参,来实现显示的参数调用。
AlarmClock(int h,int m,int s):Clock(h,m,s)
一旦基类中有带参数的构造函数,派生类中则必须有显式传参的派生类构造函数,来实现基类中参数的传递,完成初始化工作。如上述的main函数中的:
AlarmClock A(8,30,20);
4、派生类的析构函数
类似于派生类的构造函数,在派生类中,析构函数也无法被派生类吸收。
析构函数的调用顺序与构造函数则完全相反,即先派生类的析构函数,再基类的析构函数。
#include<stdio.h>
#include<iostream>
using namespace std;
class Clock{
private:
int H,M,S;
public:
Clock(){
cout<<"Clock's constructor called!"<<endl;
}
~Clock(){
cout<<"Clock's deconstructor called!"<<endl;
}
};
class AlarmClock:public Clock{
private:
int AM,AH;
public:
AlarmClock(){
cout<<"AlarmClock's constructor called!"<<endl;
}
~AlarmClock(){
cout<<"AlarmClock's deconstructor called!"<<endl;
}
};
int main(){
AlarmClock A;
return 0;
}
构造函数调用顺序:基类->派生类
析构函数调用顺序:派生类->基类
5、虚基类及虚基类的定义使用
多见于:在多继承关系中,如果一个派生类的从两个父类那里继承过来,并且这两个父类又恰恰是从一个基类那里继承而来。这时候派生类可能会继承两份一样的成员。
假设的一个继承实例:
#include <iostream>
using namespace std;
class Grandfather
{
public:
int key;
public:
};
class Father1:public Grandfather
{
};
class Father2:public Grandfather
{
};
class Grandson:public Father1,public Father2
{
};
int main()
{
Grandson A;
//A.key=9;
return 0;
}
即Grandson类继承两个father类,会有两个key成员,这个时候如果试图使用这个key,注意已经声明为public类型,在主函数中试图赋值时候,会有“不唯一、模棱两可”的错误提示,即所谓的二义性问题。
此时就要使用虚基类!所谓虚基类就是在继承的时候在继承类型public之前用virtual修饰一下。此时派生类和基类就只维护一份一个基类对象。避免多次拷贝,出现歧义。
定义方法即在两个父亲类的派生时增加virtual的声明:
class Father1:virtual public Grandfather
class Father2:virtual public Grandfather
六、多态性
1、多态性实例
多态性是面向对象程序设计的重要特性之一,从字面意思上可以简单理解就是:多种形态,多个样子。其实本质意思也是这样,在面向对象程序设计中,指同样的方法被不同对象执行时会有不同的执行效果。
具体来说,多态的实现又可以分为两种:编译时多态和运行时多态。前者是编译的时候就确定了具体的操作过程,后者是在程序运行过程中才确定的操作过程。这种确定操作过程的就是联编,也称为绑定。
联编在编译和连接时确认的,叫做静态联编,函数重载、函数模板的实例化就属于这一类。
另一种是在运行的时候,才能确认执行哪段代码的,叫做动态联编,这种情况是编译的时候,还无法确认具体走哪段代码,而是程序运行起来之后才能确认。
两者相比之下,静态联编由于编译时候就已经确定好怎么执行,因此执行起来效率高;而动态联编想必虽然慢一些,但优点是灵活。
(1)静态联编
定义了两个类,一个圆点类,一个派生出来的圆类,可以看到主函数的代码,四个输出面积的结果。
#include <iostream>
using namespace std;
#define PI 3.1415926
class Point
{
private:
int x,y;
public:
Point(int x=0,int y=0)
{
this->x = x;
this->y = y;
}
double area()
{
return 0.0;
}
};
class Circle:public Point
{
private:
int r;
public:
Circle(int x,int y,int R):Point(x,y)
{
r = R;
}
double area()
{
return PI*r*r;
}
};
int main()
{
Point A(10,10);
cout<<A.area()<<endl;
Circle B(10,10,20);
cout<<B.area()<<endl;
Point *p;
p = &B;
cout<<p->area()<<endl;
Point &pp=B;
cout<<pp.area()<<endl;
return 0;
}
四个输出:
- 第一个cout输出A的面积,是Point类中的area方法,因为area函数总是返回0,所以输出面积为0;
- 第二个cout输出B的面积,很明显是派生类Circle的area方法,面积自然按公式计算,得出1256.64的值;
- 第三个cout输出的是Point类型指针p指向的Circle类对象的area方法,它输出了0。很明显是执行了Point类里的area方法。这里C++实行的是静态联编,即在编译的时候就依据p的类型来定执行哪个area,因此是0;【换而言之,声明了一个指向point的指针p,并将其指向B,并调用p->area()。但是因为指针的静态类型是
Point*
,编译器在编译时将根据静态类型来确定调用的函数。即使指针指向的实际对象是Circle
类型,但编译器会选择调用Point
类中的area
函数,因此输出为0。】 - 第四种cout也同理,把Circle类型的对象赋给Point类型的引用,C++同样实行静态联编,也输出0。
编译器默认采用静态联编的方式,因此如果想实现更加灵活的调用,就应该采用动态联编。
2、虚函数实例
虚函数是一个函数前面用virtual声明的函数,一般形式如下:
virtual 函数返回值 函数名(形参)
{
函数体
}
虚函数的出现,允许函数在调用时与函数体的联系在运行的时候才建立,即所谓的动态联编。那么在虚函数的派生类的运行时候,就可以在运行的时候根据动态联编实现都是执行一个方法,却出现不同结果的效果,就是所谓的多态。
只需要把基类中的area方法声明为虚函数,那么主函数中无论Point类型的指针还是引用就都可以大胆调用,无用关心类型问题了。因为他们会依据实际指向的对象类型来决定调用谁的方法,来实现动态联编。【其实就是指定函数为虚函数,则无论什么类型的指针,都可以直接按照实际指向的对象类型来决定调用谁,以此实现动态联编。】
#include <iostream>
using namespace std;
#define PI 3.1415926
class Point
{
private:
int x,y;
public:
Point(int x=0,int y=0)
{
this->x = x;
this->y = y;
}
virtual double area() //虚函数
{
return 0.0;
}
};
class Circle:public Point
{
private:
int r;
public:
Circle(int x,int y,int R):Point(x,y)
{
r = R;
}
double area()
{
return PI*r*r;
}
};
int main()
{
Point A(10,10);
cout<<A.area()<<endl;
Circle B(10,10,20);
cout<<B.area()<<endl;
Point *p; //虽然定义了point类型的指针
p = &B;
cout<<p->area()<<endl; //但是因为是虚函数,所以就可以直接按照circle类型的调用area函数
Point &pp=B;
cout<<pp.area()<<endl;
return 0;
}
需要注意的地方:
- (1)虚函数不能是静态成员函数或友元函数,因为它们不属于某个对象。因为它们依赖于对象的实例来访问和操作其成员变量和其他成员函数。虚函数通过对象的虚函数表(vtable)来进行动态绑定,以便在运行时选择正确的函数实现。【静态成员函数在类的命名空间中定义和调用,并不与具体的对象关联。友元函数虽然可以访问类的私有成员,但它们也不属于类的实例。】
- (2)内联函数不能在运行中动态确定其位置,即使虚函数在类的内部定义,编译时,仍将看作非内联。【内联函数在编译时将函数的定义插入到调用处,而不是通过函数调用的方式执行函数】【由于内联函数在编译时将函数的定义插入到调用处,而虚函数的调用是在运行时动态确定的,因此内联函数不能实现虚函数的动态调用。】
- (3)构造函数不能是虚函数,析构函数可以是虚函数,而且通常声明为虚函数。
3、虚析构函数实例
在C++中,不能把构造函数定义为虚构造函数,因为在实例化一个对象时才会调用构造函数,且虚函数的实现,其实本质是通过一个虚函数表指针来调用的,还没有对象更没有内存空间无法调用,故没有实例化一个对象之前的虚构造函数没有意义也不能实现。
但析构函数却是可以为虚函数的,且大多时候都声明为虚析构函数。这样就可以在用基类的指针指向派生类的对象在释放时,可以根据实际所指向的对象类型动态联编调用子类的析构函数,实现正确的对象内存释放。
例如:点和圆
(1)基类没有使用虚析构函数,此时会造成内存泄露
#include <iostream>
using namespace std;
class Point
{
private:
int x,y;
int *str;
public:
Point(int x=0,int y=0)
{
this->x = x;
this->y = y;
str = new int[100];
}
~Point()
{
delete []str;
cout<<"Called Point's Destructor and Deleted str!"<<endl;
}
};
class Circle:public Point
{
private:
int r;
int *str;
public:
Circle(int x,int y,int R):Point(x,y)
{
r = R;
str = new int[100];
}
~Circle()
{
delete []str;
cout<<"Called Circle's Destructor and Deleted str!"<<endl;
}
};
int main()
{
Point(10,10);
Point *p;
p = new Circle(10,10,20);
delete p;
return 0;
}
基类中没有用virtual声明的析构函数,且基类和派生类当中都有动态内存开辟,那么我们在主函数中也动态开辟内存的方式创建一个Circle类,然后删除。但此时仅仅调用了基类的析构函数,这样一来派生类中new出来的4*100字节的内存就会残留,造成内存泄漏。【因为在实例化派生类之前,要先实例化基类】
(2)但如果将基类的析构函数声明为virtual,此时会先释放派生类的空间,然后再释放基类的内存空间。
#include <iostream>
using namespace std;
class Point
{
private:
int x,y;
int *str;
public:
Point(int x=0,int y=0)
{
this->x = x;
this->y = y;
str = new int[100];
}
virtual ~Point() //virtual基类的析构函数
{
delete []str;
cout<<"Called Point's Destructor and Deleted str!"<<endl;
}
};
class Circle:public Point
{
private:
int r;
int *str;
public:
Circle(int x,int y,int R):Point(x,y)
{
r = R;
str = new int[100];
}
~Circle()
{
delete []str;
cout<<"Called Circle's Destructor and Deleted str!"<<endl;
}
};
int main()
{
Point(10,10);
Point *p;
p = new Circle(10,10,20);
delete p;
return 0;
}
4、纯虚函数与抽象类总结
纯虚函数,就是没有函数体的虚函数。定义格式如下:
virtual 返回值 函数名(形参)=0;
前面virtual与虚函数定义一样,后面加了一个=0。表示没有函数体,这就是一个纯虚函数。包含纯虚函数的类就是抽象类,一个抽象类至少有一个纯虚函数。
抽象类的存在是为了提供一个高度抽象、对外统一的接口,然后通过多态的特性使用各自的不同方法,是C++面向对象设计以及软件工程的核心思想之一。
抽象类的特点:
- 抽象类无法实例出一个对象来,只能作为基类让派生类完善其中的纯虚函数,然后再实例化使用。
- 抽象类的派生来依然可以不完善基类中的纯虚函数,继续作为抽象类被派生。直到给出所有纯虚函数的定义,则成为一个具体类,才可以实例化对象。
- 抽象类因为抽象、无法具化,所以不能作为参数类型、返回值、强转类型。
- 抽象类通常用于定义接口,用于指定派生类应该实现的方法。由于抽象类无法被实例化,因此也不能作为函数的参数类型。
- 由于抽象类无法被具体化为对象,所以也不能进行强制类型转换为抽象类的类型。
- 抽象类可以定义一个指针、引用类型,指向其派生类,来实现多态特性。
七、异常处理
1、异常的概念
程序的错误通常包括:语法错误、逻辑错误、运行异常。
(1)语法错误
程序代码不符合语法要求,在编译、链接时候就由编译器提示出来的错误。
(2)逻辑错误
编译没问题,没有错误,可以运行起来。但程序的输出结果或执行过程不如愿,达不到预期的结果,需要不断的调试、测试来发现。
(3)运行异常
运行异常(exception)是指程序在运行过程中由于意外的情况,造成的程序异常终止,比如内存不足、打开的文件不存在、除数为0的情况等等。
其中第三种可以通过进行预见性的处理,来避免程序崩溃,从而保障程序的健壮性。
前面提到的if…else就是一种比较常见的异常处理。比如:
cin>>a>>b;
if(b==0)//捕获异常
{
cout<<"Drivide 0!"<<endl;
}
else
{
cout<<a<<"/"<<b<<"="a/b<<endl;
}
通过if来进行判断,从而对关键部分进行捕获和预防,但这种方式在使用过程中往往会因为if判断过多,使程序的易读性降低,并且对于需要判断函数返回值的情况,对于那些没有返回值的函数,就没用了。因此就要使用异常处理方案了。
2、异常处理机制try catch实例
try catch是一种更加结构化的异常处理机制,这种结构化机制可以把程序中正常执行的代码和异常处理的部分分开表示,使程序变得更清晰易读。
异常处理的结构:
try
{
//正常程序执行语句
throw (异常类型表达式);
}
catch(异常类型1)
{
//异常处理代码
}
catch(异常类型2)
{
//异常处理代码
}
catch(异常类型3)
{
//异常处理代码
}
//后续代码
以上是C++中异常处理的代码形式,用到了try、throw、catch三个关键词。
代码在执行时,首先遇到try代码块,作用就是启动异常处理机制,检测try代码执行中遇到的异常,然后通过throw进行抛出,throw当中的异常类型表达式是常量或变量表达式。接下来会和后面的catch语句块进行匹配(捕获),然后执行对应的代码。如果没有发现可以匹配的类型则,则继续向下执行。如若未找到匹配,则自动调用terminate()结束函数,默认功能是abort()终止程序。
练习:除法运算中除数为0时的异常处理
#include<iostream>
using namespace std;
int main(){
int a,b;
cin>>a>>b;
try{
if(b==0)
throw "error b<0";
}
catch(const char *str){
cout<<str<<endl;
}
catch(int){
cout<<"throw it"<<endl;
}
return 0;
}
输出:
- 输入非零值:无输出(空白行)
- 输入零值:输出 "error b<0"
3、标准异常exception处理类
C++提供了标准的异常处理类,它用来抛出C++标准库中函数执行时的异常。C++提供的标准异常类的层次结构如图:
可以看出,所有的异常类都继承自exception基类,exception类下的logic_error和runtime_error又是两个比较大类,包含有多个自类,它们分表代表逻辑类错误和运行时错误。
举例说明,如:
1. 使用new开辟内存时,如果遇到空间不足,则会抛出bad_alloc异常。
2. 使用dynamic_cast()进行动态类型转化失败时,则抛出bad_typeid异常。
3. 在计算数值超过该类型表示的最大范围时,则抛出overflow_error异常,表示运算上溢,同理,underflow_error表示运算下溢。
4. 在使用string类下标但越界时,则抛出out_of_range异常。
需要注意的是,使用C++自带的标准异常类,需要包含对应的头文件,因为exception、bad_exception类在头文件exception中定义,bad_alloc类在头文件new中定义,bad_typeid类在头文件typeinfo中定义,ios_base::failure类在头文件ios中定义,其他异常类在stdexcept中定义。
练习:使用C++标准异常类
#include<iostream>
#include<new>
#include<stdexcept>
using namespace std;
int main()
{
string *s;
try{
s=new string("qwertyuioasdfghj");
cout<<s->substr(60,0);
}
catch(bad_alloc &t){
cout<<"Exception occurred:"<<t.what()<<endl;
}
catch(out_of_range &t){
cout<<"Exception occurred:"<<t.what()<<endl;
}
return 0;
}
如果是在字符串的正常范围内,输出为空白。如果超出字符串的长度,就会提示出错:
八、文件操作
1、读写文件操作
C++中对于文件的操作,主要是用过以下几个类来支持的,它们分别是:
ofstream:写操作(输出)的文件类(由ostream引申而来)
ifstream:读操作(输入)的文件类(由istream引申而来)
fstream:可同时读写操作的文件类(由iostream引申而来)
它们都需要包含头文件:
#include <fstream>
对文件的读写操作也是三个步骤,分别是:
- 1. 打开文件
- 2. 读写文件
- 3. 关闭文件
2、打开文件
首先打开文件,需要用到在fstream类中的成员函数open()实现打开文件的操作,open函数是istream、ostream、fstream的成员函数,它的原型如下:
void open(const char *filename, ios::openmode mode);
第一个参数filename表示要打开的文件路径。
第二个参数mode表示打开文件的模式。
参数 | 作用 |
ios::in | 为输入(读)而打开文件 |
ios::out | 为输出(写)而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 所有输出附加在文件末尾 |
ios::trunc | 如果文件已存在则先删除该文件 |
ios::binary | 二进制方式 |
除此以外,还可以用过‘|’或运算符将多个参数进行如何使用,如:
1 ofstream out;
2 out.open("dotcpp.txt", ios::out|ios::binary) //以二进制模式打开,进行写数据
3、读文件
C++的输入输出用到了iostream头文件。
文件的读写其实也是流,叫做文件流,因此头文件需要包含fstream,它定义了三个类,负责文件的读、写、读写操作,分别如下:
类型 | 意义 |
ofstream | (out) 表示输出文件流,用于创建文件并向文件写入信息。 |
ifstream | (in)表示输入文件流,用于从文件读取信息。 |
fstream | (file)表示文件流,且同时具有 ofstream 和 ifstream 两种功能,这意味着它可以 |
如果想读一个文件的内容,那么就可以使用ifstream或fstream类型即可,代码如下:
#include <fstream>
#include <iostream>
using namespace std;
int main ()
{
char data[100];
// 以写模式打开文件
ifstream file;
file.open("d:\\1.dat");
file >> data;
cout<<data;
// 关闭打开的文件
file.close();
return 0;}
假设D盘下已经存在一个文件1.dat文件,其内容为一行字符串,则运行结束后会直接输出文件中的字符串内容。
需要注意的是,对于C/C++而言,它可以打开读写的文件并非只能是txt文件,比如样例代码中是dat文件,事实上任何后缀文件都可以打开、读写、关闭操作。
读文件是:file >> data; cout<<data;
写文件是:file << data;
4、写文件
对于要用C++进行写文件,则可以用ofsteam或fstream类型,依次经过打开、写数据、关闭三个步骤完成。
#include <fstream>
#include <iostream>
using namespace std;
int main ()
{
char data[100]="hello world!";
ofstream file;
file.open("d:\\1.dat");
file << data;
file.close();
return 0;
}
即便D盘下不存在1.dat这个文件的情况下,运行此程序仍然会自动新建并向其中写入data的数据,运行结果会输出:hello world!
读文件是:file >> data; cout<<data;
写文件是:file << data;
5、关闭文件
打开和读写文件结束后,都需要关闭文件。此时只需要在最后调用close()成员函数即可,即会断开文件与程序的关联,结束操作。
close函数是ifstream、ofstream、fstream的成员函数,在使用时用打开的文件对象通过.直接调用即可。
#include <fstream>
#include <iostream>
using namespace std;
int main ()
{
char data[100]="hello world!";
ofstream file;
file.open("d:\\1.dat");
file << data;
file.close(); //调用close函数,关闭文件操作
return 0;
}
其他需要注意的:
1、私有成员变量和函数形参的关系
class Clock{
private:
int H,M,S;
public:
int settime(int h,int m,int s){
this->H=h;
this->M=m;
this->S=s;
return 0;
}
int showtime(){
cout<<H<<":"<<M<<":"<<S<<endl;
return 0;
}
};
在Clock类中,H、M、S是私有成员变量,而h、m、s是settime函数的参数。
- H、M、S:它们是Clock类的私有成员变量,用于表示小时、分钟和秒钟。这些变量存储了Clock对象的时间信息,并且只能在类的成员函数内部进行访问和修改。私有成员变量通常使用大写字母开头的命名约定,以区别于其他成员和局部变量。
- h、m、s:它们是settime函数的参数,用于接收外部传入的小时、分钟和秒钟值。这些参数是局部变量,只在settime函数的作用域内有效。它们仅在函数执行期间使用,并在函数执行结束后被销毁。参数通常使用小写字母开头的命名约定,以区别于其他变量。
通过使用不同的命名约定,可以准确区分成员变量和函数参数,并且在代码中使用它们时不会产生混淆。在settime函数内部,this->H、this->M、this->S表示访问类的成员变量H、M、S,而h、m、s表示函数的参数。
例如,当调用settime函数时,可以传入具体的小时、分钟和秒钟值,例如obj.settime(12, 30, 45);。这将把参数h的值设置为12,m的值设置为30,s的值设置为45,并将这些值存储到Clock对象的成员变量H、M、S中,从而表示对象的时间。
最后:
上述就是所有C/C++语言学习的基础版本,也是为了系统性补一补之前遗忘的内容。
仅供个人参考,更详细内容可以查找其他资源。
原文地址:https://blog.csdn.net/zhenz0729/article/details/136787063
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!