一、心得感悟
c语言的课程学习后,开始c++的学习,首先就是学习类。在学习类时,类的使用与c语言有着极大的差别,一开始学习十分别扭。c语言的学习直接定义几个形参、函数就可以写程序了;而到了c++学习,关于类,首先必须定义类。具有相同性质和功能的东西构成的集合,通常归成一“类”。例如,“人”是类的概念。为了描述人的特点,有姓名、性别、年龄、身高、体重等特征,称为“属性”。人还有各种生活技能和工作技能,称为“方法”。类是抽象的,当属性赋给具体的值,方法有具体的内容时,才成为对象,如具体的张三、李四。对象时某个类的能动实体。
类的定义包括行为和属性两个部分,属性以数据表示,行为通过函数实现。类的实例称为对象,正如基本数据类型的实例称为变量一样,在程序设计中,“变量”与“对象”两个属于常常可以互换使用。在面向对象程序设计中,程序的基本单位时类。类时用户定义的数据和操作这些数据的函数的封装。类的对象使用自己的方法完成对数据的操作。c++中,属性用数据的储存结构实现,称为类的数据成员;方法用函数实现,称为成员函数。它们都是类的成员。使用对象包括访问对象的数据成员和调用成员函数。类中的成员函数可以使用自身不同性质的数据成员和调用成员函数。
总之,学习c++,类是最重要的,没有之一。不会定义和使用类,在今后的学习可能会寸步难行。
二、内容总结及例题与分析
1.1. 首先定义类的说明语句一般形式为:
class<类名>
{
public:
公有段数据成员和成员函数;
protected:
保护段数据成员和成员函数;
private:
私有段数据成员和成员函数;
};←分号不得省略!
类成员用关键字指定不同的访问特征,决定其在类体系中或类外的可见性。
关键字private用声明私有成员。私有成员只能在类中可见,不能在类外或派生类中使用。
protected声明保护成员。保护成员在类和它的派生类中可见。
pulibc声明共有成员。共有成员是类的接口,在类中和类外可见。
例如:
class Date
{
public:
void SetDate(int y,int m,int d);
int IsLeapYear();
void PrintDate();
private:
int year,month,day;
};
Date类有三个私有数据成员:year,month,和day。
另外,Date还有三个共有成员函数:SetDate函数是用于获取对象的值,设置日期;函数IsLeapYear用于判断是否闰年:PrintDate函数用于输出日期。在类的说明语句中没有给出函数的实现。
成员函数在类外定义使用作用域区分符进行说明,此时函数头的形式为:
返回类型 类型::函数名(参数名)
其中,作用域区分符由两个冒号构成,它用于标识属于什么类的成员。
Date类的成员函数定义在类外写为:
void Date::SetDate(int y,int m,int d)
{
year=y;
month=m;
day=d;
}
int Date::IsLeapYear()
{ return(year%4==0&&year%100!=0)||(year%400==0); }
void Date::PrintDate()
{ cout<<year<<"."<<month<<"."<<day; }
简单的成员函数实现可以在类中定义,此时,编译器作为内联函数处理。例如,成员函数SetDate在类中写成:
public:
void SetDate(int y,int m,int d)
{
year=y;
month=m;
day=d;
}
成员函数有两个作用:一是操作数据成员,包括访问和修改数据成员;二是用于协同不同的对象操作,称为传递消息。成员函数通过参数与其它对象协同操作。
对象是类类型的变量,说明方法与普通变量相同。对象没有成员函数的副本,类成员函数可以被对象调用。
类的数据成员除了可以是基本类型外,还可以是数组、结构、类等自定义的数据类型。如果一个类的成员是一个已经定义的类类型,则称为类的包含(或组合)。例如,定义Student类:
class Student
{ public:
char name[10];
Date birthday;
long code;
char *address;
char phone[20];
//...
};
1.2. 访问对象成员
使用对象包括访问对象的数据成员和调用成员函数。类中的成员函数可以使用自身不同性质的数据成员和调用成员函数。公用成员是提供给外部的接口,即,只有公用成员在类体系外可见。对象成员的访问形式与访问结构的形式相同,运算符“.”和“->”用于访问对象成员。
例:1.访问对象的公用成员。
#include<iostream>
using namespace std;
class Tclass
{ public:
int x,y;
void print()
{ cout<<x<<"."<<y; };
};
int main()
{
Tclass test;
test.x=100;//访问共有段数据成员
test.y=200;
test.print();//调用共用段成员函数
}
2.用指针访问对象成员。
#include<iostream>
using namespace std;
class Tclass
{ public:
int x,y;
void print()
{ cout<<x<<"."<<y; };
};
int add(Tclass *ptf)
{ return(ptf->x+ptf->y); }
int main()
{
Tclass test,*pt=&test;//说明一个对象test和对象指针pt
pt->x=100; //用对象指针访问数据成员
pt->y=200;
pt->print(); //用对象指针调用成员函数
test.x=150;
test.y=450;
test.print();
cout<<"x+y="<<add(&test)<<endl;//把对象地址传给指针参数
}
1.3.this指针
需要显式引用this指针的三种情况
(1)在类的非静态成员函数中返回类对象本身或对象的引用的时候,直接使用return *this,返回本对象的地址是,return this。
(2)当参数与成员变量名相同时,如this->x=x,不能写成x=x。
(3)避免对同一对象进行赋值操作,判断两个对象是否相同时,使用this指针。
函数返回对象的引用
#include<bits/stdc++.h>
using namespace std;
class Person
{
public:
Person(string n,int a)
{
name=n; //这里的name等价于this->name
age=a; //这里的age等价于this->age
}
int getage(void)const{return age;}
Person& addage(int i){age+=i; return *this;}
private:
string name;
int age;
};
int main()
{
Person Li("Li",20);
cout<<"Li age ="<<Li.getage()<<endl;
cout<<"Li add age ="<<Li.addage(1).getage()<<endl;//增加岁的同时,可以对新的年龄直接输出;
return 0;
}
程序执行结果为:
Li age=20
Li add age=21
2.1.构造函数和析构函数
构造函数时用于创建对象的特殊成员函数,当一个对象作用域结束时,系统自动调用析构函数。析构函数的作用是进行对象消亡时的清理工作。没用用户定义析构函数时,系统提供缺省版本的析构函数。析构函数没用参数,也没有返回类型。
构造函数和析构函数的原型是:
类名::类名(参数表);
类名::~类名();
2.2如果类中没有定义构造函数,系统将自动生成一个默认形式的构造函数,用于创建对象,默认构造函数形式:
类名::类名(){}
默认构造函数时一个空函数
例:为类Date建立一个构造函数
#include<bits/stdc++.h>
using namespace std;
class Date
{
public:
Date(); //无参构造函数
Date(int y,int m,int d);
void showDate();
private:
int year,month,day;
};
Date::Date() //构造函数的实现
{year=y;month=m;day=d;}
inline void Date::showDate()
{cout<<year<<"."<<month<<"."<<endl;}
通常,利用构造函数创建对象有以下两种方法:
(1)利用构造函数直接创建对象,其一般形式为:
类名 对姓名{(实参表)};
这里的“类名”与构造函数名相同,“实参表”是为构造函数提供的实际参数。
(2)利用构造函数创建对象时,通过指针和new来实现。其一般语法形式为:类名 *指针变量=new类名[(实参表)];
2.3.构造函数的初始化列表——数据成员的初始化
构造函数初始化成员有两种方法
A.使用构造函数的函数体进行初始化
class Date
{
int d,m,y;
public:
Date(int dd,int mm,int yy)
{
d=dd;
m=mm;
y=yy;
}
Date(int dd,int mm)
{
d=dd;
m=mm;
}
};
B.使用构造函数的初始化列表进行初始化
格式:
funname(参数列表):初始化列表
{函数体,可以是空函数体}
初始化列表的形式:
成员1(形参名1),成员名2(形参名2),成员名n(形参名n)
class Date
{
int d,m,y;
public:
Date(int dd,int mm,int yy):d(dd),m(mm),y(yy)
{}
Date(int d,int mm):d(dd),m(mm)
{ }
};
**必须使用参数初始化列表对数据成员进行初始化的几种情况
1.数据成员为常量
2.数据成员为引用类型
3.数据成员为没有无参构造函数的类的对象
情况1和2:
#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;
}
情况3:
#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 objB(3,5);
objB.out();
}
类成员的初始化的顺序:
按照数据成员在类中的声明顺序进行初始化,与初始化成员列表中出现的顺序无关
2.3.构造函数的重载
class Box
{
public:
Box();
Box(int,int,int);
int volume();
private:
int height,width,length;
};
Box::Box()
{height=10;width=10;length=10;}
Box::Box(int h,int w,int l):height(h),width(w),length(l)
{}
int Box::volume()
{return width*length*height;}
int main()
{
Box box1;
cout<<"The volume is"<<box1.volume();
Box box2(13,30,25);
cout<<"The volume is"<<box2.volume();
return 0;
}
2.4.复制构造函数
4.1.复制构造函数用一个已有同类对象创建新对象进行数据初始化C++为类型提供默认版本的复制构造函数。C++可以完成类对象数据的简单复制。用户自定义的复制构造函数用于完成更为复杂的操作。
复制构造函数要求有一个类类型的引用函数:
类名::类名(const 类名&引用名,……);
为了保证所引用对象不被修改,通常把引用参数说明为const参数。例如:
class A
{
public:
A(int);
A(const A&,int =1);//复制构造函数
};
...//
A a(1);//创建对象a,调用A(int)
A b(a,0);//创建对象b,调用A(const&,int=1)
A c=b;//创建对象c,调用A(const A&,int=1)
特点:
1.复制构造函数名与类名相同,并且也没有返回值类型。
2.复制构造函数可写在类中,也可以写在类外。
3.复制构造函数要求有一个类类型的引用函数。
4.如果没有显示定义复制构造函数,系统自动生成一个默认形式的复制构造函数。
一下三种情况下有编译系统自动调用:
1.声明语句中用类的一个已知对象初始化该类的另一个对象时。
2.当对象作为一个函数实参传递给函数的形参时,需要将实参对象去初始化形参对象时,需要调用复制构造函数。
3.当对象时函数的返回值时,由于需要生成一个临时对象作为函数返回结果,系统需要将临时对象的值初始化另一个对象,需要调用复制构造函数。
4.2.浅复制和深复制
浅复制:
在用一个对象初始化另一个对象时,只复制了数据成员,而没有复制资源,是两个对象同时指向了同一资源,使两个对象同时指向了同一资源的复制方式成为浅复制。即:对于复杂类型的数据成员只复制了存储地址而没有复制存储内容。默认复制构造函数所进行的是简单数据复制,即浅复制。
深复制:
通过一个对象初始化另一个对象时,不仅复制了数据成员,也复制了资源的复制方式成为深复制。自定义复制构造函数所进行的复制是深复制。
定义支持深复制的复制构造函数
1.深复制构造函数必须显式定义
2.深复制构造函数的特点:
定义:类名::类名([const]类名&对象名);
成员变量的处理:对复制类型的成员变量,使用new操作符进行空间的申请,然后进行相关的复制操作
2.5.析构函数
对象生存期结束时,需要做清理工作,比如:释放成员(指针)所占有的存储空间析构函数可以完成上述工作。
析构函数自动调用(隐式调用)
析构函数没有返回值,不能有参数,也不能重载,因此在一个类中只能有一个析构函数,当撤销对象时,编译系统会自动调用析构函数。
格式:
类名::~类名()
{
函数语句
}
默认析构函数:若没有显式定义析构函数,则系统自动生成一个默认形式的析构函数。系统自动生成的默认析构函数形式如下:类名::~类名(){}
一般情况下,可以不定义析构函数,但如果类的数据成员中包含指针变量时从堆上进行存储空间分配的话,需要在析构函数中进行存储空间的回收
class Student
{
public:
Student(int n,string a_name,char s)
{
num=n;
name=a_name;
sex=s;cout<<"Constructor called."<<endl;
}
~Student()
{cout<<"Destructor called."<<endl;}
void display(){cout<<name<<endl;cout<<num<<endl;cout<<sex<<endl;}
private:
int num;string name;char sex;
};
3.类的其它成员
3.1常成员
常数据成员是指数据成员在实例化被初始化后约束为只读;常成员函数是指成员函数的this指针被约束为指向常量的常指针,在函数体内不能修改数据成员的值。在类的成员函数说明后面可以加const关键字,则该成员函数成为常量成员函数。
1.常数据成员
使用const说明的数据成员成为常数据成员。如果在一个类中说明了常数据成员,那么构造函数就只能通过初始化列表对该数据成员进行初始化,而任何其它函数都不能对该成员赋值。
(1)常数据成员可以在构造函数中直接用常量进行初始化,这样,每个对象建立的常数据成员都有相同的值。
#include<iostream>
using namespace std;class Mclass
{
public:
int k;
const int M; //说明常数据成员
Mclass():M(5){} //用初始化对常数据成员赋值
void testFun()
{
//M++; //错误,不能在成员函数中修改常数据成员
k++;
}
};
int main()
{
Mclass t1,t2;
t1.k=100;
//ti.M=123; //错误,不能在类外修改常数据成员
cout<<"t1.k="<<t1.k<<'\t'<<"t1.M"<<endl;
t2.k=200;t2.testFun();
cout<<"&t1.M="<<&t1.M<<endl;
cout<<"t2.k="<<t2.k<<'\t'<<"t2.M"<<t2.M<<endl;
cout<<"&t2.M"<<&t2.M<<endl;
}
程序运行结果:
t1.k=100 ti.M=5
&t1.M=0012FF4C
t2.k=201 t2.M=5
&t2.M=0012FF3C
(2)另外一种对常数据成员进行初始化的方法是,使用到参数的构造函数,创建对象是,用实际参数对常数据成员赋值。这样,每个常数据成员就可以有不同的初始值。
#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,char *pname="no name");
void PrintStudent()const; //常成员函数
private:
const int code; //常数据成员
char name[20];
Date birthday; //结构数据成员
};
void Student::PrintStudent()const
{
cout<<"序号:"<<code<<"\t姓名:"<<name<<"\t"<<"出生日期:"<<birthday.year<<"-"<<birthday.month<<"-"<<birthday.day;
cout<<endl;
}
//带参数结构函数完成数据成员初始化
Student::Student(int y,int m,int d,int num,char *pname):code(num)
{
strcpy_s(name,pname);
name[sizeof(name)-1]='\0';
birthday.year=y;
birthday.month=m;
birthday.day=d;
}
int main()
{
Student stu1(1997,5,30,20171769,"王克复");
stu1.PrintStudent();
Student stu2(1998,10,4,20171265,"生物能");
stu2.PrintStudent();
}
程序运行结果:
序号:20171769 姓名:王克复 出生日期:1997-5-30
序号:20171265 姓名:生物能 出生日期:1998-10-4
在上述程序中,student类有3个数据成员,其中,birthday是结构Date类型成员,code是常数据成员。在带参数的构造函数中,特别地用参数初始化code(num)对this->code赋初值。这个值在建立对象后被约束为只读。
2.常对象
若在定义对象的说明语句以const作前缀,则该对象成为常对象。这个对象的全部数据成员在作用域中约束为只读。
说明形式如下:类名const对象名[(参数表)];或者const 类名 对象名[(参数表)]
在定义常对象时必须进行初始化,而且不能被更新。
说明:(1)C++不允许直接或间接更改常对象的数据成员。
(2)C++规定常对象只能调用它的常成员函数、静态成员函数、构造函数(具有共有访问权限)。
#include<iostream>
#include<cstring>
using namespace std;
class T_class
{
public:
int a,b;
T_class(int i,int j)
{ a=i;b=j;}
};
int main()
{
const T_class t1(1,2); //t1是常对象
T_class t2(3,4);
//t1.a=5; //错误,不能修改常对象的数据成员
//t1.b=6; //错误,不能修改常对象的数据成员
t2.a=7;
t2.b=8;
cout<<"t1.a="<<t1.a<<'\t'<<"t1.b="<<t1.b<<endl;
cout<<"t2.a="<<t2.a<<'\t'<<"t2.b="<<t2.b<<endl;
}
程序运行结构:
t1.a=1 t1.b=2
t2.a=7 t2.b=8
3.常成员函数
常成员函数的this指针被约束为指向常量的常指针。由于this指针隐含定义,因此常成员函数在函数头以关键字const作为后缀。例如:
class X
{
int a;
int f() //常用函数
{ return a++;} //合法
int g()const //常成员函数
{return a++;} //错误,不能修改数据成员
};
常成员函数格式如下:
类型说明符 函数名(参数表) const;
3.2静态成员
类成员冠以static声明时,成为静态成员。静态数据成员为同类对象共享。静态成员函数与静态数据成员协同操作。静态成员不属于某一个单独的对象,而是为类的所以对象所共有。静态成员函数的作用不是为了对象之间的沟通,而是为了能处理静态数据成员:保证在不依赖与某一个对象的情况下,访问静态数据成员
静态数据成员在定义或说明是前面加关键字static,如:
class A
{
int n;
static int s;
};
调用静态成员函数:
viod g()
{
X obj;
X::StaFun();
obj.StaFun();
}
说明:
(1)静态成员函数在类外定义是不用static前缀
(2)静态成员函数主要用来访问同一类中的静态成员
(3)私有静态成员函数不能在类外部或用对象访问
(4)可以在建立对象之前处理静态数据成员
(5)编译系统将静态成员函数限定为内部连接(在其他文件中不可知)
(6)静态成员函数中是没有this指针的
(7)静态成员函数不访问同类中的非静态数据成员。如有需要,只能通过对象名(或指向对象的指针)访问该对象的非静态成员
3.3友元
如果在本类以外的其它地方定义了一个函数
这个函数可以是不属于任何类的非成员函数
也可以是其它类的成员函数
在类体中用friend对其进行声明,此函数就称为本类的友元函数
友元函数可以访问这个类的私有成员
class A
{private:
int i;
friend void FriendFun(A *,int)
public:
void MemberFun(int);
};
...
void FriendFun(A * ptr,int x)
void A::MemberFun(int x)
{i=x;};
4.类的包含
类的包含是程序设计总一种软件重要技术。即定义一个新的类是,通过编译器把另一个类“抄“进来。
当一个类中含有已经定义的类类型成员,到参数的构造函数对数据成员初始化,必须使用初始化语法形式。
构造函数(形参表):对象成员1(形参表),...,对象粗n(形参表);
4.1对象成员初始化
出现粗对象是,该类的构造函数要包含对象成员的初始化。如果构造函数的初始化列表没有对成员初始化是,则使用成员对象的无参(缺省)构造函数。
建立一个类的对象时,要先执行成员对象自己的构造函数,再执行当前类的构造函数。
成员对象的构造函数调用次序和成员对象在类中的说明次序一致,与它们在成员初始化列表中出现的次序无关。
析构函数的调用顺序相反。
简单对象成员初始化:
#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 objB(3,5);
objB.out();
}
程序运行结果:
aa=3
b=5
4.2.对象数组
所谓对象数组是指每一数组元素都是对象的数组。
定义一个以为对象数组的格式如下:
类名 数组名 [下表表达式];
成员对象数组的初始化:
#include<iostream>
using namespace std;
class A
{
public:
A(int i=0):x(i){}
int x;
};
class B
{
public:
B():a[0](0),a[1](1){} //error C2059:语法错误:"["
A a[2];
};
int main()
{
B b;
cout<<b.a[0].x<<endl;
cout<<b.a[1].x<<endl;
return 0;
}