面向对象基础知识1
1、C++中类的成员函数的缺省访问权限是private。
2、在面向对象程序设计中,一般把数据成员设计成私有的,以便实现类的数据的“隐藏”。也就是说,用户在使用“类”时,不必关心类中具体的数据结构,而只要使用类的成员函数即可。
3、可以把类看成“黑箱子”,私有成员被“隐藏”在黑箱子中,要想存取私有成员,必须通过黑箱子上的专用窗口即公有函数接口来进行。
4、类的成员函数的定义可以在类体内,也可以在类体外。
1 //person.h文件 2 #include<string.h> 3 class Person 4 { 5 char name[20]; 6 char sex; 7 int age; 8 public: 9 void setData(char [],char,int); 10 void GetName(char *); 11 char GetSex(); 12 int GetAge(); 13 }; 14 15 void Person::setData(char na[], char s, int a) 16 { 17 strcpy(name,na); 18 sex=s; 19 age=a; 20 } 21 void Person::GetName(char *na) 22 { strcpy(na,name); } 23 char Person::GetSex() 24 { return sex; } 25 int Person::GetAge() 26 { return age; }
1 //person.cpp文件 2 #include "person.h" 3 #include<iostream> 4 using namespace std; 5 int main() 6 { 7 Person a,*pa; 8 char name[20]; 9 //以下通过对象访问成员 10 a.setData("Cheng",'F',20); 11 a.GetName(name); 12 cout<<"Name:"<<name<<endl; 13 cout<<"Sex:"<<a.GetSex()<<endl; 14 cout<<"Age:"<<a.GetAge()<<endl; 15 //以下通过指针访问成员 16 pa=&a; 17 pa->setData("Zhang",'M',18); 18 pa->GetName(name); 19 cout<<"Name:"<<name<<endl; 20 cout<<"Sex:"<<pa->GetSex()<<endl; 21 cout<<"Age:"<<pa->GetAge()<<endl; 22 return 0; 23 }
5、类和对象具有“封装性”和“隐藏性”。封装性是指将对象的静态属性和动态属性封装成一个整体——“类”类型,隐蔽性是指类的私有成员和保护成员被隐藏在类内,在类外不可通过对象或其指针直接访问,从而达到保护数据成员的目的。
6、对象的存储空间
对象是一个类的变量,也成为类的实例。
在VC++中,每定义一个对象,系统就给它分配空间,用于存放该对象的具体数据成员值;所有对象的成员函数的代码是相同的,因此系统将成员函数的存储空间处理成该类的所有对象共享同一代码空间。
1 #include "person.h" 2 #include<iostream> 3 using namespace std; 4 int main() 5 { 6 Person a; 7 int num; 8 num=sizeof(a); 9 cout<<num<<endl; 10 return 0; 11 }
上述代码的输出是:28
一个Person类对象的数据成员所占用存储空间理论上是25字节,但实际上,sizeof(a)和sizeof(Person)的值都是28,因为VC++6.0编译系统中,一般以4字节的整数倍分配空间。
对象空间的分配和撤销是系统根据对象的作用域自动完成的,即进入对象作用域时,系统自动为对象分配空间,退出对象作用域时,系统自动撤销对象空间。当使用new和delete运算符动态建立和撤销对象时,对象空间的分配和撤销是有编程者指定在何时完成的。
如果在程序执行的某一时刻动态申请了空间:
Person *p1,*p2; p1=new Person; p2=new Person[10]; delete p1; delete [] p2;
那么,在程序执行的另一时刻(一般是程序执行结束前,或者是离开该指针的作用域前),必须释放对象空间。
7、定义类和对象的有关说明
①自身类的指针或引用可以作为类的成员,但自身类的对象不可以作为成员。
②当定义一个类时,引用了另一个类,而另一个类的定义在当前类的后面,则必须对另一个类作引用性说明。
class Date; //对Date类的引用性说明 class Person { Date &day1,&day2; //使用了关键字Date Date *pd; }; class Date //Date类的定义 { ... };
③与结构体类似,定义类的对象也有三种方法:
a.类的定义完成后,定义对象。
b.在定义类的同时定义对象。
c.定义无类名的对象。
8、初始化对象、撤销对象
结构体变量的成员初始化可以在定义结构体变量的同时进行。
结构体是类的特例,结构体成员的缺省访问权限是公有的,在定义结构体变量时可以对其进行初始化。
但是,对于类而言,如果成员是公有的才能在定义对象时直接进行初始化(不通过构造函数)。
但是,如果类的成员是私有的,则在定义对象时不可以对其进行初始化。私有数据成员是通过类的公有成员函数进行赋值的。
实际上,私有数据成员的初始化也可以在定义对象时进行,它是通过C++提供的构造函数完成的。
9、构造函数和析构函数
①构造函数的功能:在创建对象时,由系统自动调用,用给定的值对数据成员初始化。
②析构函数的功能:系统在撤销对象时,自动调用它来做一些清理工作。
构造函数的特点:
①构造函数是成员函数,函数体可写在类体内,也可写在类体外。
类体内:ClassName(形参列表){}
类体外:ClassName::ClassName(形参列表){}
②构造函数是一种特殊的函数,函数名和类名相同,且不给出返回值类型。
③构造函数可以重载。
④一般将构造函数定义为公有成员函数。
⑤在创建对象时,系统自动调用构造函数。
⑥不可以通过对象名调用构造函数。
⑦可以直接通过构造函数名调用构造函数创建对象。d1=Date(2009)
析构函数特点:
①析构函数是成员函数,函数体可写在类体内,也可写在类体外。
类体内:~ClassName(){}
类体外:ClassName::~ClassName(){}
②一般将析构函数定义为公有成员函数。
③析构函数也是一种特殊的函数,其函数名是类名前加~,用来与构造函数区分。
④析构函数没有参数,且不给出返回值类型。因此一个类只能定义一个析构函数,即析构函数不允许重载。
⑤构造函数可以被显式调用,也可以由系统自动调用。
例如:a.~Student() //显式调用析构函数
在语法上是正确的,但是要慎用!程序在编译时,语法正确;但是在程序运行时,除了执行显式调用的析构函数外,程序结束前又调用了一次析构函数析构a对象,一段内存空间被释放了两次,出现运行错误。
1 //定义学生类,利用构造函数初始化数据成员,利用析构函数做清理工作 2 #include<iostream> 3 using namespace std; 4 class Student 5 { 6 char Num[10]; //学号,注意:用数组实现 7 char *Name; //姓名,注意:用指针实现 8 int Score; //成绩 9 public: 10 Student(char *nump,char *namep,int score) //构造函数 11 { 12 if(nump) //在构造函数中,不需要动态申请Num成员的空间 13 strcpy(Num,nump); 14 else 15 strcpy(Num,""); 16 if(namep) //在构造函数中,需要动态申请Name成员的空间 17 { 18 Name=new char[strlen(namep)+1]; 19 strcpy(Name,namep); 20 } 21 else 22 Name=0; 23 Score=score; 24 cout<<"构造函数被调用!"<<endl; 25 } 26 ~Student() 27 { 28 if(Name) 29 delete [] Name; 30 cout<<"析构函数被调用!"<<endl; 31 } 32 void Show() 33 { 34 cout<<Num<<'\t'<<Name<<'\t'<<Score<<endl; 35 } 36 }; 37 38 int main() 39 { 40 Student a("20121003727","Unique",99); 41 a.Show(); 42 int num1,num2; 43 num1=sizeof(a); 44 num2=sizeof(Student); 45 cout<<num1<<endl; 46 cout<<num2<<endl; 47 return 0; 48 }
程序运行结果为:
在下面两种情况下,析构函数会被自动调用:
①当对象是系统自动创建的,则在对象的作用域结束时,系统自动调用析构函数。例如,在函数体内定义了一个局部对象,当函数执行结束时,系统要撤销该对象,在撤销该对象前,系统先自动调用析构函数,然后再撤销该对象本身的存储空间。若定义一个全局对象,则当程序结束时(即main函数结束时),该对象的析构函数被自动调用。
②若一个对象是使用new运算符动态创建的,则在使用delete运算符释放它时,自动调用析构函数。
10、调用构造函数和析构函数的时机(对象的生存周期)
1)全局对象(总是静态)——程序开始执行时,系统自动创建对象,调用构造函数;程序结束时,系统在撤销对象前调用析构函数。“程序开始执行时”是指main()函数开始执行时,“程序结束时”是指main()函数执行结束时。
2)局部动态对象——当程序执行进入作用域,在定义对象处系统自动创建对象,调用构造函数;退出作用域时,系统在撤销对象前调用析构函数。
3)局部静态对象——当程序执行首次到达定义对象处,系统自动创建对象,调用构造函数;程序结束时,系统在撤销对象前调用析构函数。(只调用一次构造函数,且main函数结束时才撤销)
4)动态申请的对象——使用new产生对象时,系统自动调用构造函数;使用delete撤销对象时,系统自动调用析构函数。
1 //调用构造函数和析构函数的时机 2 #include<iostream> 3 using namespace std; 4 class Date 5 { 6 int year,month,day; 7 public: 8 Date(int y=2002,int m=1,int d=1) 9 { 10 year=y;month=m;day=d; 11 cout<<"Constructor:"; 12 ShowDate(); 13 } 14 void ShowDate() 15 { 16 cout<<year<<'.'<<month<<'.'<<day<<endl; 17 } 18 ~Date() 19 { 20 cout<<"Destructor:"; 21 ShowDate(); 22 } 23 }; 24 25 Date d4(2008,4,4); //全局对象(静态的) 26 void fun() 27 { 28 cout<<endl; 29 cout<<"进入fun()函数!"<<endl; 30 static Date d2(2008,2,2); //局部静态函数 31 Date d3(2008,3,3); //局部动态函数 32 cout<<"退出fun()函数!"<<endl; 33 } 34 int main() 35 { 36 cout<<"进入main()函数!"<<endl; 37 Date d1(2008,1,1); //局部动态对象 38 fun(); 39 fun(); 40 cout<<endl; 41 cout<<"退出main()函数!"<<endl; 42 return 0; 43 }
程序运行结果:
在主函数结束时,先撤销主函数中的局部对象,再撤销静态对象,对象的撤销顺序(即调用析构函数的顺序)与定义顺序(即调用构造函数的顺序)相反。
11、缺省构造函数和缺省析构函数
类的缺省构造函数有两种形式:
1)没有参数的构造函数
Date::Date(){year=2009;month=5;day=1;}
2)所有参数均带有缺省值的构造函数
Date(int y=2009,int m=5,int d=1){year=y;month=m;day=d;}
注意:
①在产生对象时,若不需要对数据成员进行初始化,可以不显式定义缺省构造函数。
②在一个类的定义中,缺省构造函数只能有一个,即上述两种形式的缺省构造函数只能写一个。
③若编程者给出了构造函数,则编译系统不再自动生成缺省构造函数。
④在撤销对象时,若不需要做任何结束工作,可以不显式定义析构函数。
⑤当类中有动态申请的存储空间时,必须显式定义析构函数,撤销动态存储空间。