1 类与对象
1.1 类
1.1.1 类的定义
-
类是具有相同属性和方法的一类对象集合的抽象,它包含数据抽象(即数据成员)和行为抽象(即成员函数)。
-
定义类的过程就是对问题进行抽象和封装的过程。
一般形式为:
class 类名{
public:
<共有数据和函数>
protected:
<保护数据和函数>
private:
<私有数据和函数>
}
1.1.2 类成员的访问控制
-
成员缺省定义为private的。私有成员是被隐藏的数据,只有该类的成员函数或友元函数才可以访问它。
-
保护成员也不能通过对象访问,但是可以被该类或派生类的成员函数访问。
-
共有成员定义了类的外部接口。
1.1.3 成员函数的实现
- 成员函数的实现,可以放在类体内;也可以放在类体外,但必须在类体内给出原型说明。
- 放在类体内定义的函数被默认为内联函数,而放在类体外定义的函数是一般函数,如果要定义为内联函数则需在前面加上关键字inline。
在类体外定义成员函数的一般形式为:
<返回类型> <类名> :: <成员函数名>(<参数说明>)
{
类体
}
1.1.4类的界面和实现
为了减少代码的重复,加快编译速度,在大型程序设计中,C++的类结。构常常被分为两部分:
一部分是类的界面,另一部分是类的实现
1.2 对象
1.2.1 对象的声明
对象是类的实例或实体。
一般格式为:
<类名><对象名>;
例如,声明类Point的对象:
Point p1,p2;
Point p[3];//定义了三个Point类型的对象数组
Point *p3;//定义了一个Point类型的指针
Point &rp=p1;//给p1取了一个别名rp
1.2.2 对象成员的访问
- 圆点访问方式
对象名.成员名 或 (*指向对象的指针).成员名
- 指针访问方式
对象指针变量->成员名 或 (&对象名)->成员名
1.3构造函数与析构函数
1.3.1 构造函数
构造函数是一种特殊的成员函数,对象的创建和初始化工作可以由它完成,其格式如下:
<类名>::<类名>(<形参表>)
{
<函数体>
}
-
构造函数的特点:
- 被声明为公共
- 函数名与类名相同
- 可以重载
- 不能指定返回类型
- 不能被显式调用
-
默认构造函数
默认构造函数就是无参数的构造函数。既可以是自己定义的,也可以是编译系统自动生成的。
当没有为一个类定义任何构造函数的情况下,编译系统就会自动生成无参数、空函数体的默认构造函数。
其格式如下:
<类名>::<类名>() { }
1.3.2 成员初始化表
带有成员初始化表的构造函数的一般形式如下:
类名::构造函数名([参数表])[:(成员初始化表)]
{
//构造函数体
}
成员初始化表的一般形式为:
数据成员名1(初始值),数据成员名2(初始值2),...
eg.
class Simple
{
int x;
int ℞//引用
const float pi;//const
public:
Sample::Sample(int x1):x(x1),rx(x),pi(3,14f){
//x=x1'
}
1.3.3 具体默认参数的构造函数
如果构造函数的参数值通常是不变的,只有在特殊形况下才需要改变它的参数值,这时可以将其定义为带默认参数的构造函数
1.3.4 析构函数
-
析构函数有如下特点:
- 只能被声明为共有函数。
- 析构函数的名字同类名,与构造函数名区别在于析构函数名前加~,表明它的功能与构造函数的功能相反。
- 析构函数没有参数,不能重载,一个类中只能定义一个析构函数。
- 不能指定返回类型。
- 析构函数在释放一个对象的时候被自动调用。
-
默认析构函数
如果一个类中没有定义析构函数,系统将自动生成一个默认析构函数,其格式如下:
<类名>::~<类名>() { }
1.3.5 拷贝构造函数
-
拷贝构造函数的定义
拷贝构造函数是一种特殊的构造函数,它的作用是用一个已经存在的对象去初始化另一个对象。
其格式为:
<类名>::<类名>(const <类名>&<类名>) { 函数体 }
2.拷贝构造函数的特点
- 拷贝构造函数名字与类相同,不能指定返回类型
- 拷贝构造函数只有一个参数,该参数是该类的对象的一个引用。
- 它不能被显示调用。
3.在以下情况会被自动调用
- 当用类的一个对象去初始化该类的另一个对象时
- 当函数的形参是类的对象,进行形参和实参的结合时
- 当函数的返回值时类的对象,函数执行完成返回调用者时
调用拷贝构造函数的三种情况
(1)当用类的一个对象去初始化该类的另一个对象时。
如:
Point p2(p1);//用对象p1初始化对象p2,拷贝构造函数被调用(代入法)
Point p3=p1;//用对象p1初始化对象p3,拷贝构造函数被调用(赋值法)
(2)当函数的形参时类的对象,调用函数,进行形参和实参结合时。
如:
Fun1(Point p)//函数的形参是类的对象
{
p.print();
}
int main(){
Point p1(10,20);
Fun1(p1);//当调用函数,进行形参和实参结合时
return 0;
}
(3)当函数的返回值时类的对象,函数执行完成,返回调用者时.
如:
Point Fun2(){
Point p1(10,30);
return p1;//函数返回值是对象
}
main(){
Point p2;
p2=Fun2();//函数执行完成,返回调用者时
return 0;
}
1.3.6 浅拷贝和深拷贝
所谓浅拷贝,就是用默认的拷贝构造函数实现数据成员逐一赋值。
需要自己写拷贝构造函数,实现额外的内容,这种称为深拷贝。
如:(如果不进行深拷贝,一个空间会被多次析构,发生错误)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstring>
using namespace std;
class String
{
char* sbuf;//用来存放字符串的指针
int length;//表示字符串长度
public:
String()
{
length = 0;//串长为0
sbuf = new char;
sbuf[0] = '\0';
}
String(const char* s)//用字符初始化
{
length = strlen(s);//计算字符串长度
sbuf = new char[length + 1];//开辟空间,对空间放'\0'字符串结束符
strcpy(sbuf, s);
}
String(const string& s)
{
length = s.length;
//sbuf=s.sbuf
sbuf = new char[length+1];
strcpy(sbuf, s);
cout << "我重写的拷贝构造函数" << endl;
}
~String() {//析构函数,回收空间
delete[] sbuf;
}
void Show()//输出字符串
{
cout << sbuf << endl;
}
};
int main() {
String s1("Hello");//定义一个对象,调用构造函数
String s2 = s1;
s1.Show();
return 0;
}
1.4 this指针
- this指针是由c++编译自动产生且较常用的一个隐含对象指针,它不能被显式声明。
- this指针是一个局部变量,局部于某个对象
- this指针是一个const指针,不能修改它或给它赋值
this的用途
-
为了区分成员和非成员
CDate(int year = 2009, int month = 1, int day = 1) { this->year = year; (*this).month = month; this->day = day; }
-
类的方法需要返回当前对象的引用
1.5 向函数传递对象
- 使用对象作为函数参数
- 使用对象指针作为函数参数
- 使用对象引用作为函数参数(可加
const
防止被修改)
class Point
{
int x;
int y;
public:
Point(int a, int b):x(a),y(b){}
void set(int a, int b)
{
x = a;
y = b;
}
void Show() {
cout << x << " " << y << endl;
}
};
void Fun1(Point point)//Point point = p
{
point.set(10, 10);
cout << "point1 is x and y are:" << endl;
point.Show();
}
void Fun2(Point *point)//Point *point = &p
{
point->set(10, 10);//p.set(10,10);
cout << "point2 is x and y are:" << endl;
point->Show();
}
void Fun3(Point &point)//Point &point = p 引用=别名
{
point.set(10, 10);//p.set(10,10);
cout << "point3 is x and y are:" << endl;
point.Show();
}
int main() {
Point p(1, 2);
cout << "The original p is" << endl;
p.Show();//1 2
Fun1(p);//使用对象作为函数参数 10 10
cout << "But,p is unchanged in main:" << endl;
p.Show();//1 2
Fun2(&p);//使用对象指针作为函数参数 10 1
cout << "p is changed in main:" << endl;
p.Show();//10 10
p.set(1, 2);
cout << "p is already intialized" << endl;
p.Show();//1 2
Fun3(p);//使用对象引用作为函数参数 10 10
cout << "p is changed in main:" << endl;
p.Show();//10 10
return 0;
}
1.6 类的静态成员
静态成员是指声明为static的成员,在类的范围内所有对象共享该数据。
静态成员可说明为共有、私有的或保护的。
若为共有的可直接访问,引用静态成员的格式为:
<类名>::<静态成员>
对象名.共有静态成员
对象指针->静态成员
静态成员可分为:
- 静态数据成员
- 静态成员函数
1.6.1 静态数据成员
静态数据成员不属于任何对象,它在程序编译时创建并且初始化,所有在该类的任何对象被创建前就存在。
静态数据成员初始化的格式为:
<数据类型><类名>::<静态数据成员名> = <初始值>;
1.6.2 静态成员函数
静态成员函数的定义是在一般函数定义前加上static关键字。
(1)一般情况下,静态成员函数主要用来访问全局变量或同一个类中的静态数据成员。可以用在建立任何对象之前处理静态数据成员。
(2)静态成员函数不访问类中的非静态成员。
(3)静态成员函数中是没有this指针的。
(4)静态成员函数可以在类体内定义,也可以在类外定义,在类外定义时,不用static前缀。
class Student
{
public:
static int count;//班级人数 静态数据成员
static double total;//班级的总分 静态数据成员
Student(double score) {
this->score = score;
count++;
total += score;
}
static double getAverage() {
//cout<<score<<endl;//error 静态成员函数不访问类中的非静态成员
return total / count;
}
static double getTotal() {
return total;
}
private:
double score;//个人的分数
};
int Student::count = 0;//static成员初始化、
double Student::total = 0;
int main() {
/*cout << Student::count << endl;//0
cout << Student::total << endl;//0
Student s1(70);
cout << Student::count << endl;//1
cout << Student::total << endl;//70
cout << s1.count << endl;//1
*/
Student s[3] = { Student(70),Student(80),Student(90) };
//cout << s1.getAverage() << endl;//80
cout << Student::getAverage() << endl;//80
cout << Student::getTotal() << endl;//240
//cout << s[0].count << endl;//3
cout << Student::count << endl;//3
return 0;
}
1.7 类的友元
(1)使用友元函数的目的是提高程序的运行效率
(2)慎用友元函数,因为它可以在类外直接访问类的私有或保护成员,破坏了类信息隐蔽的特性
1.7.1 友元函数
-
友元函数是在类中说明的由关键字friend修饰的非成员函数。
-
友元函数不是当前类的成员函数,而是独立于当前类的外部函数,但它可以访问该类的所有对象的成员。
-
在类中声明友元函数,其原型为:
friend <类型><友元函数>(<参数表>);
此声明可以放在共有部分,也可以放在保护部分和私有部分。 -
友元函数可以定义在类内部,也可以定义在类外部。
class Point { public: Point(int a = 0, int b = 0)//带缺省参数的构造函数 { x = a; y = b; } void Show()//用于输出 { cout << "x=" << x << " y=" << endl; } int getX () const //对于一个常量对象,只能调用常函数 { return x; } const int getY() const { return y; } friend double Distance(const Point& p1, const Point& p2);//普通函数作为友元函数 private: int x; int y; }; /* double Distance(const Point& p1, const Point& p2) { double d; //d = sqrt((p2.x - p1.x)(p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y)); //error 不能访问私有成员 d = sqrt((p2.getX() - p1.getX())*(p2.getX() - p1.getX()) + (p2.getY() - p1.getY()) * (p2.getY() - p1.getY())); return d; } */ double Distance(const Point& p1, const Point& p2) { double d; d = sqrt((p2.x - p1.x)*(p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y)); return d; } int main() { Point p1, p2(1, 1);//定义了Point的两个对象p1和p2 //调用Distance计算p1和p2间的距离 cout << Distance(p1, p2);//1.41421 return 0; }
1.7.2 友元成员
- 一个类的成员函数也可以作为另一个类的友元。
- 这种成员函数不仅可以访问自己所在类对象中的所有成员,还可以访问friend声明语句所在类对象中的所有成员
- 这样能使两个类相互合作、协调工作,完成某一任务
1.7.3友元类
-
友元还可以是类,即一个类可以作为另一个类的友元。
-
当一个类作为另一个类的友元时,则该类中所有成员函数都是另一个类的友元成员,都可以访问另一个类的所有成员。
-
友元类的声明格式:
friend 类名;
此语句可以放在共有部分,也可以放在私有部分或保护部分。
class Girl;//声明
class Boy {
string name;
public:
Boy(string nm) {
name = nm;
}
void IntroOneself() {
cout << "I am " << name << endl;
}
void IntroFriend1(Girl &x);//必须事先声明Girl
void IntroFriend2(Girl& x);
};
class Girl {
string name;
friend Boy;//声明类Girl为类Boy的友元类
public:
Girl(string nm) {
name = nm;
}
void IntroOneself() {
cout << "I am " << name << endl;
}
//friend void Boy::IntroFriend1(Girl &x);
//声明Boy的成员函数IntroFriend()是Girl的友元函数
};
void Boy::IntroFriend1(Girl &x) {
cout << "She is " << x.name << endl;
//介绍自己的女朋友
}
void Boy::IntroFriend2(Girl& x) {
cout << x.name<<" is my best friend." << endl;
//介绍自己的女朋友
}
int main() {
Boy b("Tom");
Girl g("Mary");
b.IntroOneself();// I am Tom
b.IntroFriend1(g);//She is Mary
b.IntroFriend2(g);//She is Mary
return 0;
}
说明
- 友元关系是单向的,不具有交换性,即类A中将类B声明为自己的友元类,但类B中没有将类A声明为友元类,所有类A的成员函数不可以访问类B的私有成员。
- 当两个类都将对方声明为自己的友元时,才可以实现互访。
- 友元关系也不具备传递性,即类A将类B声明为友元,类B将类C声明为友元,此时,类C不一定是类A的友元。
1.3.8 对象成员
如果一个类的对象是另一个类的数据成员,则称这样的数据成员为对象成员。例如:
class A{
//...
};
class B{
A a;//对象成员
...
};
对象成员初始化问题
class X{
类名1 对象成员名1;
类名2 对象成员名2;
类名n 对象成员名3;
};
一般来说,类X的构造函数的定义形式为:
X:X(形参表0):对象成员1(参数表1),对象成员名2(参数表2),..对象成员名n(参数表n)
{//构造函数体}
构造函数的调用顺序为:
对象成员所属类的构造函数、本类的构造函数如果对象成员不止一个,则按各对象成员在类声明中的顺序依次调用它们的构造函数,使这些对象初始化。
析构函数的调用顺为:
始终与构造函数的调用顺序正好相反,即先调用本类的析构函数,再调用对象成员所在类的析构函数。
class Date//Date类的定义
{
public:
Date(int y, int m, int d)//构造函数
{
cout << "Constructing Date" << endl;
year = y;
month = m;
day = d;
}
void Show()//输出函数
{
cout << year << "." << month << "." << day << endl;
}
~Date() {
cout << "Destructing Date" << endl;
}
private:
int year, month, day;
};
class Time//Time的定义
{
public:
Time(int h, int m, int s)//构造函数
{
cout << "Constructing Time" << endl;
hour = h;
minute = m;
second = s;
}
void Show() {
cout << hour << ":" << minute << ":" << second << endl;
}
~Time() {
cout << "Destructing Time" << endl;
}
private:
int hour, minute, second;
};
class Schedule//Schedule的定义
{
public:
Schedule(int num,int y, int m, int d, int a, int b, int c,string w):date(y,m,d),time(a,b,c)
{
number = num;
work = w;
cout << "Constructing Schedule" << endl;
}
void Show() {
cout << number << endl;
date.Show();
time.Show();
cout << work << endl;
}
~Schedule() {
cout << "Destructing Schedule" << endl;
}
private:
int number;
Date date;//定义对象成员 date
Time time;//定义对象成员 time
string work;
};
int main() {
Schedule s(1, 2016, 7, 27, 15, 11, 11, "开会");
s.Show();
/*Constructing Date
Constructing Time
Constructing Schedule
1
2016.7.27
15:11 : 11
开会
Destructing Schedule
Destructing Time
Destructing Date
*/
return 0;
}
1.9 常对象
对于既需要共享,又需要防止改变的数据,应该声明为常量进行保护,因为常量在程序运行期间是不可以改变的。这些常量需要用关键字const来定义
- 常数据成员————const修饰数据成员
- 常数据函数————const修饰成员函数
- 常对象————const修饰类的对象
1.9.1常数据成员——类的数据成员用const说明
(1)如果类中说明了常数据成员,则构造函数只能通过初始化列表对该数据成员进行初始化。
(2)其它函数都不能对常数据成员进行修改,只能访问
1.9.2 常成员函数——成员函数用const说明
常成员函数的声明格式为:
类型 函数名(参数表) const;
(1)在常成员函数的原型声明及函数定义的首部都要使用关键字const。
(2)常成员函数不能修改本类的数据成员,也不能调用普通的成员函数,从而保证了在常成员函数中不会 修改数据成员的值。
(3)关键字const可以作为函数重载的标志
(4)访问属性为public的常成员函数可以通过该类的任何对象调用。
1.9.3 常对象——对象用const修饰
常对象函数的声明格式
const 类名 对象名; 或 类名 const 对象名
(1)常对象必须初始化,而且不能被更新。
(2)由于常对象的值(包括所有的数据成员的值)不能被改变,因此,通过常对象只能调用常对象函数,而不能调用类中的其他普通成员函数。
eg.要求姓名一旦初始化就不能再修改。
class Student {
public:
Student(string n, string i):name(n)//name是常数据成员,只能采用成员列表初始化
{
// name = n; error
id = i;
}
void showStudent() {//输出函数
cout << "name:\t" << name << "\tid:\t" <<id<< endl;
cout << "no const showStudent" << endl;
}
void showStudent() const{//常成员函数
//modifyStudent(); error
//id="f11118; error s2是常对象,一旦初始化好name,id,便不可再更改
//name="Nacy" error name本身也是常数据成员 且 常对象的数据不可更改
cout << "name:\t" << name << "\tid:\t" << id << endl;
cout << "const showStudent" << endl;
}
void modifyStudent() {
id = "f11112";//可以
}
string getName() {
return name;
}
private:
const string name;//常数据成员
string id;
};
int main() {
Student s1("simon", "f11111");//s1是普通对象
s1.showStudent();//name: simon id: f11111
s1.modifyStudent();
s1.showStudent();
//name: simon id: f11112
//no const showStudent
const Student s2("Mary","f11115");
s2.showStudent(); //通过常对象只能调用常对象函数
//name: Mary id: f11115
//"const showStudent
return 0;
}