类和对象的基本概念

类和对象的基本概念

类和对象

从客观事物抽象出类。
举例理解类:
写一个程序,输入矩形的长和宽,输出面积和周长。

//面向过程的方法:
#include <iostream>
using namespace std;

int main()
{
    double length, width;
    double area = 0, perimeter = 0;
    cout << "请输入矩形的长:" << endl;
    cin >> length;
    cout << "请输入矩形的宽:" << endl;
    cin >> width;
    area = length * width;
    perimeter = 2 * (length + width);
    cout << "矩形的面积为:" << area << endl;
    cout << "矩形的周长为:" << perimeter << endl;

    return 0;
}

/*--------------------------------------------*/
//面向对象的方法:
#include <iostream>
using namespace std;

class Rectangular  //矩形类
{
public:
    double length, width;  //长和宽
    double Area()   //面积
    {
        return length * width;
    }
    double Perimeter()   //周长
    {
        return 2 * (length + width);
    }
    void Init(double l_, double w_)   //初始化长和宽
    {
        length = l_;
        width = w_;
    }
};

int main()
{
    double l, w;
    Rectangular r;    //r是一个对象
    cin >> l >> w;
    r.Init(l, w);
    cout << r.Area() << endl << r.Perimeter();

    return 0;
}

类定义出来的变量就是对象。
c++中类的名字就是用户自定义的类型的名字,可以像使用基本类型那样来使用它。
对象所占用的内存空间的大小等于所用成员变量的大小之和。(不包括成员函数)

使用类的成员变量和成员函数

用法1:对象名.成员名

Rectangular r1, r2;
r1.length = 5.0;
r2.Init(5.0, 4.1);

用法2:指针->成员名

Rectangular r1, r2;
Rectangular *p1 = &r1;
Rectangular *p2 = &r2;
p1->width = 5.0;
p2->Init(5.0, 4.0);  //Init作用在p2指向的对象上。

用法3:引用名.成员名

Rectangular r2;
Rectangular &rr = r2;
rr.length = 5.0;
rr.Init(5.0, 4.0);  //rr的值变了,r2的值也变了。

void PrintfRectangular (Rectangular &r)
{
    cout << r.Area() << "," << r.Perimeter();
}
Rectangular r3;
r3.Init(5.0, 4.0);
PrintfRectangular(r3);

类的成员函数与类的定义分开写

class Rectangular
{
public:
    double length, width;
    double Area();              // 成员函数仅在此声明
    double Perimeter();
    void Init(double l_, double w_);
};

double Rectangular::Area()
{
    return length * width;
}

double Rectangular::Perimeter()
{
    return 2 * (length + width);
}

void Rectangular::Init(double l_, double w_)
{
    length = l_;
    width = w_;
}
//Rectangular::说明后面的函数是Rectangular类的成员函数,而非普通函数。所以一定要通过对象或者对象的指针或者对象的引用才能调用。

类成员的可访问范围

在类的定义中,可以用下列访问范围关键字来说明类成员可被访问的范围:
public: 公有成员,可以在任何地方访问;
private: 私有成员,只能在成员函数内访问;
protected: 保护成员。
三种关键字出现的次数和先后顺序都没有限制。
如果某个成员前面没有上述关键字,则缺省地被认为是私有成员。

class Man
{
    int nAge;          //私有成员
    char szName[20];   //私有成员
public:
    void SetName(char *szName)
    {
        strcpy(Man::szName, szName);
    }
};

在类的成员函数内部,能够访问:当前对象的全部属性、函数;同类其他对象的全部属性、函数。
在类的成员函数以外的地方,只能够访问该类对象的公有成员。

class CEmployee
{
private:
    char szName[30];  //名字
public:
    int salary;    //工资
    void SetName(char *name);
    void GetName(char *name);
    void AverageSalary(CEmployee e1, CEmployee e2);
};

void CEmployee::SetName(char *name)
{
    strcpy(szName, name);
}

void CEmployee::GetName(char *name)
{
    strcpy(name, szName);
}

void CEmployee::AverageSalary(CEmployee e1, CEmployee e2)
{
    cout << e1.szName;    //正确,访问同类其他对象私有成员
    salary = (e1.salary + e2.salary) / 2;
}

int main()
{
    CEmployee e;
    strcpy(e.szName, "Tom123456789");  //编译错误:不能访问私有成员
    e.SetName("Tom");         //正确
    e.salary = 50000;         //正确

    return 0;
}

设置私有成员的机制叫“隐藏”。
“隐藏”的目的是为了强制对成员变量的访问一定要通过成员函数进行,那么以后成员变量的类型等属性修改后,只需要更改成员函数即可。否则,所有直接访问成员变量的语句都需要修改。
例如:若将上面的程序移植到内存空间紧张的手持设备上,希望将szName改为 char szName[5], 若szName不是私有,那么就要找出所有类似 strcpy(e.szName, “Tom123456789”);这样的语句进行修改,以防止数组越界。
若将szName变成私有,那么程序中就不可能出现(除非在类的内部)strcpy(e.szName, “Tom123456789”);这样的语句,所有对szName的访问都是通过成员函数来进行的,如:e.SetName(“Tom123456789”);
那么,就算szName改短了,上面的语句也不需要找出来修改,只需要改SetName成员函数,在里面确保不越界就行。

成员函数的重载及参数缺省

class Location
{
private:
    int x, y;
public:
    void init(int x = 0, int y = 0);
    void valueX(int val)
    {
        x = val;
    }
    int valueX()
    {
        return x;
    }
};

void Location::init(int X, int Y)
{
    x = X;
    y = Y;
}

int main()
{
    Location A, B;
    A.init(5);
    A.valueX(5);
    cout << A.valueX();

    return 0;
}

使用缺省参数要注意避免有函数重载时的二义性

class Location
{
private:
    int x, y;
public:
    void init(int x = 0, int y = 0);
    void valueX(int val = 0)
    {
        x = val;
    }
    int valueX()
    {
        return x;
    } 
};

int mian()
{
    Location A;
    A.valueX();   //错误:编译器无法判断调用那一个valueX

    return 0;
}

构造函数

1、构造函数属于成员函数的一种。
2、它的名字与类名相同,可以有参数,不能有返回值(void也不行)。
3、作用:对对象进行初始化,如给成员变量赋初值。
4、如果定义类时没写构造函数,则编译器生成一个默认的无参数的构造函数。默认构造函数无参数,不做任何操作。
5、如果定义了构造函数,则编译器不生成默认的无参构造函数。
6、对象生成时构造函数自动被调用。对象一旦生成,就再也不能在其上执行构造函数。
7、一个类可以有多个构造函数。
8、为什么需要构造函数:
1)构造函数执行必要的初始化工作,有了构造函数就不需要再写初始化函数,也不用担心忘记调用初始化函数。
2)有时对象没有被初始化使用,会导致程序出错。

//默认构造函数实例:
class Complex
{
private:
    double real, imag;
public:
    void Set(double r, double i);
};//编译器自动生成默认构造函数

Complex c1;    //默认构造函数被调用
Complex *pc = new Complex;   //默认构造函数被调用

/*----------------------------------------------*/
//自己定义构造函数:
class Complex
{
private:
    double real, imag;
public:
    Complex(double r, double i = 0);
};

Complex::Complex(double r, double i)
{
    real = r;
    imag = i;
}

Complex c1;     //错误:缺少构造函数的参数
Complex *pc = new Complex;   //错误:没有参数
Complex c2(2);  //正确
Complex c3(2, 4), c4(3, 5);
Complex *pc = new Complex(3, 4);

可以有多个构造函数,参数个数或类型不同

class Complex
{
private:
    double real, image;
public:
    void Set(double r, double i);
    Complex(double r, double i);
    Complex(double r);
    Complex(Complex c1, Complex c2);
};

Complex::Complex(double r, double i)
{
    real = r;
    imag = i;
}

Complex::Complex(double r)
{
    real = r;
    imag = 0;
}

Complex::Complex(Complex c1, Complex c2)
{
    real = c1.real + c2.real;
    imag = c1.imag + c2.imag;
}

Complex c1(3), c2(1, 0), c3(c1, c2);
//c1 = {3, 0}, c2 = {1, 0}, c3 = {4, 0}

构造函数在数组中的使用

//示例1
class CSample
{
    int x;
public:
    CSample()
    {
        cout << "Constructor 1 called" << endl;
    }
    CSample(int n)
    {
        x = n;
        cout << "Constructor 2 called" << endl;
    }
};

int main()
{
    CSample array1[2];
    cout << "step1" << endl;
    CSample array2[2] = {4, 5};
    cout << "step2" << endl;
    CSample array3[2] = {3};
    cout << "step3" << endl;
    CSample *array4 = new CSample[2];
    delete []array4;

    return 0;
}
/*
输出:
Constructor 1 called
Constructor 1 called
step1
Constructor 2 called
Constructor 2 called
step2
Constructor 2 called
Constructor 1 called
step3
Constructor 1 called
Constructor 1 called
*/

/*-------------------------------------------*/
//示例2
class Test
{
public:
    Test(int n){}          // (1)
    Test(int n, int m){}   // (2)
    Test(){}               // (3)
};
Test array1[3] = {1, Test(1, 2)};              //三个元素分别用(1)、(2)、(3)来初始化
Test array2[3] = {Test(2, 3), Test(1, 2), 1};  //三个元素分别用(2)、(2)、(1)来初始化
Test *pArray[3] = {new Test(4), new Test(1, 2)};//只生成了两个对象,二个元素分别用(1)、(2)来初始化,pArray[2]是一个指针,没有初始化

复制构造函数

1、只有一个形参,即对同类对象的引用。
2、形如 X::X(X&)或者 X::X(const X&),后者能以常量对象作为参数。
3、如果没有定义复制构造函数,那么编译器生成默认复制构造函数。默认的复制构造函数完成复制功能。

class Complex
{
private:
    double real, imag;
};

Complex c1;      //调用缺省的无参构造函数。
Complex c2(c1);  //调用缺省的复制构造函数,将c2初始化成c1一样。

如果定义了自己的复制构造函数,则默认的复制构造函数不存在
不允许有形如 X::X(X) 的构造函数

class Complex
{
public:
    double real, imag;
    Complex(){}
    Complex(const Complex &c)
    {
        real = c.real;
        imag = c.imag;
        cout << "Copy Constructor called";
    }

    Complex(Complex c3){}   //错误:不允许这样的构造函数
};

Complex c1;
Complex c2(c1);

复制构造函数起作用的三种情况

1、用一个对象去初始化同类的另一个对象时
Complex c2(c1);
Complex c2 = c1;  //等价于上一条语句,属于初始化语句,非赋值语句
Complex c3, c4;
c3 = c4;          //属于赋值语句
2、如果某函数有一个参数是类A的对象,那么该函数被调用时,类A的复制构造函数将被调用
class A
{
public:
    A(){}
    A(A &a)
    {
        cout << "Copy Constructor called" << endl;
    }
};

void Func(A a1){}

int main()
{
    A a2;
    Func(a2);

    return 0;
} 
//输出:Copy Constructor called
3、如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调用
class A
{
public:
    int v;
    A(int n)
    {
        v = n;
    }
    A(const A&a)
    {
        v = a.v;
        cout << "Copy Constructor called" << endl;
    }
};

A Func()
{
    A b(4);
    retuen b;
}

int main()
{
    cout << Func().v << endl;

    return 0;
}

/*
输出:
Copy Constructor called
4
*/

注意:对象间赋值并不导致复制构造函数被调用

class CMyclass
{
public:
    int n;
    CMyclass() {}
    CMyclass(CMyclass &c)
    {
        n = 2 * c.n;
    }
};

int main()
{
    CMyclass c1, c2;
    c1.n = 5;
    c2 = c1;
    CMyclass c3(c1);
    cout << "c2.n = " << c2.n << ",";
    cout << "c3.n = " << c3.n << endl;

    return 0;
}
/*
输出:
c2.n = 5,c3.n = 10
*/

常量引用参数的使用

void fun(CMyclass obj_)
{
    cout << "fun" << endl;
}
/*
这样的函数,调用时生成形参会引发复制构造函数调用,开销比较大。
所以可以考虑使用CMyclass & 引用类型作为参数
如果希望确保实参的值在函数中不应被改变,那么可以加上const关键字:
*/
void fun(const CMyclass &obj) {}  //函数中任何试图改变obj值的语句都是非法的

类型转换构造函数

1、定义转换构造函数的目的是实现类型的自动转换。
2、只有一个参数,而且不是复制构造函数的构造函数,一般可以看作是转换构造函数。
3、当需要的时候,编译系统会自动调用转换构造函数,建立一个无名的临时对象(或临时变量)。

class Complex
{
public:
    double real, imag;
    Complex(int i)     //类型转换构造函数
    {
        cout << "IntConstructor called" << endl;
        real = i;
        imag = 0;
    }
    Complex(double r, double i)
    {
        real = r;
        imag = i;
    }
};

int main()
{
    Complex c1(7, 8);
    Complex c2 = 12;
    c1 = 9;         //9被自动转换成一个临时Complex对象. (1)
    cout << c1.real << "," << c1.imag << endl;
    cout << c2.real << "," << c2.imag << endl;

    return 0;
}
/*
输出:
IntConstructor called
IntConstructor called
9,0
12,0
--------------------------
如果删除(1)输出:
IntConstructor called
7,8
12,0
*/

析构函数

1、名字与类名相同,在前面加‘~’,没有参数和返回值,一个类最多只能有一个析构函数。
2、析构函数在对象消亡时自动被调用。可以定义析构函数在对象消亡前做善后工作,如释放分配的空间等。
3、如果定义类时没写析构函数,则编译器生成缺省析构函数。缺省析构函数什么也不做。
4、如果定义了析构函数,编译器就不会生成缺省的析构函数。

class String
{
private:
    char *p;
public:
    String()
    {
        p = new char[10];
    }
    ~String();
};

String::~String()
{
    delete []p;
}
//对象数组生命周期结束时,对象数组的每个元素的析构函数都会被调用
class Ctest
{
public:
    ~Ctest()
    {
        cout << "destructor called" << endl;
    }
};

int main()
{
    Ctest array[2];
    cout << "End Main" << endl;

    return 0;
}
/*
输出:
End Main
destructor called
destructor called
*/

析构函数和delete运算符

//delete运算符导致析构函数调用
Ctest *pTest;
pTest = new Ctest;  //构造函数调用
delete pTest;       //析构函数调用

/*------------------------------*/
pTest = new Ctest[3];    //构造函数调用3次
delete []pTest;          //析构函数调用3次
//若new一个对象数组,那么用delete释放时要写[],否则只delete一个对象(调用一次析构函数)。

析构函数在对象作为函数返回值返回后被调用

class CMyclass
{
public:
    ~CMyclass()
    {
        cout << "destructor" << endl;
    }
};

CMyclass obj;
CMyclass fun(CMyclass sobj)  //参数对象消亡也会导致析构函数被调用
{
    return sobj;       //函数调用返回时生成临时对象返回
}

int main()
{
    obj = fun(obj);  //函数调用的返回值(临时对象)被调用之后,该临时对象析构函数被调用。

    return 0;
}
/*
输出:
destructor 
destructor
destructor

第一次调用析构函数是fun函数形参消亡时,第二个是临时对象消亡时,第三个是程序结束时全局对象消亡时。
*/

构造函数和析构函数什么时候被调用

class Demo
{
    int id;
public:
    Demo(int i)
    {
        id = i;
        cout << "id = " << id << "constructed" << endl;
    }
    ~Demo()
    {
        cout << "id = " << id << "destructed" << endl;
    }
};

Demo d1(1);   //全局对象在main函数之前就初始化完成
void Func()
{
    static Demo d2(2);  //静态局部变量,函数结束不会消亡,直到程序结束才消亡
    Demo d3(3);
    cout << "func" << endl;
}

int main()
{
    Demo d4(4);
    d4 = 6;       //类型转换构造函数
    cout << "main" << endl;
    {
        Demo d5(5);
    }  //运行至此处d5消亡
    Func();
    cout << "main ends" << endl;

    return 0;
}
/*
输出:
id = 1 constructed
id = 4 constructed
id = 6 constructed
id = 6 destructed
main
id = 5 constructed
id = 5 destructed
id = 2 constructed
id = 3 constructed
func
id = 3 destructed
main ends
id = 6 destructed
id = 2 destructed
id = 1 destructed
*/

例题:

int main()
{
    A *p = new A[2];  
    A *p2 = new A;
    A a;          //1次
    delete []p;   //2次
}
/*
A是一个类名,上面类A调用析构函数3次。new出的空间,必须delete才能释放。
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值