C++之类和对象
一,C中结构体的缺陷
1,main函数中的任意赋值语句都可以访问结构体中的成员,但在现实生活中并不是什么数据都可以被随意访问的,因此C语言中的结构体的数据是不安全的;
2,结构体中的数据和对该数据的操作是分离的,并不是一个被封装起来的整体,因此使程序难以重用,影响了软件生产效率;于是C++中引入了类的概念。
客户/服务模型
OOP(面向对象程序设计)程序员常依照客户/服务模型来讨论程序设计。在这个概念中,客户是使用类的程序。类声明(包括类方法)构成了服务器,他是程序可以使用的资源。客户只能通过以公有方式定义的接口使用服务器,这意味着客户程序员唯一的责任是了解该接口。服务器设计人员的责任时确保服务器根据该接口可靠并准确的执行。服务器设计人员只能修改类设计的实现细节,而不能修改接口。这样程序员独立的对接口和服务器进行改进,对服务器的修改不会对客户的行为造成意外的影响。
二,类与对象
#include <iostream> using namespace std; class Point { private: static int count; //记录构造的对象数,静态数据成员 int x_; int y_; string name_; public: Point(const string &name, int i, int j); //带参构造函数 Point(); //默认构造函数,构造函数重载 Point(const Point &p); //拷贝构造函数 ~Point(void); //析构函数 static void showCount(); //静态成员函数 void showPoint(); int ReadX() const { //内联成员函数(隐式声明),在声明前面加inline修饰显示声明内联函数 return x_; }; int ReadY() const { return y_; }; }; //成员函数实现部分 Point::Point(const string & name, int x, int y) { count++; name_ = name; x_ = x; y_ = y; cout<<count<<":调用非默认构造函数:Point("<<name_<<","<<x_<<","<<y_<<")"<<endl; } Point::Point() { count++; x_ = y_ = 0; name_ = "no name"; cout<<count<<":调用默认构造函数:Point("<<name_<<","<<x_<<","<<y_<<")"<<endl; } Point::Point(const Point &p) { count++; name_ = p.name_; x_ = p.x_; y_ = p.y_; cout<<count<<":调用拷贝构造函数:Point("<<name_<<","<<x_<<","<<y_<<")"<<endl; } void Point::showPoint() { cout<<"Point对象:"<<name_<<","<<x_<<","<<y_<<endl; } void Point::showCount() { cout<<"目前已经创建了count="<<count<<"个对象"<<endl; } Point::~Point(void) { count--; cout<<"调用析构函数,释放类Point("<<name_<<","<<x_<<","<<y_<<")"<<endl; } int Point::count = 0; //初始化静态数据成员,注意使用类名限定 Point func(Point pointA); int main(void) { { Point pointA("pointA", 20, 30); Point pointC(pointA); //用pointA初始化pointC,调用拷贝构造函数(1) pointC.showPoint(); Point pointB; //pointB的数据成员被初始化为0 //赋值运算,这里不是初始化.创建一个临时对象然后赋值给pointB pointB = Point("pointB", 1, 2); Point pointD = Point("pointD", 3, 4); //使用临时对象初始化pointD //Point pointE{"pointE", 5, 6};//使用初始化列表初始化pointE, C++11 Point *pointP = new Point("pointP", 7, 8); //C++11 delete pointP; pointB = func(pointA); pointB.showPoint(); cout<<"Done!"<<endl; } cout<<"exit now!\n"; return 0; } Point func(Point pointA) //类作形参,调用拷贝构造函数(2) { cout<<"在func函数体内\n"; return Point("temp", pointA.ReadX()+10, pointA.ReadY()+10); //定义临时对象,调用拷贝构造函数3次返回Point类 }
运行结果(VC6.0):
1:调用非默认构造函数:Point(pointA,20,30)
2:调用拷贝构造函数:Point(pointA,20,30)
Point对象:pointA,20,30
3:调用默认构造函数:Point(no name,0,0)
4:调用非默认构造函数:Point(pointB,1,2)
调用析构函数,释放类Point(pointB,1,2)
4:调用非默认构造函数:Point(pointD,3,4)
5:调用非默认构造函数:Point(pointP,7,8)
调用析构函数,释放类Point(pointP,7,8)
5:调用拷贝构造函数:Point(pointA,20,30)
在func函数体内
6:调用非默认构造函数:Point(temp,30,40)
调用析构函数,释放类Point(temp,30,40)
调用析构函数,释放类Point(pointA,20,30)
Point对象:temp,30,40
Done!
调用析构函数,释放类Point(pointD,3,4)
调用析构函数,释放类Point(temp,30,40)
调用析构函数,释放类Point(pointA,20,30)
调用析构函数,释放类Point(pointA,20,30)
exit now!
1,类与对象
数据成员:只有数据类型,无存储类型,因此不能再类的声明中初始化数据成员;在类外,不能访问私有和保护成员
成员函数在类外定义:
成员函数体内可以访问该类的所有数据成员;非静态成员函数中都有一个隐含的参数,即this指针,该指针指向当前正在调用成员函数的对象
2,构造函数与析构函数
默认构造函数:当且仅当没有定义任何构造函数时,编译器才会提供默认构造函数。所以当定义非默认的构造函数时,必须定义默认构造函数,否则下面的声明将会出错:
Point pointB; //调用默认构造函数
或者是给非默认构造函数提供默认值:Point(const string &name = "no name", int x=0, int y=0);
析构函数:无参数不可重载,在对象存在的函数体结束时或使用delete释放new创建的对象时被自动调用
缺省拷贝构造函数:把初始对象的每个数据成员的值都复制到新建对象中
它们都没有返回值;调用析构函数和构造函数的顺序正好相反;对没有定义构造函数的类,其公有数据成员可以用初始化值表进行初始化
拷贝构造函数的调用:
a,由一个类的对象M初始化该类另一个对象N时:Point N(M);
b,当类的对象N作为函数实参传递给函数形参时:P = func(N);
3,类的作用域
定义成员函数时,使用作用域运算符(::)来标识所属的类。
类的作用域:类作用域意味着不能不能从外部直接访问类的成员,公有成员函数也是如此。在类声明或成员函数定义中,可以使用未限定的成员名称 。在其他情况下使用类成员名时,必须根据上下文使用直接成员运算符(.),间接成员运算符(->)或作用域解析运算符(::)。
作用域为类的常量:
class Bakery { private: static const int Months = 12; //该常量与其他静态变量存储在一起,而不是存储在对象中 double costs[Months]; }; //或者定义枚举常量 class Bakery { private: enum {Months = 12}; double costs[Months]; };
作用域内枚举:
当两个枚举定义中的枚举量可能发生冲突时,可以定义作用域为类的枚举。如下所示:
//发生冲突的枚举定义 enum egg {Small, Medium, Large, Jumbo}; enum t_shirt {Small, Medium, Large, Xlarge}; //类作用域的枚举定义 enum calss egg {Small, Medium, Large, Jumbo}; enum class t_shirt {Small, Medium, Large, Xlarge}; enum egg choice = egg::Large; enum t_shirt Floyd = t_shirt::Large;
C++11还提供了作用域内枚举的类型安全。在有些情况下,常量枚举将自动转换为整形,但作用域内枚举不能隐式的转换为整形。
但在必要时可以进行显示类型转换:int Floyd = int(t_shirt::Large);
3,注意事项
a,定义类的指针时,不调用类的构造函数;拷贝构造函数也是构造函数的重载
b,malloc/free和new/delete(C++)
调用malloc和free没有调用构造和析构函数,而调用new和delete给类指针分配存储空间可以执行构造和析构函数,也可以为其他类型的指针分配存储空间。必须配对使用。
c,"类名 对象名",当定义了构造函数,调用的是无参的构造函数创建对象或者是带默认形参的构造函数,没有定义构造函数时,调用的是缺省构造函数。而"类名 对象名()" 不能调用任何一种构造函数。
d,使用inline定义的内联函数必须将类的声明和内联成员函数的定义都放在同一个文件中,否则编译时无法进行代码的置换
e,this指针的作用:C++的编译系统用一段空间来存放各个对象共同的函数代码段,每个对象的存储空间只是对象数据成员所占用的存储空间。而成员函数就是通过this指针知道是哪个对象调用了
三,共享机制一(静态成员)
静态成员实现一个类多个对象之间的数据共享
1,静态数据成员
是类的所有对象共享的成员,静态数据成员的值对每一个对象是一样的,可以被该类的任何一个对象更新。静态数据成员在声明类的时候就分配了内存,在定义对象之前就存在了。
必须在类外初始化:
<数据类型> <类名>::<静态数据成员名> = <值> //一般放在类定义之后
类外对公有静态数据成员的引用:
<类名>::<公有静态成员名> 或 <对象名>.<公有静态成员名>
2,静态成员函数
能直接引用类中的静态成员,但只能通过对象,对象的指针或引用来访问类中的非静态成员(类的对象做函数参数);静态成员函数中没有this指针
使用格式:
<类名>::<公有静态成员函数名>(<参数表>) 或 <对象名>.<公有静态成员函数名>(<参数表>)
指一个类内嵌其他类的的对象作为成员的情况,两个类间是包含与被包含的关系
创建对象时,内嵌的对象成员会被自动创建,因此要初始化本类的基本数据成员和内嵌的对象成员
组合类构造函数(需通过初始化列表对内嵌对象初始化):
类名::类名(形参表):内嵌对象1(形参表1),内嵌对象2(形参表2),…… {类的初始化}
#include <iostream> using namespace std; #include <cmath> class Point { private: int x,y; public: Point(int i, int j):x(i),y(j) { cout<<"调用Point构造函数:Point("<<x<<", "<<y<<")"<<endl; } Point(const Point &p) { cout<<"调用Point拷贝构造函数"<<endl; x=p.x; y=p.y; } int ReadX() { return x; } int ReadY() { return y; } ~Point() { cout<<"调用Point析构函数"<<endl; } }; class Line { public: Line(Point xp1, Point xp2); Line(int i, int j, int m, int n); //构造函数重载 ~Line() { cout<<"调用Line析构函数"<<endl; } private: Point p1, p2; }; Line::Line(Point xp1, Point xp2):p1(xp1),p2(xp2) { cout<<"调用Line构造函数1: "; cout<<"Point("<<p1.ReadX()<<", "<<p1.ReadY()<<") Point("<<p2.ReadX()<<", "<<p2.ReadY()<<")"<<endl; } Line::Line(int i, int j, int m, int n):p1(i,j),p2(m,n) { cout<<"调用Line构造函数2: "; cout<<"Point("<<p1.ReadX()<<", "<<p1.ReadY()<<") Point("<<p2.ReadX()<<", "<<p2.ReadY()<<")"<<endl; } void main() { Line line1(Point(1,2), Point(3,4)); //用临时对象做实参 Line line2(5,6,7,8); }
运行结果(VC++6.0):
注意事项:
<1>自身类的对象不可以作为该类的成员;自身类的引用或指针可以作为该类的成员(递归类)
<2>前向引用声明
class N; class M {private: N *n;}; //n是N类的指针 class N {public: void f(M m);}; //m是M类的对象
五,共享机制二(友元函数和友元类)
友元提供了不同类或对象的成员函数之间、类的成员与一般函数之间进行数据共享的机制。
<1>友元函数
在友元函数体中可以通过对象名访问类的私有和保护成员。友元函数分友元非成员函数和友元成员函数。
友元非成员函数
友元函数为非成员函数,那么在定义该函数时不用在前面加"类名::",同样的它也没有所谓的this指针
#include <iostream> using namespace std; class Employee { private: char *name; char *id; static int count; public: Employee(char* name, char* id); ~Employee(); static void showEmployeeCount(); friend void showEmployee(Employee &emp); }; Employee::Employee(char* name, char* id) { this->count ++; this->name = new char[strlen(name)+1]; this->id = new char[strlen(id)+1]; strcpy(this->name, name); strcpy(this->id, id); } void Employee::showEmployeeCount() { cout<<"雇员总数: count="<<count<<endl; } void showEmployee(Employee &emp) //友元非成员函数定义 { cout<<"name="<<emp.name<<", id="<<emp.id<<endl; } Employee::~Employee() { delete []name; delete []id; } int Employee::count = 0; void main() { Employee emp[3] = { //创建临时对象初始化对象数组 Employee("haha", "11070000"), Employee("hehe", "11070001"), Employee("yiyi", "11070002") }; for (int i=0; i<3; i++) { showEmployee(emp[i]); //友元非成员函数的调用 } emp->showEmployeeCount(); }
运行结果(VC6.0):
友元成员函数
友元成员函数不仅可以访问自己所在类对象中的私有,公有或保护成员,也可以访问friend声明语句所在的类对象中的所有成员,这样就实现了类与类之前的协作
#include <iostream> using namespace std; #include <string> class Salary; //前向引用声明 class Employee { private: string name; string id; static int count; public: Employee(string name, string id); static void showEmployeeCount(); void showEmployee(Salary &sal); //成员函数,参数是Salary对象的引用 }; Employee::Employee(string name, string id):name(name),id(id) { this->count ++; } void Employee::showEmployeeCount() { cout<<"雇员总数: count="<<count<<endl; } int Employee::count = 0; class Salary { private: double salary; //薪水 double wage; //工资 double bonus; //奖金 double commission; //提成 public: Salary(double salary, double wage, double bonus, double commission); friend void Employee::showEmployee(Salary &sal); }; Salary::Salary(double salary, double wage, double bonus, double commission):salary(salary),wage(wage),bonus(bonus),commission(commission) {} void Employee::showEmployee(Salary &sal) //友元非成员函数定义 { cout<<"name="<<name<<", id="<<id<<endl; cout<<"salary: "<<sal.salary<<endl; //可以访问Salary类中的私有数据成员 cout<<"wage: "<<sal.wage<<endl; cout<<"bonus: "<<sal.bonus<<endl; cout<<"commission: "<<sal.commission<<endl; } void main() { Employee emp("yiyi", "11070002"); Salary sal(10000,50000,30000,20000); emp.showEmployee(sal); emp.showEmployeeCount(); }
运行结果:
注意,在涉及到这种类的前向引用声明的,将成员函数Employee::showEmployee(Salary &sal)的实现放在这两个类的定义之后.要不然编译时会出错
<2>友元类
如果一个类被说明为另一个类的友元类,那么这个类的所有成员函数都将成为另一个类的友元函数。
注意:友元关系是单向的,不具有交换性,也不具有传递性。比如类A为类B的友元类,类B为类C的友元类,并不代表类A为类C的友元类,是不是友元类,看其类A有没有在类C中声明;
六,C++的多文件程序
C++的源程序基本上由3个部分组成:类的声明部分、类的实现部分和类的使用部分,针对3个部分,C++中对应分为3个文件:类声明文件(*.h文件)、类的实现文件(*.cpp)和类的使用文件(*.cpp,主函数main文件)。
分为3部分主要有以下几个方面:(1)类的实现文件通常会比较大,将类的声明和实现放在一起,不利于程序的阅读、管理和维护。我们通常说接口应该和实现的部分分离,这样可以更易于修改程序。(2)把类成员函数的实现放在声明文件中和单独放实现文件里,在编译时是不一样的。前者是作为类的内联函数来处理的。(3)对于软件的厂商来说,它只需向用户提供程序公开的接口(只需提供*.h文件),而不用公开程序源代码。