(2020.11.27 Fri)
类Class,是具有相似内部状态和行为的实体的集合。类=数据+操作。
从结构体到类
结构体struct一般是各种变量的集合,其中也可以加入成员函数。
#include
struct point
{
int x;
int y;
void point()
{
cout <<"x="<
从结构体的概念引入类的概念,关键字struct变成class,其他都不变,会有如下代码,但运行会返回错误
#include
class point
{
int x;
int y;
void point()
{
cout<<"x="<
错误信息提示,不能访问勒种私有(private)的成员变量和成员函数。默认情况下,struct成员是公有的(public),class成员是私有的,这也是结构体和类的最大区别。公有成员可以在类的外部进行访问,而私有成员只能在类的内部进行访问。上述代码只需加入public关键字就可以消除error
#include
class point
{
public: //说明符
int x;
int y;
void point()
{
cout<<"x="<
类声明
class
{
private:
私有数据和函数;
public:
公有数据和函数;
};
一个例子
class cbook
{
private:
char *mpname;
int m_pages;
int m_edition;
public:
void get_book_name(char * pname);
int GetTotalPages();
int GetBookEdition();
private:
void SetBookName(char * pname);
void SetTotalPages(int npages);
voi SetBookEdition(int nedition);
public:
cbook();
};
void main()
{
cbook op; //类的实例化
cout<<'Class define success.'<
类的访问控制
前面提到了public和private两类成员,实际为三类
- 公有成员,以public声明
- 私有成员,以private声明
- 保护乘员,以protected声明
这些关键字被称为访问说明符(access specified),用来控制相应成员在程序中的可访问性,使得信息封装和模块化的风格更好。
- 对公有成员而言,在程序的任何位置都能够以正确的方式引用它
- 私有成员只能被其自身成员所访问,即私有成员的名字只能出现在所属类类体、成员函数中,不能出现在其他函数中
- 保护成员只能在该类的派生类类体中使用
- 类的友元可以访问类的任何成员
class point
{
int x; //x和y没有access specifier,则默认是private,只能被成员访问
int y;
public:
void print()
{
cout<<"x="<
类的定义
类的定义也就是类成员函数的定义,也称类体定义。格式如下
<返回类型> <类名>::<成员函数名> (<形参列表>)
{
函数体;
} //函数体结尾没有分号
如果该成员在类内部定义,则类名不写,但在类内部要声明。
#include
#include
class cbook
{
private:
char *mpname;
int m_pages;
int m_edition;
public:
void get_book_name(char * pname);
int GetTotalPages();
int GetBookEdition();
private:
void SetBookName(char * pname);
void SetTotalPages(int npages);
voi SetBookEdition(int nedition);
public:
cbook();
};
void cbook::GetBookName(char * pname)
{
strcpy(pname, mpname);
}
void cbook::GetBookEdition(char *pname)
{
return m_edition; //调用成员变量,不需要加self,直接调用
}
void main()
{
cbook op; //类的实例化
bn = op.GetBookEdition();
}
类的声明和定义如何保存
如果类说明的行数较多,则应该放在一个独立文件中,以.h为后缀,比如上面案例中的cbook类说明可放在cbook.h或book.h中。类体的定义放在一个以.cpp为后缀的文件中,称为类的实现文件。在这个文件的开始部分应该用文件包含指令将类说明文件包含进来。
(2020.11.28 Sat)
对象
对象和类的关系是具体和抽象的关系,如果苹果是对象,那么水果就是类。
构造函数constructor
类定义过程中,注意不能给类成员变量赋初值。在C++中,使用构造函数来解决这一问题。构造函数有如下特征
- 构造函数名称和类的名称相同
- 构造函数的主要功能是为对象分配空间,也可用来为类成员变量赋初值,因此没有返回类型,甚至不能用return语句。相当于停车就必须找个车位来放车,这个寻找车位的操作就是构造函数所要进行的操作
- 定义对象时,编译系统会自动的调用构造函数完成对象内存空间的分配和初始化工作
- 构造函数是类的成员函数,具有一般成员函数的所有性质,可访问类的所有成员,可以是内联函数,可以带有参数表,可以带有默认的形参值,可以重载;可以在类的内部或外部定义。
# 在类的内部定义构造函数
class complex
{
private:
double real, imag;
public:
complex(double r, double i)
{
real = r;
imag = i;
}
void disp()
{
cout<
# 在类的外部定义构造函数
class complex
{
private:
double real, imag;
public:
complex(double r, double i);
void disp()
{
cout<
实际使用中,如果没有给类定义构造函数,编译系统会自动生成一个默认的构造函数,这个默认的构造函数不带任何参数,只能给对象开辟一个存储空间,而不能为对象中的数据成员赋初值。此时数据成员的值是随机的,运行时会出错。系统自动生成的构造函数形式为
类名::构造函数名()
{
}
//比如
complex::complex()
{
}
构造函数的调用
定义对象的同时调用构造函数,调用格式为
类名 对象名(实参表)
构造函数一般不需要用户显示调用,声明对象时系统会自动调用构造函数。
class complex
{
private:
double real, imag;
public:
complex(double r, double i);
void disp()
{
cout<
在这个例子中,没有直接调用构造函数complex(),只是声明了对象op,系统自动调用了构造函数,并完成了初始化工作。
不带参数的构造函数
#include
class myclass
{
private:
int a;
public:
myclass();
void disp()
{
cout<<"a*a="<
返回结果: initialised, a*a = 100。
带参数的构造函数
构造函数在声明时有默认参数 ,但是定义是不写这些参数。
#include
#include
class complex
{
double real, imag;
public:
complex(double r=0.0, double i=0.0); //声明时需要标明默认值
double abscomplex();
};
complex::complex(double r, double i); //定义时不需要标默认值,但重载时可以定义时指定默认值
{
real = r;
imag = i;
}
complex::abscomplex()
{
double n;
n = real*real + imag*imag;
return sqrt(n);
}
void main()
{
complex op(); //调用默认参数
complex op2(1.5, 3.0); //调用赋值参数
cout<<"op: "<
注意,构造函数的默认值一般在声明时给出,但在重载时可以在定义时给出。
构造函数重载
一个类中可以有多个不同参数形式的构造函数,用类去定义一个变量(后面可以附带参数),也就是内存中产生一个类的实例(用类定义的实例变量通常也叫对象)时,程序将根据参数自动调用该类中对应的构造函数。
作用
C++可以定义多个参数及参数类型不同的构造函数,用以适应不同的情况,增加程序灵活性,这些构造函数之间通过参数的个数或类型来区分。
#include
class point
{
double fx,fy;
public:
point();
point(double x, double y);
void showpoint();
};
point::point()
{
fx = 0;
fy = 0;
}
point::point(double x, double y = 5.5)
{
fx = x;
fy = y;
}
void point::showpoint()
{
cout<
拷贝构造函数
拷贝构造函数的作用是用一个已经存在的对象来初始化该类的新对象,用户可以根据需要定义拷贝构造函数,也可由系统生成一个默认的拷贝构造函数。格式如下
类名 (类名 &对象名)
{
拷贝构造函数的函数体;
}
#include
class point
{
double fx,fy;
public:
point(point &p);
point(double, double);
void showpoint();
};
point::point(point &p)
{
fx = p.fx+10;
fy = p.fy+20;
}
point::point(double x,double y)
{
fx = x;
fy = y;
}
void point::showpoint()
{
cout<
在创建对象时,调用的是构造函数还是拷贝构造函数,由编译系统根据需要创建对的参数来确定。
调用拷贝构造函数
三种情况下拷贝构造函数会被调用
- 用类的对象去初始化该类的另一个对象时
- 函数的形参是类的对象,调用函数进行形参和实参的结合时
- 函数的返回值是类的对象,函数执行完返回调用者时
#include
class point
{
int x,y;
public:
point(int a=0,int b =0)
{
x = a;
y = b;
}
point(point &p);
int getx()
{
return x;
}
int gety()
{
return y;
}
};
point::point(point &p)
{
x = p.x+10;
y = p.y+20;
}
void f(point p)
{
cout<
默认拷贝构造函数
当用一个已经存在的对象初始化本类的新对象时,如果没有自定义拷贝构造函数,则系统会自动生成一个默认的拷贝构造函数来完成初始化的工作。
#include
class point
{
int x,y;
public:
point(int a,int b)
{
x = a;
y = b;
}
void showpoint();
};
void main()
{
point p1(1.1, 2.2);
cout<
默认的构造函数的调用也是由编译系统根据对象的特征自动调用的。
析构函数destructor
也是一种特殊的成员函数,被声明为公有成员。作用是释放分配给对象的内存空间,并做一些善后。
- 名字必须与类名相同,但在名字前面加符号~
- 析构函数没有参数,没有返回值,不能重载,在一个类中只能有一个析构函数
- 撤销对象时,系统会自动调用析构函数完成空间的释放和善后
#include
#include
class complex
{
double real, imag;
public:
complex(double r=0.0, double i=0.0);
~complex();
double abscomplex();
};
complex::complex(double r, double i);
{
cout<<"constructing..."<
返回的结果
constructing...
...
destructing...
- 每个类都必须有一个析构函数,若没有显式的定义,系统会自动生成一个默认的析构函数,它是一个空函数
- 对大多数类,默认的析构函数能满足要求,但如果对象在完成操作前需要做内部处理,则应显式的定义析构函数
- 构造函数和析构函数的常见用法是,在构造函数中用new运算符为对象分配空间,在析构函数中用delete运算符释放空间
友元friend
为了使类的private and protected成员能够被其他类或其他成员函数访问,引入友元概念。友元提供了不同类或对象的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。如果友元是一般成员函数或类的成员函数,则成为友元函数;如果友元是一个类,则称为一个类,友元类的所有成员函数都是友元函数。
友元函数
与普通成员函数不同,不是当前类的成员函数,而是独立于当前类的外部函数;可以是普通函数或其他类的成员函数。定义友元函数后可以访问该类的所有对象的成员,包括private/protected/public。
friend函数使用前必须要在类定义时声明,声明时在其函数名前加上关键字friend。该声明可放在公有成员中,也可放在私有成员中。定义既可以在类的内部进行,也可以在类外部进行,但通常定义在类的外部。一般声明格式
friend <数据类型><友元函数名>(参数表);
#include
class point
{
int x,y;
public:
point(int a=0,int b =0)
{
x = a;
y = b;
}
point(point &p);
int getx()
{
return x;
}
int gety()
{
return y;
}
friend double dist(point &p1, point &p2); //声明友元函数
};
double dist(point &p1, point &p2) //在类的外部定义友元函数
{
return (sqrt((p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y))); //友元函数可访问私有成员
}
point::point(point &p)
{
x = p.x+10;
y = p.y+20;
}
void main()
{
point p1(2,4);
point p2(4,5);
cout<
- 由于友元函数不是成员函数,因此在类的外部定义友元函数时,不必像成员函数那样在函数名前加::
- 友元函数不是类的成员,不能直接引用对象成员的名字,也不能通过this指针引用对象的成员,必须通过作为入口参数传递进来的对象名或对象指针来引用该对象的成员。为此,友元函数一般都带有一个该类的入口参数,如上面的double dist(point &p1, point &p2)
- 当一个函数需要访问多个类时,应该把这个函数同时定义为这些类的友元函数,使其能访问这些类的数据。
友元成员
如果一个类的成员函数是另一个类的友元函数,则这个成员函数是友元成员,它不仅可以访问自身类的私有公有成员,也可以访问声明了friend的类中的私有公有成员。
#include
#include
class boy;
class girl
{
char *name;
int age;
public:
girl(char *n, int a)
{
name =new char[strlen(n)+1]; // 分配空间
strcpy(name,n); //调用字符串拷贝函数
age= a;
}
void ptr(boy &); //声明公有成员
~girl();
{
delete name;
}
};
class boy
{
char *name;
int age;
public:
boy(char *n, int a)
{
name =new char[strlen(n)+1]; // 分配空间
strcpy(name,n); //调用字符串拷贝函数
age= a;
}
friend void girl::prt(boy &); //声明友元函数
~boy();
{
delete name;
}
};
void girl::prt(boy &b)
{
cout<<'girls age'<
需要注意,当一个类的成员函数作为另一个类的友元函数时,必须先定义成员函数所在的类,如上所示,先定义了类girl。在声明友元函数时,要加上成员函数所在类的类名和运算符::。类定义前使用该类的成员,需要在使用前对该类进行声明,比如上面声明了class boy,否则系统报错。
友元类
一个类左右另一个类的友元,称为友元类。成为了友元类,则这个类的所有成员含糊都成为另一个类的友元函数,因此一个类都可以通过对象名直接访问另一个类中的私有成员。友元类的声明可以放在类声明中的任何位置,此时友元类中的所有成员函数都是友元函数。一般格式如下
friend class <友元类名> //第一种格式
friend <友元类名> //第二种
#include
#include
class boy;
class girl
{
char *name;
int age;
public:
girl(char *n, int a)
{
name =new char[strlen(n)+1]; // 分配空间
strcpy(name,n); //调用字符串拷贝函数
age= a;
}
void ptr(boy &); //声明公有成员
~girl();
{
delete name;
}
};
class boy
{
char *name;
int age;
friend girl; //声明一个友元类
public:
boy(char *n, int a)
{
name =new char[strlen(n)+1];
strcpy(name,n);
age= a;
}
friend void girl::prt(boy &); //声明友元函数
~boy();
{
delete name;
}
};
void girl::prt(boy &b)
{
cout<<'girls age'<
友元关系是不能传递的,类B是类A的友元,类C是类B的友元,则C和A之间,除非特别声明,没有任何关系,不能共享数据。友元关系是单向的。B是A的友元,B可以访问A的私有和保护成员,反之,A的成员不能访问B的私有和保护成员。
Reference
1 刘蕾编著,21天学通C++(第五版),电子工业出版社
2 聚慕课教育研发中心 编著,C++从入门到项目实践(超值版),清华大学出版社