第三章 类和对象

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;
 }
 
 

 

posted @ 2020-09-24 13:39  做一只热爱生活的小透明  阅读(142)  评论(0)    收藏  举报