第三章 类和对象
1.构造函数
#include <iostream> #include <string> using namespace std; class myDate{ public: //声明4个构造函数 myDate(); //不带参构造函数 myDate(int);//带1个参数 myDate(int,int);//带2个参数 myDate(int,int,int);//带3个参数 void printDate() const;//打印日期 private: int year,month,day;//成员变量,表示年,月,日 }; myDate::myDate(){}//无参 myDate::myDate(int d){//1个参 day=d; } myDate::myDate(int m,int d){//2个参数 month=m; day=d; } myDate::myDate(int y,int m,int d){//3个参数均有值 year=y; month=m; day=d;
//return; //错误 构造函数无返回值 } void myDate::printDate() const{ cout<<year<<"/"<<month<<"/"<<day; return; } int main(){ myDate m(1010,10,10);
cout<<m(1010,10,10)<<endl;//错误,构造方法只是把实例构造出来,不可以直接打印,需要写一个打印的方法,调用
m.printDate();
return 0; }
分析:
1.构造方法只是把实例构造出来,不可以直接打印
2.set,get和打印方法是普通方法可以有返回值,构造函数没有。
3.定义类的对象时,会自动调用类的构造函数
4.若用户没有定义构造函数时,系统会调用默认的构造函数
注意:构造函数的函数名与类名相同 2.参数个数不同 3.参数类型不同 4.无返回值 5.构造函数可以重载
2.复制构造函数
复制构造函数也称拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:
-
通过使用另一个同类型的对象来初始化新创建的对象。
-
复制对象把它作为参数传递给函数。
-
复制对象,并从函数返回这个对象。
如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。拷贝构造函数的最常见形式如下:
4)格式:
classname (const classname &obj) {
// 构造函数的主体
} 在这里,obj 是一个对象引用,该对象是用于初始化另一个对象的。
A:A(const A &)或 A:A(A &)
实例:
#include <iostream> using namespace std; class A{ public: A (int a);//简单的构造函数 A(const A &obj);//拷贝构造函数 int getLength(void); private: int *ptr; }; A::A(int len){ cout<<"调用构造函数:"<<endl; //为指针分配内存 ptr=new int; *ptr=len; } A::A(const A &obj){ cout<<"调用拷贝构造函数并为指针ptr 分配内存"<<endl; ptr=new int; *ptr=*obj .ptr; } int A::getLength(void){ return *ptr; } void display(A obj){ cout<<"a的大小: "<<obj.getLength()<<endl; } int main(){ A a(10); display(a); return 0; }
输出:
调用构造函数: 调用拷贝构造函数并为指针ptr 分配内存 a的大小: 10
分析:
当调用拷贝构造函数时,类的对象正在被建立并被初始化。
自动调用复制构造函数的情况有以下3种:
1)当用一个对象去初始化本类的另一个对象时,会调用复制构造函数。例如,使用下 列形式的说明语句时,即会调用复制构造函数。
类名 对象名2(对象名1);
类名 对象名2=对象名1;
2)如果函数F的参数是类A的对象,那么当调用F时,会调用类A的复制构造函数。换 句话说,作为形参的对象,是用复制构造函数初始化的,而且调用复制构造函数时的 参数,就是调用函数时所给的实参。
3)如果函数的返回值是类A的对象,那么当函数返回时,会调用类A的复制构造函数。 也就是说,作为函数返回值的对象是用复制构造函数初始化的,而调用复制构造函数 时的实参,就是retrun语句所返回的对象。
注意,在复制构造函数的参数表中,加上const是更好的做法。这样复制构造函数才 能接收常量对象作为参数,即才能以常量对象作为参数去初始化别的对象。
3.析构函数
#include <iostream> #include <string> using namespace std; class myDate{ public: myDate(); //构造函数 ~myDate(); }; class Student{ public: Student(); ~Student(); private : string name;//姓名 myDate birthday;//生日 }; myDate::myDate() { cout<<"myDate 构造函数"<<endl; } myDate::~myDate() { cout<<"myDate 析构函数"<<endl; } Student::Student() { cout<<"Student 构造函数"<<endl; } Student::~Student() { cout<<"Stuclent 析构函数"<<endl; } int main(){ Student *stud = new Student(); delete stud; }
输出:
myDate 构造函数
Student 构造函数
Stuclent 析构函数
myDate 析构函数
分析:
1.为什么我只实例化了Student类,却把myDate类的构造和析构打印出来了呢?
因为把myDate类当做一个student的一个属性
2.打印为什么是先打印myDate类呢?
因为只有这个myDate构造出来了才能构造自己的,析构的顺序和构造的顺序相反。
其他:如果正常实例化,就按照正常的执行顺序,如:
#include <iostream> using namespace std; class Clock{ public: Clock(){cout<<"clock的构造函数"<<endl;} ~Clock(){cout<<"clock的析构函数"<<endl;} }; class Date{ public: Date(){cout<<"date的构造函数"<<endl;} ~Date(){cout<<"date的析构函数"<<endl;} }; int main(){ Clock c; Date d; return 0; }
输出:
clock的构造函数
date的构造函数
date的析构函数
clock的析构函数
还有一种情况,继承了其他类,是先析构自已的
#include <iostream> using namespace std; class A{ int a; public: A(int aa=0){a=aa;} ~A(){cout<<"析构函数A!"<<a<<endl;} }; class B:public A{ int b; public: B(int aa=0,int bb=0):A(aa){b=bb;} ~B(){cout<<"析构函数B!"<<b<<endl;} }; int main(){ B x(5),y(6,7); }
输出:
析构函数B!7 析构函数A!6 析构函数B!0 析构函数A!5
注意:
1)与类名名字相同,在类名前面加一个“~”字符,区别于构造函数,
2)析构函数无参数,无返回值。一个类中有且仅有一个析构函数。
3)如果程序中没有定义析构函数,则自动生成默认的析构函数。
4)当使用new运算符生成对象指针时,自动调用本类的构造函数。
使用 delete删除这个对象时,首先为这个动态对象调用本类的析构函数,然后再 释放这个动态对象占用的内存。
何时调用:
--在对象消亡时自动调用析构函数,它的作用是做一些善后处理的工作。
简言之:后构造的先析构
4.类的静态成员
#include <iostream> using namespace std; class Sample{ private : int x; static int y;//声明静态成员 public: Sample(int a);//定义有参构造函数 void print(); int getX() { return x; } }; Sample::Sample(int a){ x=a; y=x++; } void Sample::print(){//代表print是Sample类的方法,声明在类里,只是实现在类外 //cout<<"x= "<<x<<" y="<<y<<endl;//直接属性取值 cout<<"x= "<<Sample::x<<" y="<<Sample::y<<endl;//所以x不是静态成员变量也可以通过类名::属性名拿到值 } int Sample::y=25;//初始化静态数据成员,类外赋初始值,不能加static int main(){ Sample s1(5); Sample s2(10); s1.print(); s2.print(); return 0; }
输出:
x= 6 y=10 x= 11 y=10
static就是静态的这类的成员属于类级别的,一般用于初始化操作
const修饰的不能被赋值普通对象
1.静态局部变量:用于函数体内部修饰变量,这种变量的生存期一直到程序关闭。
作用域:函数体内部, 生存期:整个程序运行期间
准确来说 ,刚刚y 是属于类,所有人都可以去修改,x只是属于某个对象,不能随便修改
5.常量成员和常量引用
关键字:const
const修饰的类成员变量称为类的常量成员变量,类的常量成员变量必须进行初始化,而且只能通过构造函数的成员初始化列表的方式进行。
常量成员变量格式:const 数据类型 常量名-表达式;
常量函数格式:类型说明符 函数名 (参数表) const;
注意:对于常量对象,只能调用常量函数。
实例:
#include <iostream> using namespace std; class CDemo{ public: void SetValue(){}//非常量成员函数 }; int main(){ const CDemo Obj;//Obj是常量对象 Obj.SetValue();//错误! return 0; }
使用常量对象不能调用非常量成员函数。
6.友元(friend)
目的:破坏了类的封装性和信息隐藏,有助于信息共享,能够提高程序执行效率。当出现这个字,就表示该类为类的友元类。
友元不是本类的成员函数。
7.友元函数
1)在友元函数内部可以直接访问本类对象的私有成员,一个类中可以有多个友元函数。
格式:friend 返回值类型 函数名(参数表);
2)当某类A的定义后,将类A的成员函数说明为本类的友元函数的格式如下:
friend 返回值类型 类A::类A的成员函数名(参数表);
3)不能把其他类的私有成员函数声明为友元函数,友元函数不是类的成员函数,但允许访问类中的所有成员。
4)在函数体中访问对象成员时,必须使用"对象名.对象成员名"
5)友元函数不受类中的访问权限关键字限制,可以把他放在类的共有,私有,保护部分,结果是一样的。
实例:
#include <iostream> using namespace std; #include <cmath> class Piano;//类的声明 class Test{ public: void printX(Piano p);//用到了类Piano }; class Piano{//类的定义 private: int x,y; public: Piano(int x0,int y0){//类的构造函数 x=x0; y=y0; } void printxy(){ cout<<"====piano:("<<x<<"\t"<<y<<")"<<endl; } friend double getDist(Piano p1,Piano p2); friend void Test::printX(Piano p); }; void Test::printX(Piano p){//实现Test里面的这个printX方法 cout<<"x="<<p.x<<"\ty="<<p.y<<endl;//访问类Piano的私有成员 return; } double getDist(Piano p1,Piano p2){ double xd=double(p1.x-p2.x);//使用类Piano的私有成员x double yd=double(p1.y-p2.y);//使用类Piano的私有成员y return sqrt(xd*xd+yd*yd);//两点间距离 } int main(){ Piano p1(0,0),p2(10,10); p1.printxy(); p2.printxy(); cout<<"(p1,p2)间距离="<<getDist(p1,p2)<<endl; Test t; cout<<"从友元函数中输出--"<<endl; t.printX(p1);//通过对象调用类的成员函数 t.printX(p2);//通过对象调用类的成员函数 return 0; }
输出:
====piexl:(0 0) ====piexl:(10 10) (p1,p2)间距离=14.1421 从友元函数中输出-- x=0 y=0 x=10 y=10
8.this指针
#include <iostream> using namespace std; class Student{ public: void setName(char * name); void setAge(int age); void show(); private: char * name; int age; }; void Student::setName(char * name){ this->name=name; } void Student::setAge(int age){ this->age=age; } void Student::show(){ cout<<name<<"的年龄是:"<<age<<endl; } int main(){ Student *p=new Student(); p->setName("小李"); p->setAge(18); p->show(); delete p; return 0; }
注意:
你这里 p-> setName("**"); 这种写法是用指针去调用的
this->age=age;this是指针,是指向调用者的本身,加this->name=name 因为这里成员变量和参数同名了,计算机就知道你是指调用类的name=参数name
总结:参数和类的属性同名的时候才用this来作区别,不同命名不需要用this,计算机能区别那个跟那个。
如果我这里main函数不用指针的形式呢?如何实现?
#include <iostream> using namespace std; #include <string> class Student{ public: void setName(string name); void setAge(int age); void show(); private: string name; int age; }; void Student::setName(string n){ name = n; } void Student::setAge(int a){ age = a; } void Student::show(){ cout<<name<<"的年龄是:"<< age<<endl; } int main(){ Student p; p.setName("小李"); p.setAge(18); p.show(); return 0; }

浙公网安备 33010602011771号