开始了类的学习,是个难啃的硬骨头,下面开始对类学习的总结。
一、知识总结
简单来说,类是一个包含函数的结构体。是对具有相同属性和行为的一组对象的抽象与统一描述。比如学生信息统计表,定义一个student类,其中的学号,姓名,各科成绩这些数据成员就是用来描述学生的属性。下面介绍一下类的格式:
class student//类名,表示声明一个类,例如student
{
public://共有数据成员或成员函数的定义
protected://保护数据成员或成员函数的定义
private://私有数据成员或成员函数的定义
};//分号不可省略;
实现各个成员函数,就是成员函数功能的函数。
注意:访问权限关键词先后顺序无关紧要,且可使用多次。如果前面没有标明访问权限,则默认访问权限为private.
(一)成员函数
类的成员有数据成员和函数成员两类,统称为类的成员,类的数据成员一般用来描述该类对象的属性;函数成员是描述类行为;函数成员由函数构成,这些函数就是成员函数。成员函数的定义和声明格式与非成员函数的格式相同,可以放在类中定义,称为内联函数,在类中声明函数的方法与一般函数原型的声明一样,在类外定义函数体的格式:
返回值类型 类 ::成员函数(形参)//::为作用域分辨符,表明后边的函数属于前面的类
{
函数体;
}
注:在类外定义的成员函数前加inline即可使其成为内联函数,与在类内定义成员函数的效果相同。
(二)对象
对象是什么呢,可以说对象是类的实体,类相当于一种包含函数的自定义数据类型,不占内存,类建立对象后对象占据内存,从而变成一个实体。类与对象的关系,如同C++基本数据类型和该类型的变量之间的关系。定义类的格式如下:
类名 对象名1,对象名2,…,对象名n;
注意必须在定义了类之后,才可以定义类的对象。
(三)类成员的访问
类的访问形式主要有两种,一种是圆点访问形式即:对象名.公有成员;
另一种是指针访问形式:对象指针变量名->公有成员
#include<iostream.h> class ptr_access { public: void setvalue(floata, float b) { x=a; y=b; } float Getx() {return x;} float Gety() {return y;} void print() { cout<<"x="<<x<<endl; cout<<"y="<<y<<endl; } private: //私有数据成员 floatx,y; }; | intmain() { floata1,a2; ptr_access*ptr=new ptr_access; ptr->setvalue(2,8); //通过指针访问公有成员函数 ptr->print(); a1=(*ptr).Getx(); //通过公有成员函数访问私有数据成员 a2=(*ptr).Gety(); cout<<"a1="<<a1<<endl; cout<<"a2="<<a2<<endl; return 0; } |
(四)构造函数和析构函数
我们希望在定义一个对象的同时,能给他的数据成员赋初值,这时,C++给我们提供了专门用于对象初始化的函数,即构造函数。构造函数是与类名相同的在建立对象时自动调用的函数。在用户没有定义构造函数的情况下,系统会自动生成一个默认形式的隐含的构造函数,它不具备任何功能。而如果用户定义了构造函数,C++就不会生成默认的构造函数,而是从定义的函数里选择最合适的进行对对象的初始化。构造函数一般定义为公有函数。
构造函数可以直接访问类的所有数据成员,可以带有参数表以及默认形参值。一个类可以有多个构造函数,可以根据参数个数不同或参数类型的不同来区分它们,即构造函数重载。注意构造函数的雷鸣必须和类名完全相同,而一般成员函数则不能和类名相同。构造函数不能直接被调用,是在创建对象是有编译器自动调用。下面定义一个Data类的构造函数。
#include <iostream.h>
class Date {
public:
Date(); // 无参构造函数
Date(int y,int m,int d);
void showDate();
private:
int year, month, day;
};
Date::Date() // 构造函数的实现
{ year=0; month=0; day=0; }
Date::Date(int y,int m,int d)
{ year=y; month=m; day=d; }
inline void Date::showDate()
{ cout<<year<<"."<<month<<"."<<day<<endl; }
int main()
{
Date a_date,bDate(2014,3,25);
a_date.showDate();
b_date.showDate();
return 0;
}
析构函数是用于取消对象的成员函数。当一个对象的作用域结束时系统在对象消失之前自动调用的。析构函数的形式为在构造函数前加一个逻辑非运算符~,析构函数也没有逻辑运算符,析构函数一般由用户自己定义,在对象消失时由系统自动调用。
1.利用构造函数创建对象
(1) 利用构造函数直接创建对象.其一般形式为:
类名 对象名[(实参表)];
(2) 利用构造函数创建对象时,通过指针和new来实现。其一般语法形式为:
类名 *指针变量 = new 类名[(实参表)];
class Date
{
int d, m, y;
public:
Date(intdd, int mm, intyy)
{
d=dd;
m=mm;
y=yy;
}
Date(intdd, int mm)
{
d=dd;
m=mm;
}
}//使用构造函数的函数体进行构造函数数据沉远的初始化class Date
{
int d, m, y;
public:
Date(intdd, int mm, intyy):d(dd),m(mm),y(yy)
{ }
Date(intdd, int mm): d(dd),m(mm)
{ }
}
注意必须使用阐述初始化列表对数据的三种情况:
数据成员为常量;
数据成员为引用类型;
数据成员为无参构造函数的类的对象。
#include< iostream>
using namespace std;
classCMyClass{
public:CMyClass(int x, inty):m_y(x),m_x(m_y)
{
cout<<"m_x="<<m_x<<endl;
cout<<"m_y="<<m_y<<endl;
}
private:
int m_x,m_y;
};
int main()
{
CMyClass mc(15,10);
return 0;
}
如上一段函数,运行结果m_y=15,m_x为随机数,由此就可以得出类成员函数的初始化规律,是按照数据成员在类中的声明顺序进行初始化与初始化成员列表中出现顺序无关。
4.带默认值的构造函数
#include<iostream>
usingnamespace std;
class Box
{
public:
Box(); //定义了全部带默认值的构造函数,不能再定义无参构造函数
Box(int h=10,int w=10 , intl=10); //只能在声明时指定默认值
int volume();
private:
int height,width, length;
};
Box::Box(inth, int w,int l):height(h),width(w),length(l)
{}
intBox::volume(){
return width*length*height;
}
int main()
{
Box box1;
cout<<"The volume is"<<box1.volume();
Box box2(12,30,25);
cout<<"The volume is"<<box2.volume();
Box box3(30,25);
cout<<"The volume is"<<box3.volume();
return 0;
}
运行结果:box1值全为10,box2值为12,30,25,box3值为30,25,10。
(五)复制构造函数
1.定义
复制构造函数就是用一个已有同类对象创建新对象进行数据初始化。其格式如下:
语法形式 类名::类名(const类名&引用名);//const保护对象只读
复制构造函数名与类名相同,并且无返回值,必须要有一个类类型的参数,可在类内也可在类外,没有显示定义的话,系统自动提供一个默认形式的构造函数。
具体工作看如下引例:
#include <iostream>
using namespace std;
class Box
{public:
Box(int=10,int=10,int=10);
Box(const Box&b)
{height=2*b.height;
width=2*b.width;
length=2*b.length;
}
int volume();
private:
int length,height,width;
};
Box::Box(int h,int w,int len)
{height=h;
width=w;
length=len;
}
int Box::volume()
{return width*height*length;}
int main()
{Box box1(1,2,3);
Box box2(box1);
Box box3=box2;
cout<<"the volume of Box1is"<<box1.volume()<<endl;
cout<<"the volume of Box2is"<<box2.volume()<<endl;
cout<<"the volume of Box3is"<<box3.volume()<<endl;
return 0;
}
注意:以下三种情况由编译器系统自动调用
(1)声明语句中用类的一个已知对象初始化该类的另一个对象时。
(2)当对象作为一个函数实参传递给函数的形参时,需要将试产对象去初始化形参对象时,需要调用复制构造函数。
(3)当对象是函数的返回值时,由于需要生成一个临时对象作为函数返回结果,系统需要将临时对象的值初始化另一个对象,需要调用复制构造函数。
浅复制就是指再用一个对象去初始化另一个对象时,只复制了数据成员,而没有复制资源,使两个对象同时指向同一资源的复制方式,即只复制了存储地址而没有复制存储内容,例如默认复制构造函数进行的就是浅复制。
#include<string.h>
using namespace std;
class Person
{public:
Person(char*name1,int a,double s);
void display()
{cout<<name<<'\t'<<age<<'\t'<<salary<<endl;}
~Person()
{delete name;}
private:
char*name;
int age;
double salary;
};
Person::Person(char*name1,int a,doubles)
{name=new char[strlen(name1)+1];
strcpy(name,name1);
age=a;
salary=s;
}
int main()
{
Person*p1=new Person("Wangwei",8,3880);
p1->display();
Person p2(*p1);
delete p1;
p2.display();
return 0;
}
编译会发现姓名不能正确复制。这是因为未自定义复制构造函数,系统调用默认复制构造函数,进行浅复制,而深复制不仅复制了数据成员,同时也复制了资源。自定义复制构造函数是深复制。深复制构造函数必须显式定义,在成员变量处理上,对于复杂数据类型的成员变量,先用new来申请新空间,再进行复制操作.
在程序中加一个复制构造函数如下,就可以输出正确结果了:
Person(const Person&p0)
{
name=new char[strlen(p0.name)+1];
age=p0.age;
salary=p0.salary;
cout<<"ff"<<endl;
}
(六)类的其他成员
1.常成员
(1)常数据成员
常数据成员是指数据成员在实例化被初始化后,其值不能改变。在类的成员函数说明后面可以加const关键字,则该成员函数成为常量成员函数。如果在一个类中说明了常数据成员,那么构造函数就只能通过初始化列表对该数据成员进行初始化,而任何其他函数都不能对该成员赋值。
(2) 常对象
说明对象时用const修饰,则被说明的对象称为常对象,说明形式为
类名 const 对象名[参数表];或者const 类名 对象名[参数表]
定义常对象时必须进行初始化,并且不能更新。不能直接或间接更改常对象的数据成员,常对象只能调用它的常成员函数,静态成员函数,构造函数(有公用访问权限)。
(3)常成员函数
在类中使用关键字const说明的成员函数,说明格式为
类型说明符 函数名(参数表)const
注:函数实现部分也要带const
常成员函数不能更新对象的数据,也不能调用非const修饰的成员函数。
2.静态成员
类成员冠以static声明时,称为静态成员。静态数据成员为同类对象共享。静态成员函数与静态数据成员协同操作。
静态成员不属于某一个单独的对象,而是为类的所有对象所共有,作用不是为了对象之间的沟通,而是为了能处理静态数据成员: 保证在不依赖于某个对象的情况下,访问静态数据成员 。
(1)静态数据成员
静态数据成员在定义或说明时前面加关键字static,如:
class A
{
intn;
static int s;
};
对于类的普通数据成员,每一个对象都各自拥有一个副本。(分配不同的存储空间)对于静态数据成员,每个类只拥有一个副本 。(在静态存储区分配一个存储空间,对所有对象都是可见的)公有访问权限的静态成员,可以通过下面的形式进行访问
类名::静态成员的名字
对象名.静态成员名字
在静态成员函数内部,直接访问。在类外进行静态数据成员的声明
类型 类名::静态数据成员[=初始化值]; //必须进行声明
不能在成员初始化列表中进行初始化如果未进行初始化,则编译器自动赋初值(默认值是0)初始化时不能使用访问权限
(2)静态成员函数
除静态数据成员以外,一个类还可以有静态成员函数。静态函数仅可以访问静态成员,或是静态成员函数或是静态数据成员。静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。静态成员函数没有this指针,只能对静态数据操作 定义静态成员函数的格式如下:
static 返回类型 静态成员函数名(参数表);
与静态数据成员类似,调用公有静态成员函数的一般格式有如下几种:
类名::静态成员函数名(实参表)
对象. 静态成员函数名(实参表)
对象指针->静态成员函数名(实参表)
(七)友元函数
在本类外的其他地方定义了一个函数,这个函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数,在类体中用friend对其进行声明,此函数为本类的友元函数,可以访问本类中的私有成员。要注意使用不同类间友元函数时,定义类的顺序,有时需要对要引用的类做提前声明。
注:友元函数可以访问其他类,但会破坏系统封装性,在这里只是简单介绍一下友元函数的一些用法与概念。
(八)类的包含
类的包含是程序设计中一种软件重用技术。即定义一个新的类时,通过编译器把另一个类“抄”进来。
当一个类中含有已经定义的类类型成员,带参数的构造函数对数据成员初始化,须使用初始化语法形式。
出现成员对象时,该类的构造函数要包含对象成员的初始化,如果构造函数的成员初始化列表没有对成员对象初始化,则使用成员对象的无参即缺省构造函数。建立一个类的对象时,先执行成员对象自己的构造函数,再执行当前类的构造函数。
成员对象的构造函数调用次序和成员对象在类中的说明次序一致(声明顺序为:a1、b1、b2),与它们在成员初始化列表中出现的次序无关(初始化列表顺序为:b1、b2、a1)。析构函数的调用顺序相反.示例如下:
#include <iostream>
using namespace std;
class A
{
public:
A(int x):a(x){}
int a;
};
class B
{public:
B(int x,int y):aa(x){b=y;}
void out()
{cout<<aa.a<<endl<<bb.b<<endl;}
private:
int b;
A aa;
};
int main()
{B ojB(3,5);
ojB.out();
}
(九)对象数组
所谓对象数组是指每一数组元素都是对象的数组。定义一个一维对象数组的格式如下:
类名 数组名[下标表达式];
1.对象数组的初始化
当对象数组所属的类中包含带参的构造函数,可用初始化列表完成对象数组的初始化。
int main(){
Point p1[4];
Point p2[4]={5,6,7,8};
Point p3[4]={Point(9,10),Point(11,12),Point(13,14),Point(15,16)};
Pointp4[4]={Point(17,18),Point(10,20)};
Pointp5[2][2]={21,22,Point(23,24)};
}
当对象数组所属的类中包含无参的构造函数,也可以先定义,再给每个数组元素赋值
int main(){
Point p1[4];
p1[0]=Point(1,1);
}
当对象数组所属的类中包含单个参数的构造函数,可简写。
int main(){
Point p1[4];
Point p2[4]={5,6,7,8};
//Pointp2[4]={Point(5), Point(6), Point(7), Point(8)};
Point p3[4]={Point(9,10),Point(11,12),Point(13,14),Point(15,16)};
Pointp4[4]={Point(17,18),Point(10,20)};
Pointp5[2][2]={21,22,Point(23,24)};
}
1.成员对象数组的初始化
成员对象的初始化可在构造函数初始化列表中进行,推想对于成员对象数组的初始化,也可以在本类构造函数使用初始化列表:
#include <iostream>
using namespace std;
class A{
public:
A(inti = 0):x(i) {}
intx;
};
class B
{
public:
B():a[0](0),a[1](1){} // error C2059: 语法错误: “[”
Aa[2];
};
int main()
{
Bb;
cout<<b.a[0].x<<endl;
cout<<b.a[1].x<<endl;
return0;
}
二、题型总结
下面看一个学生信息统计表的完整代码:
#include<cstdio>
#include<string>
#include<algorithm>
using namespace std;
class Student
{private:
int no;
string name;
int order;
int score[3];
double average;
public:
Student(int id,string nam,int x,int y,int z):no(id),name(nam)
{score[0]=x;
score[1]=y;
score[2]=z;
order=-1;
average=(score[0]+score[1]+score[2])/3.0;
}
Student()
{name="aa";
no=0;
score[0]=score[1]=score[2]=0;
order=-1;
average=0;
}
void setNo(int id)
{
no=id;
}
int getNo()
{return no;}
void setName(string name)
{
this->name=name;
}
string getName()
{
return name;
}
void setOrder(int a)
{
order=a;
}
int getOrder()
{
return order;
}
void setScore(int x,int y,int z)
{
score[0]=x;
score[1]=y;
score[2]=z;
}
void setAverage(double avg)
{
average=avg;
}
double getAverage()
{
return average;
}
void display();
};
void Student::display()
{
cout<<no<<"\t"<<name<<"\t"<<score[0]<<"\t"<<score[1]<<"\t"<<score[2]<<"\t"<<average<<"\t"<<order<<endl;
}
bool cmp1(Student stu1,Student stu2)
{
if(stu1.getAverage()-stu2.getAverage()>=1e-10)
return 1;
else return 0;
}
bool cmp2(Student stu1,Student stu2)
{
return stu1.getNo()<stu2.getNo();
}
class StudentOperation
{
Student list[100];
int n;
public:
StudentOperation():n(0)
{
};
void add();
void sortlist();
void query();
void display(int flag);
void change();
void deleteStu();
int search(int no);
};
void StudentOperation::add()
{
int no,x,y,z;
string name;
system("cls");
cout<<"按学号、姓名、数学、英语、c++顺序输入学生信息,学号输-1结束";
while(cin>>no&&no!=-1)
{
cin>>name>>x>>y>>z;
Student s(no,name,x,y,z);
list[n++]=s;
sortlist();
}
/*for(int i=0;i<n;i++)
{
list[i].display();
}*/
}
void StudentOperation::sortlist()
{
sort(list,list+n,cmp1);
int i;
for(i=0;i<n;i++)
list[i].setOrder(i+1);
}
void StudentOperation::display(int flag)
{
if(flag)
sort(list,list+n,cmp2);
else
sort(list,list+n,cmp1);
cout<<"学号"<<"\t"<<"姓名"<<"\t"<<"数学"<<"\t"<<"英语"<<"\t"<<"c++"<<"\t"<<"平均分"<<"\t"<<"名次"<<endl;
for(int i=0;i<n;i++)
list[i].display();
}
int StudentOperation::search(int no)
{int i;
for( i=0;i<n;i++)
if(list[i].getNo()==no)
return i;
return -1;
}
void StudentOperation::query()
{
int no,i;
system("cls");
cout<<"请输入要查询同学的学号,按-1结束";
while(cin>>no&&no!=-1)
{
i=search(no);
if(i!=-1)
{cout<<"学号"<<"\t"<<"姓名"<<"\t"<<"数学"<<"\t"<<"英语"<<"\t"<<"c++"<<"\t"<<"平均分"<<"\t"<<"名次"<<endl;
list[i].display();
cout<<"请输入要查询同学的学号,按-1结束";
}
else
cout<<"输入学生学号有误,请重新输入,按-1结束";}
}
void StudentOperation::change()
{
int no,i;
int x,y,z,order;
string name;
double average;
system("cls");
cout<<"请输入要修改同学的学号,按-1结束";
while(cin>>no&&no!=-1)
{cin>>name>>x>>y>>z;
average=(x+y+z)/3.0;
i=search(no);
list[i].setName(name);
list[i].setScore(x,y,z);
list[i].setAverage(average);
sortlist();
cout<<"请输入要修改的同学的学号,按-1结束";
}
}
{int i,no;
system("cls");
cout<<"请输入要删除的学生学号按-1结束";
while(cin>>no&&no!=-1)
{
i=search(no);
for(int j=i;j<n-1;j++)
list[j]=list[j+1];
n--;
cout<<"请输入要删除的学生学号按-1结束";
}
}
int main()
{/*Student stu1(1,"ff",66,77,88);
stu1.display();
stu1.setNo(2);
cout<<stu1.getNo()<<endl;
stu1.setName("gg");
cout<<stu1.getName()<<endl;
stu1.setOrder(5);
cout<<stu1.getOrder()<<endl;
stu1.setAverage(86.8);
cout<<stu1.getAverage()<<endl;
stu1.display();
stu1.setScore(89,92,75);
stu1.display();
cout<<endl;
Student stu2;
stu2.display();
stu2.setNo(2);
cout<<stu2.getNo()<<endl;
stu2.setName("gg");
cout<<stu2.getName()<<endl;
stu2.setOrder(5);
cout<<stu2.getOrder()<<endl;
stu2.setAverage(86.8);
cout<<stu2.getAverage()<<endl;
stu2.display();
stu2.setScore(89,92,75);
stu2.display();*/
StudentOperation c;
c.add();
c.display(1);
//c.display(0);
//cout<<c.search(2)<<endl;
c.query();
c.change();
//c.display(1);
c.deleteStu();
//c.display(1);
c.display(0);
return 0;
}
三、学习心得
C++类的学习告一段落了,但其实我现在还是有一些概念似懂非懂,因为身为田径队一员,头几个星期早晨训练,上课时精力的不集中,课上有些东西就没怎么弄懂(还是自己的原因),结果不断积压,到后来一度感觉啥都不会了。还好,经过后来不断的看,不断地问,还好现在跟上了。回想一下那时的自己,其实真的很害怕,真正的感觉到了差距。更怕的是作为计算机专业的学生,刚开始起步,就掉进了深坑。现在勉强跟上了,以后该怎么办心里也有数了。
类的东西看似旁乱无章,实则联系密切,所以我们老师才一再强调我们一定要写完一类,写个主函数调一下类的功能,检查是否有错误。这确实是最有效的,比起一股脑编完后再一条一条的改错误,这样反而更有效。还有最重要的一点,一定要想好怎么构建类,这个类要实现哪些功能。。。