一:知识总结:
1、类的定义和基本概念:
(1)类的定义:
class 类名
{public:公有数据成员和成员函数(类的外部接口)
protected:保护数据成员和成员函数;(仅允许本类函数及派生类成员访问)
private:
私有成员和成员函数;(仅允许本类成员函数访问)
};(不能省略)
类的成员可以是其他类的对象,但不能是类自身的对象,类自身的指针和引用可以作为类的成员。
类与结构体区别:不声明类的成员私有,结构体成员公有。
(2)成员函数:
类的成员函数是实现类的行为属性的成员,一般将其声明为函数原型,在类外具体实现成员函数。
定义方式:返回值类型 类名::成员函数名(参数表)
{函数体
}
在这里总结一下set和get成员函数
void setx(int a) int getx()
{x=a; {return x;}
}
set是修改成员值,get是返回成员值,可以用于在一个类中对另一个类的对象做操作,在做信息管理系统时,经常会用到这两个函数。
(3)对象:
对象的定义方式:类名 对象名1,对象名2;
例如 Point p1,p2;
(4)类成员的访问:
圆点访问 对象名.公有成员
指针访问 对象指针变量名->公有成员;
对指针调用简单举个例子:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; class ptraccess { public: void setvalue(float a,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: float x,y; }; int main() { float a1,a2; ptraccess*ptr=new ptraccess; ptr->setvalue(2,8); ptr->print(); a1=(*ptr).getx(); a2=(*ptr).gety(); cout<<"a1="<<a1<<endl; cout<<"a2="<<a2<<endl; return 0; }
建立一个指针变量,用这个指针去访问成员
(5)内联函数
声明 inline 函数原型 减少频繁调用小子程序的时间开销,适用于短代码
(6)成员函数可以重载,函数重载函数名相同参数不同(类型,个数)
2.构造函数:
(1)构造函数的基本概念:
构造函数用于创建对象,创建对象时自动调用构造函数,作用是为对象分配空间,对数据成员赋初值,用户未定义构造函数时,系统自动提供缺省版本的构造函数,不实现功能,构造函数名与类名相同,构造函数可以重载,构造函数可以有任意类型的参数,但没有返回类型。
(2)构造函数的定义与使用:
#include <iostream> using namespace std; 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,b_date(2014,3,25); a_date.showDate(); b_date.showDate(); return 0; }
a_data调用无参构造函数,b_data调用有参构造函数。在程序中一般要定义两个默认构造函数,其中一个为无参类型。
利用构造函数创建对象两种方法:利用构造函数直接创建 类名 对象名[(实参表)];
指针和new实现 类名*指针名=new类名[实参表];
(3)利用参数初始化表进行初始化
格式举例 Date(int dd,int mm,int yy):d(dd),m(mm),y(yy)
{ 函数体可以为空}
必须用参数初始化表初始化的几种情况:
数据成员为常量,数据成员为引用类型,数据成员为无参构造函数的类的对象。
#include <iostream> using namespace std; class A {public: A(int i):x(i),rx(x),pi(3.14) {} void display() {cout<<"x="<<x<<"rx="<<rx<<"pi="<<pi<<endl; } private: int x,℞ const float pi; }; int main() {A aa(10); aa.display(); return 0; }
&rx是引用,pi是常量,都只能用初始化列表初始化。
#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="<<aa.a<<endl<<"b="<<b<<endl;} private: int b; A aa; }; int main() { B ojb(3,5); ojb.out(); return 0; }
aa为A类的对象,在B类的构造函数中也要进行初始化,只能用参数初始化表,实际上先调用了A类中的构造函数
(4)类成员的初始化顺序:
按照数据成员在类中的声明顺序进行初始化,与初始化成员列表中出现顺序无关。
(5)带默认值的构造函数
#include <iostream> using namespace std; class Box {public: Box(int h=10,int w=10,int l=10); int volume(); private: int height,width,length; }; Box::Box(int h,int w,int l):height(h),width(w),length(l){} int Box::volume() {return height*width*length; } int main() { Box box1; cout<<"the volume is"<<box1.volume()<<endl; Box box2(12,30,25); cout<<"the volume is"<<box2.volume()<<endl; Box box3(30,25); cout<<"the volume is"<<box3.volume(); return 0; }box1 3个值全都是10,box2 12,30,25 box3 30,25,10
3.析构函数:
(1)析构函数的基本概念:
取消对象的成员函数,作用域结束后自动调用,进行对象消亡时的清理工作,用户未定义时,系统提供缺省版本的析构函数,析构函数名为~类名,没有参数,没有返回值,不能重载。
(2)析构函数的定义与使用:
#include <iostream> #include<cstring> using namespace std; class Student {public: Student(int n,char*a_name,char s){num=n;name=new char[strlen(a_name)+1]; strcpy(name,a_name); sex=s; cout<<"Constructor called"<<endl; } ~Student() {cout<<"Destructor called"<<endl; delete [] name; } void display() {cout<<name<<endl; cout<<num<<endl; cout<<sex<<endl; } private: int num; char*name; char sex; }; int main() { Student stud1(10001,"Wang_li",'f'); stud1.display(); Student stud2(10002,"Zhao_Wu",'s'); stud2.display(); return 0; }
注意姓名复制时采用new来动态分配空间,调用析构函数时删除数组的空间。
4.this指针:
作用:指向不同对象。
显式引用this指针的3种情况:
(1)在类的非静态成员函数中返回类对象本身或对象的引用时,直接使用return*this,返回本对象地址时,return this。
#include <iostream> #include<string> using namespace std; class Person {public: Person(string n,int a) {name=n; age=a; } int get_age() {return age; } Person&add_age(int i) {age+=i; return *this;} private: int age; string name; }; int main() { Person Li("Li",20); cout<<"Liage="<<Li.get_age()<<endl; cout<<"Liaddage="<<Li.add_age(1).get_age()<<endl; return 0; }
this指针返回类对象所引用的addage函数,完成年龄的增加。
(2)当参数与成员变量名相同时用this->x=x;
#include <iostream> using namespace std; class Point {public: int x; Point() {x=0; } Point (int a) {x=a; } void print() {cout<<"x="<<x<<endl;} void set_x(int x) {this->x=x;} }; int main() { Point pt(5); pt.set_x(10); pt.print(); return 0; }
直接用x=x,不能完成修改操作
(3)避免对同一对象进行赋值操作
#include <iostream> using namespace std; class Location {int X,Y; public: void init(int x,int y) {X=x; Y=y; } void assign(Location&pointer); int Getx() {return X; } int Gety() {return Y; } }; void Location::assign(Location&pointer) {if(&pointer!=this) {X=pointer.X; Y=pointer.Y; } } int main() { Location x; x.init(5,4); Location y; y.assign(x); cout<<"x.X="<<x.Getx()<<"x.Y="<<x.Gety(); cout<<"y.X="<<y.Getx()<<"y.Y="<<y.Gety(); return 0; }
assign成员函数的形参是一个对象,用this指针判断对象是否相同,避免对同一对象赋值。
5.复制构造函数
(1)复制构造函数的基本概念与基本工作:
复制构造函数用一个已有同类对象创建新对象进行数据初始化。
语法形式 类名::类名(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 Box1 is"<<box1.volume()<<endl; cout<<"the volume of Box2 is"<<box2.volume()<<endl; cout<<"the volume of Box3 is"<<box3.volume()<<endl; return 0; }
注意主函数中box2(box1)调用复制构造函数,把box1的数据乘2复制给box2,再把对象box2复制给对象box3,也调用了复制构造函数。
(2)复制构造函数调用的三种情况:
声明语句中用用类的一个已知初始化该类的另一个对象时。
当对象作为一个函数实参传递给函数形参时,需要将实参对象去初始化形参对象时。
当对象是函数的返回值时
(3)深复制与浅复制:
所谓浅复制,是指再用一个对象去初始化另一个对象时,只复制了数据成员,而没有复制资源,使两个对象同时指向同一资源的复制方式,即只复制了存储地址而没有复制存储内容,例如默认复制构造函数进行的就是浅复制。
浅复制对于复杂数据类型成员的复制会出现问题,如下面这个程序。
#include <iostream> #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,double s) {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来申请新空间,再进行复制操作,上面的程序可以改为
#include <iostream> #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(const Person&p0) {name=new char[strlen(p0.name)+1]; age=p0.age; salary=p0.salary; cout<<"ff"<<endl; } ~Person() {delete name;} private: char*name; int age; double salary; }; Person::Person(char*name1,int a,double s) {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对name申请新空间,就可以输出正确的结果了,
6.常成员:
(1)常数据成员:
数据成员在实例化被初始化后,其值不能被改变。
使用const说明
如果一个类中说明了常数据成员,构造函数只能通过对数据成员进行初始化列表对该数据成员进行初始化,任何其他函数都不能对该成员赋值。
#include <iostream> #include<cstring> using namespace std; struct Date { int year,month,day; }; class Student {public: Student(int y,int m,int d,int num=0,string name="no name"); void Printstudent()const {cout<<birthday.year<<birthday.month<<birthday.day<<endl; cout<<code<<endl; cout<<name<<endl; } private: const int code; string name; Date birthday; }; Student::Student(int y,int m,int d,int num,string name):code(num) {this->name=name; birthday.year=y; birthday.month=m; birthday.day=d; } int main() { Student stud1(1990,3,21,1001,"陈春"); stud1.Printstudent(); Student stud2(1985,10,1,1002,"陈庆华"); stud2.Printstudent(); }
code是常数据成员,只能用参数初始化表去初始化,printstudent是常成员函数,Date是结构体,birthday是结构体成员,结构体的功能与使用与类相似。
(2)常对象
说明对象时用const修饰,说明形式为 类名 const 对象名[参数表];或者const 类名 对象名[参数表]
定义常对象时必须进行初始化,并且不能更新。不能直接或间接更改常对象的数据成员,常对象只能调用它的常成员函数,静态成员函数,构造函数(有公用访问权限)。
(3)常成员函数
在类中使用关键字const说明的成员函数,说明格式为 类型说明符 函数名(参数表)const
函数实现部分也要带const
常成员函数不能更新对象的数据,也不能调用非const修饰的成员函数。
7静态成员
(1)静态数据成员的定义与访问:
类成员冠以static声明时称为静态成员,静态成员为同类对象共享。静态数据成员每个类只有一个副本(类的普通数据成员,每一个对象各自拥有一个副本)
公有访问权限的静态成员,可以通过下面的形式进行访问;
类名::静态成员的名字
对象名.静态成员名字
对象指针->静态成员名字
在静态成员函数内部可以直接访问。
(2)静态数据成员声明及初始化:
在类外进行静态数据成员的声明
类型 类名::静态数据成员[初始化值】(必须进行声明)
未进行初始化编译器自动赋初值。默认为0
通过下面这个样例来看类的静态数据成员的定义和调用
#include <iostream> using namespace std; class Counter {public: Counter(int a) {mem=a; } int mem; static int smem; }; int Counter::smem=1; int main() { Counter c(5); int i; for(i=0;i<5;i++) {c.smem+=i; cout<<c.smem<<'\t'; } cout<<endl; cout<<c.smem<<endl; cout<<c.mem<<endl; return 0; }
smem是静态数据成员,在类外定义并赋初值为1,在主函数中对smem数据成员进行累加,输出每次累加的结果
(3)静态成员函数
静态函数仅可以访问静态成员,或是静态函数成员,或是静态数据成员,对静态成员的引用不需要对象名,静态成员函数没有this指针,只对静态数据操作。
定义静态成员函数的格式 :
static 返回类型 静态成员函数名(参数表)
调用格式与静态数据成员类似,分3种情况。
静态成员函数在类外定义时不用static前缀,静态成员函数不访问类中的非静态数据成员。如有需要,只能通过对象名访问该对象的非静态成员。
通过下面这个程序看一下静态成员函数的使用
#include<iostream> using namespace std; class Smallcat { public: Smallcat(double w) { weight=w; totalweight+=w; totalnum++; } static void display(Smallcat&w) { cout<<w.weight<<endl; } static void totaldisplay() { cout<<totalnum<<" "<<totalweight<<endl; } private: double weight; static double totalweight,totalnum; }; double Smallcat::totalweight=0; double Smallcat::totalnum=0; int main() { Smallcat w1(0.9),w2(0.8),w3(0.7); Smallcat::display(w1); Smallcat::display(w2); Smallcat::display(w3); Smallcat::totaldisplay(); return 0; }
定义一个小猫类,totalweight,totalnum为静态数据成员,totaldisplay为静态成员函数,静态成员函数要调用非静态成员w必须建立一个临时对象w,通过对象名调用。
8友元函数:
注:友元函数可以访问其他类,但会破坏系统封装性,因此我们一般不使用,在这里只是简单介绍一下友元函数的一些用法与概念,为重载部分做基础。
在本类外的其他地方定义了一个函数,这个函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数,在类体中用friend对其进行声明,此函数为本类的友元函数,可以访问本类中的私有成员。
要注意使用不同类间友元函数时,定义类的顺序,有时需要对要引用的类做提前声明。
9.类的包含组合
(1)基本概念:
类的包含是一种软件重用技术,定义一个新类,把另一个类“抄”进来。
当一个类中含有已经定义的类类型成员,用带参数的构造函数对数据成员初始化,需使用初始化语句形式。
出现成员对象时,该类的构造函数要包含对象成员的初始化,如果构造函数的成员初始化列表没有对成员对象初始化,则使用成员对象的无参构造函数。
建立一个类的对象时,先执行成员对象自己的构造函数,再执行当前类的构造函数。
(2)代码示例:
#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(); }
一个简单的示例,aa是A类的对象,调用构造函数时,先调用A的构造函数,对aa初始化,要注意a是公有数据成员,否则出错。
#include <iostream> #include<cstdio> using namespace std; class Score {public: Score(float c,float e,float m):computer(c),english(e),math(m){} Score() {computer=0; english=0; math=0; } void show(); void modify(float c,float e,float m); private: float computer; float english; float math; }; void Score::modify(float c,float e,float m) {computer=c; english=e; math=m; } void Score::show() {cout<<computer<<" "<<english<<" "<<math<<endl; } class Student {private: string name; string stuno; Score score1; public: Student(){} Student(string name1,string stuno1,float s1,float s2,float s3):score1(s1,s2,s3) {name=name1; stuno=stuno1; } void modify(string name1,string stuno1,float s1,float s2,float s3); void show(); }; void Student::modify(string name1,string stuno1,float s1,float s2,float s3) {name=name1; stuno=stuno1; score1.modify(s1,s2,s3); } void Student::show() {cout<<name<<endl; cout<<stuno<<endl; score1.show(); } int main() { Student stu1("LiMing","990201",90,80,70); stu1.show(); cout<<endl; stu1.modify("Zhang Hao","990202",97,95,82); stu1.show(); cout<<endl; return 0; }
一个简单的学生类,对学生信息进行修改输出,注意使用set,get函数来完成在一个类中对另一个类的操作。
#include <iostream> using namespace std; class A {public: A(){cout<<"This is A"<<endl;} A(int i) {cout<<"This is A,and its value ="<<i<<endl;} }; class B {public: B() {cout<<"This is B"<<endl;} B(int i):a2(i){cout<<"Hello B"<<endl;} private: A a1,a2; }; int main() {B b1,b2(9); return 0; }
输出结果如下:
This is A
This is AThis is B
This is A
This is A,and its value =9
Hello B
通过程序来体会构造函数调用的顺序:先执行成员对象自己的构造函数,再执行当前类的构造函数
类的包含与组合在写系统的过程中非常有用,要重点掌握。
10对象数组
(1)定义格式: 类名 数组名【下标表达式】;
(2)主函数中对象数组的初始化有两种方式,当对象数组所属的类中包含带参的构造函数,可以用初始化列表完成对象数组的初始化,初始化的值由参数的个数,形式决定,例如 Point p[4]={Point(9,10),Point(11,12),Point(13,14),Point(15,16)};
当对象数组所属的类中包含无参的构造函数,也可以先定义,再给每个元素赋初值。
例如:Point p1[4];
p1[0]=point(1,1);}
(这一种方法更为常用.)
对象数组中包含单个参数的构造函数可以简写。
(3)成员对象数组的初始化不能用参数初始化列表进行
#include <iostream> using namespace std; class A {public: A() {cout<<"Hello.A"<<endl;} A(int i):x(i){cout<<x<<endl;} int x; }; class B {public: B() {a[0]=A(0); a[1]=A(1);} A a[2]; }; int main() {B b; cout<<b.a[0].x; cout<<endl; cout<<b.a[1].x; return 0; }注意B的构造函数,a数组的初始化不能用参数初始化表。
二、心得体会:
通过这一章的学习,我学会了类的定义,使用,以及类中的成员,对象,成员函数等有关概念,这一章的概念很多,很繁琐,我觉得我不能完全的记住所有的概念,但是我对这些概念已经有了大体的了解,明白它们是怎么一回事,大致可以运用它们。这一章对我来说最大的收获和意义不是前面那些概念和基础知识,而是通过学习,我学会了怎样去写c++程序,怎样定义类,操作类,怎样大致的去完成一个信息管理系统。这段时间,最主要的任务就是学生成绩管理系统的编写和调试,这是我写的第一个系统,虽然程序可能还不够完善,但我已经能大体完成这个系统主要功能的编写与调试。借着写这个系统的体会,我总结一下写信息管理系统的方法与经验教训:首先要分析一个系统要实现什么样的功能,每个用到什么样的数据,一般将功能与数据分开,写一个数据类,一个操作类,在分析时可能会发现一个数据类中可能会包含其他的数据类,就再写增加数据类,这样大体确定一个思路,再开始写程序。先写数据类,将数据整合,每个数据尽量跟一个set和get函数,便于后面调试调用,通常还得写一个显示(display)函数,用于输出。写完数据类,先将数据类调通,每个函数都试一下,再去写操作类。操作类,一般以数据类的对象作为数据成员,有很多功能,比如说学生成绩信息的增、删、查、改,要注意这些功能之间的联系和顺序,先增加,才能完成其他,在实现功能时可能会根据需要增加函数。注意,每增加一个功能,就在主函数中进行调试,可以减少调试的难度,对于一些没有输出的功能,可以加一个显示函数来进行调试。实现操作时,一般需要调用数据类中的成员函数,所以数据类一定要先调通。这大致就是一个系统的编写过程。总的来说,就是要循序渐进,像滚雪球一样往上加功能,保证写过的每一个功能的正确性。
这一章学习完后,我大体明白了简单的信息管理系统的编写思路与方法,但还不够熟练,存在很多问题,还需要更多的练习与思考。