12 - 类和对象

类和对象基本概念

1)类、对象、成员变量、成员函数

2)面向对象三大概念:封装、继承、多态

 

封装(Encapsulation)

封装是面向对象程序设计最基本的特性。把数据(属性)和函数(操作)合成一个整体。把客观的事物抽象成类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏(把属性和方法进行封装,对属性和方法进行访问控制)。

C++中类的封装:成员变量,C++中用于表示类属性的变量;成员函数,C++中用于表示类行为的函数。

在C++当中可以给成员变量和成员函数定义访问级别,Public修饰的成员变量和成员函数可以在类的内部和类的外部进行访问。Private修饰的成员变量和成员函数只能在类的内部被访问。

在使用struct定义类时,所有的成员的默认属性为public。在使用class定义类时,所有的成员默认的属性为private。我们常使用class定义类。

 

对象的构造和析构

创建一个对象时,经常需要某些初始化工作,为了解决这个问题,C++编译器提供了构造函数来处理对象的初始化。构造函数是一种特殊的成员函数,与其他的成员函数不同,不需要用户来调用它,而是在建立对象时自动执行。

 

构造函数和析构函数的概念

构造函数

在C++中的类可以定义与类名相同的特殊成员函数,这种与类名相同的成员函数被叫做构造函数。构造函数在定义时可以有参数,但是构造函数没有任何返回类型的声明。

构造函数一般情况下是C++编译器自动调用,但是在某些情况还是需要手动调用构造函数。

析构函数

C++中的类可以定义一个特殊的成员函数清理对象,这个特殊的成员函数叫做析构函数。析构函数没有参数也没有任何返回类型的声明,析构函数在对象销毁时自动被调用。

class hero
{
public:
    hero();    //构造函数
    ~hero();   //析构函数
};

设计构造函数和析构函数的原因

面向对象的思想是从生活中来的,生活中存在的对象都是被初始化之后才上市的。初始状态是对象普遍存在的一个状态。

那么可能有人会说,那我自己写好啦,不需要你这个啊。

首先,你所写的只是一个普通的函数,必须显式的调用,如果是定义一个对象数组,根本就没有机会进行显式初始化。而且一旦由于某种失误对象没有初始化,那么结果是不确定的。没有初始化的对象,其内部的成员变量的值时不定的。而且不能完全的解决问题。

 

构造函数的分类以及调用

//有参数构造函数的三种调用方法
class Test
{
private:
    int a;
    int b;

public:
    //无参数构造函数
    Test()
    {
        ;
    }
	
    //带参数的构造函数
    Test(int a, int b)
    {
        ;
    }

    //赋值构造函数
    Test(const Test &obj)
    {
        ;
    }

public:
    void init(int _a, int _b)
    {
        a = _a;
        b = _b;
    }
}

无参构造函数调用方法

Test t1,t2;

有参构造函数三种调用方法

//有参数构造函数的三种调用方法
class Test
{
private:
    int a;

public:
    //带参数的构造函数
    Test(int a)
    {
        printf("\na:%d", a);
    }

    Test(int a, int b)
    {
        printf("\na:%d b:%d", a, b);
    }
};

int main()
{
    Test t1(10);        //c++编译器默认调用有参构造函数 括号法 
    Test t2 = (20, 10); //c++编译器默认调用有参构造函数 等号法
    Test t3 = Test(30); //程序员手工调用构造函数 产生了一个对象 直接调用构造构造函数法

    system("pause");
    return 0;
}

拷贝构造函数调用的四种场景

/*
    第一个调用场景
*/
#include "iostream"
using namespace std;

class A
{
public:
    A() //无参构造函数 默认构造函数
    {
        cout << "我是构造函数,自动被调用了" << endl;
    }
    A(int _a) //无参构造函数 默认构造函数
    {
        a = _a;
    }
    A(const A& obj2)
    {
        cout << "我也是构造函数,我是通过另外一个对象obj2,来初始化我自己" << endl;
        a = obj2.a + 10;
    }
    ~A()
    {
        cout << "我是析构函数,自动被调用了" << endl;
    }
    void getA()
    {
        printf("a:%d \n", a);
    }

private:
    int a;
};

int main()
{
    A a1; //变量定义

    //赋值构造函数的第一个应用场景
    //用对象1 初始化 对象2 
    A a2 = a1; //定义变量并初始化 //初始化法

    a2 = a1; //用a1来=号给a2 编译器给我们提供的浅copy
}
/*
    第二个调用场景
*/
#include "iostream"
using namespace std;

class A
{
public:
    A() //无参构造函数 默认构造函数
    {
        cout << "我是构造函数,自动被调用了" << endl;
    }
    A(int _a) //无参构造函数 默认构造函数
    {
        a = _a;
    }
    A(const A& obj2)
    {
        cout << "我也是构造函数,我是通过另外一个对象obj2,来初始化我自己" << endl;
        a = obj2.a + 10;
    }
    ~A()
    {
        cout << "我是析构函数,自动被调用了" << endl;
    }
    void getA()
    {
        printf("a:%d \n", a);
    }

private:
    int a;
};

int main()
{
    A a1(10); //变量定义

    //赋值构造函数的第一个应用场景
    //用对象1 初始化 对象2 
    A a2(a1); //定义变量并初始化 //括号法

    //a2 = a1; //用a1来=号给a2 编译器给我们提供的浅copy
    a2.getA();
} 
//注意:初始化操作 和 等号操作 是两个不同的概念
/*
    第三个调用场景
*/
#include "iostream"
using namespace std;

class Location
{
public:
    Location(int xx = 0, int yy = 0)
    {
        X = xx;  Y = yy;  cout << "Constructor Object.\n";
    }

    Location(const Location& p) 	    //复制构造函数
    {
        X = p.X;  Y = p.Y;   cout << "Copy_constructor called." << endl;
    }

    ~Location()
    {
        cout << X << "," << Y << " Object destroyed." << endl;
    }

    int GetX() { return X; }
    int GetY() { return Y; }

private:   
    int X, Y;
};

void f(Location  p)
{
    cout << "Funtion:" << p.GetX() << "," << p.GetY() << endl;
}

void mainobjplay()
{
    Location A(1, 2);  //形参是一个元素,函数调用,会执行实参变量初始化形参变量
    f(A);
}

void main()
{
    mainobjplay();
    system("pause");
}
/*
    第四个调用场景
*/
#include "iostream"

using namespace std;

class Location 
{ 
public:
    Location( int x = 0 , int y = 0 ) 
    { 
        X = x ;  Y = y ;  cout << "Constructor Object.\n" ; 
    }

    Location( const Location & p ) 	    //复制构造函数
    { 
        X = p.X ;  Y = p.Y ;   cout << "Copy_constructor called." << endl ;  
    }

    ~Location() 
    { 
        cout << X << "," << Y << " Object destroyed." << endl ; 
    }

    int GetX () { return X ; }
    int GetY () { return Y ; }

private:
    int X , Y ;
} ;


void f ( Location  p )   
{ 
    cout << "Funtion:" << p.GetX() << "," << p.GetY() << endl ; 
}

Location g()
{
    Location A(1, 2);
    return A;
}

//对象初始化操作 和 =等号操作 是两个不同的概念
//匿名对象的去和留,关键看,返回时如何接
void mainobjplay()
{  
    //若返回的匿名对象,赋值给另外一个同类型的对象,那么匿名对象会被析构
    //Location B;
    //B = g();  //用匿名对象 赋值 给B对象,然后匿名对象析构

    //若返回的匿名对象,来初始化另外一个同类型的对象,那么匿名对象会直接转成新的对象
    Location B = g();
    cout<<"传智扫地僧测试"<<endl;
} 

void main()
{  
    mainobjplay();
    system("pause");
}

默认构造函数

当类中没有定义构造函数时,编译器会自动提供一个无参的构造函数,其函数体为空。

当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值复制。

构造函数调用规则

当类中没有定义任何一个构造函数时,C++编译器会自动提供默认的无参构造函数和默认拷贝构造函数。

当类中定义了拷贝构造函数时,C++编译器不会提供默认无参数构造函数。

当类中定义了任意的非拷贝构造函数(有参构造函数或者无参构造函数),C++编译器都不会提供默认无参构造函数。

默认拷贝构造函数是成员变量简单的赋值。

构造函数是C++中用于初始化对象状态的特殊函数,在对象创建时被自动调用,是对象正确初始化的重要保证。

构造函数和普通函数都遵循重载规则,必要的时候,必须手工编写拷贝构造函数。

 

深拷贝和浅拷贝

默认复制构造函数可以完成对象的数据成员值的简单复制。

对象的数据资源是由指针指向的堆,默认复制构造函数仅做指针值复制。

浅拷贝出现的原因

默认提供的浅拷贝是传递了一个指向的内存空间,即两个指针同时指向一个内存空间,那么这会产生一个问题,如果一个指针对内存空间进行了释放,那么另一个指针就成为了野指针。

浅拷贝的解决方法

所谓深拷贝,把内部的数据都传递给另一个对象。

这就像是,你看见我在吃糖,你也想吃,你就问我能不能把糖给你吃呢。浅拷贝就是咱俩吃这一块糖,一起吃,吃没了那就都没了。深拷贝就像是,我给你了一块糖,咱俩一人一块糖。你吃没了那就是你自己的没有了,我的还在,不存在相互影响。

需要注意的是如何进行深拷贝,我想给你一块糖,那你是不是要有地方放,换句话说,我给你好多好多糖,还有不同的各种品种,是不是需要对每种类型的糖进行划分,然后分别存放。

//拷贝构造函数
Array::Array(const Array& obj)
{
	this->m_length = obj.m_length;        //我有多少我就给你一样多的糖
	this->m_space = new int[m_length];    //我的糖需要这么大的地方放,那你肯定也是
	
	for (int i = 0; i < m_length; i++)    //我把糖一个个的装好了,然后给你。
	{
		this->m_space[i] = obj.m_space[i];
	}
}
//这是我写的一个数组类,因为内部只有这些数据成员,如果内部数据成员多的话,那么可以按照这个思路继续往下写。

 

多个对象构造和析构

对象初始化列表

如果我们有一个类成员,它本身是一个类或者结构,而且这个成员它只有一个带参数的构造函数而没有默认构造函数。这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数。

如果没有初始化列表,它将无法完成初始化,就会报错。

类成员当中如果有const修饰,必须在对象初始化时,对const进行初始化。

因为这两种对象要在声明之后马上初始化,而在构造函数当中,做的是对他们的赋值,这样是不被允许的。

//语法规则
Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)
{
    // some other assignment operation
}

成员变量的初始化顺序与声明的顺序有关,与在初始化列表中的顺序无关。初始化列表先于构造函数的函数体执行。

当类中有成员变量或者是其他类的对象时,首先调用成员变量的构造函数,调用顺序与声明顺序相同,之后调用自身了IDE构造函数。

析构函数的调用顺序与对应的构造函数调用顺序相反。

 

对象的动态建立和释放

new和delete基本语法

在软件开发过程当中,经常需要动态的分配和释放内存。在C语言中是利用库函数malloc和free来分配和释放空间。C++中提供了简便而功能更强的运算符new和delete对库函数malloc和free进行取代。

需要注意,new和delete是运算符,因此执行的效率更高。

虽然为了与C兼容,C++仍然保留malloc和free函数,但是并不建议是使用。因为在初始化对象时,new和delete会调用类对象的构造函数和析构函数。

new int;           //开辟一个存放整数的存储空间,返回一个指向该存储空间的地址(即指针)
new int(100);      //开辟一个存放整数的空间,并指定该整数的初值为100,返回一个指向该存储空间的地址
new char[10];      //开辟一个存放字符数组(包括10个元素)的空间,返回首元素的地址
new int[5][4];     //开辟一个存放二维整型数组(大小为5*4)的空间,返回首元素的地址

//开辟一个存放单精度数的空间,并指定该实数的初值为3.14159,将返回的该空间的地址赋给指针变量p
float *p=new float (3.14159);  

new分配数组空间时,不能指定初值。如果由于内存不足的原因而无法正常的分配空间,new会返回一个空指针NULL,用户可以根据该指针的值判断空间是否成功分配。

类对象的动态建立和释放

使用类名定义的对象都是静态的,在程序运行过程当中,对象所占的空间是不能随时释放的。但是有时人们希望在需要用到对象的时候才建立对象,在不需要对象时就撤销它,释放它所占用的内存空间以供别的数据使用。这样可以提高内存空间的利用效率。

C++当中,可以使用new运算符动态建立对象,用delete运算符撤销对象。

Box *pt;     //定义一个指向Box类对象的指针变量pt

pt = new Box;  //在pt中存放了新建对象的起始地址

//在程序中就可以通过pt访问这个新建的对象
cout << pt->height;     //输出该对象的height成员

cout << pt->volume( );  //调用该对象的volume函数,计算并输出体积

//C++还允许在执行new时,对新建立的对象进行初始化。
Box *pt = new Box(12,15,18);

//这种写法是把上面两个语句(定义指针变量和用new建立新对象)合并为一个语句,并指定初值。这样更精炼。 
//新对象中的height,width和length分别获得初值12,15,18。调用对象既可以通过对象名,也可以通过指针。

在执行new运算时,如果内存容量不足,无法开辟所需要的内存空间,目前大多数C++编译器系统都会让new返回一个0指针值。只要检测返回值是否为0,就可以判断内存是否开辟成功。

ANSI C++标准提出,在执行new出现故障时,就抛出一个异常,用户可以根据异常进行有关处理。但是C++标准仍然允许在出现new故障时返回0指针值。当前,不同的编译系统对new故障的处理方法是不同的。

在不再需要使用由new建立的对象时,可以用delete运算符予以释放。

delete pt; //释放pt指向的内存空间

这就撤销了pt指向的对象,此后程序不能再使用该对象。

如果用一个指针变量pt先后指向不同的动态对象,应该注意指针变量的当前指向,以免删错了对象。在执行delete运算符时,在释放内存空间之前,自动调用析构函数,完成有关的善后清理工作。

 

静态成员变量成员函数

静态成员变量

关键字static可以用于说明一个类的成员,静态成员提供了一个同类对象的共享机制。把一个类的成员说明为static时,这个类无论有多少个对象被创建,这些对象共享这个static成员。静态成员局部与类,它不是对象成员。

#include<iostream.h>

class  counter
{ 
public :
    counter (int a) { mem = a; }
    int mem;		//公有数据成员
    static  int  Smem ;	//公有静态数据成员
};

int  counter :: Smem = 1 ;	//初始值为1 使用静态成员时,需要在使用前进行定义

int main()
{   
    counter c(5);
    int i ;

    for( i = 0 ; i < 5 ; i ++ )
    { 
        counter::Smem += i ;
        cout << counter::Smem << '\t' ;  //访问静态成员变量方法2
    }

    cout<<endl;
    cout<<"c.Smem = "<<c.Smem<<endl; //访问静态成员变量方法1
    cout<<"c.mem = "<<c.mem<<endl;
}

静态成员函数

静态成员函数同样使用关键字static,静态成员提供不依赖于类数据结构的共同操作,它没有this指针。在类外调用静态成员函数需要使用“类名::”作限定词,或者通过对象调用。

 

C++面向对象模型初探

语言中直接支持面向对象程序设计的部分,主要涉及如构造函数、析构函数、虚函数、继承(单继承、多继承、虚继承)、多态等等。

在C语言当中,“数据”和“处理数据的操作(函数)”是分开来声明的,也就是说,语言本身并没有支持“数据和函数”之间的关联性。在C++当中,通过抽象数据类型,在类当中定义数据和函数,来实现数据和函数直接的绑定。

概括的来说,在C++类中有两种成员数据:static、nonstatic;三种成员函数:static、nonstatic、virtual。

C++中的Class从面向对象理论出发,将变量和函数集中定义在一起,用于描述现实世界中的类。从计算机的角度,程序依然由数据段和代码段构成。

C++类对象中的成员变量和成员函数是分开存储的。

普通成员变量:存储在对象当中,与struct变量有相同的内存布局和字节对齐方式。

静态成员变量:存储在全局数据区中。

成员函数:存储在代码段中。

C++中类的普通成员函数都隐式包含一个指向当前对象的this指针,这就是为什么很多对象共用一块代码,编译器还能区分代码是哪个具体对象的。

静态成员函数、成员变量属于类。静态成员函数和普通成员函数的区别就在于,静态函数不包含指向具体对象的指针,普通成员包含一个指向具体对象的指针。

this指针

全局函数 PK 成员函数

//把全局函数转化成成员函数,通过this指针隐藏左操作数
Test add(Test &t1, Test &t2) ===》Test add(Test &t2)

//把成员函数转换成全局函数,多了一个参数
void printAB() ===》void printAB(Test *pthis)

//函数返回元素和返回引用
Test& add(Test &t2) //*this //函数返回引用
{
    this->a = this->a + t2.getA();
    this->b = this->b + t2.getB();
    return *this; //*操作让this指针回到元素状态
} 

Test add2(Test &t2) //*this //函数返回元素
{
    //t3是局部变量
    Test t3(this->a+t2.getA(), this->b + t2.getB()) ;
    return t3;
}

 

友元

友元函数

友元函数使用friend进行修饰,说明语句位置与访问描述无关。友元函数可以通过对象参数访问私有数据成员。友元函数大部分都被使用在运算符重载。

 

友元类

如果B类是A类的友元类,则B类的所有成员函数都是A类的友元函数。友元类通常设计为一种数据操作或者类之间传递消息的辅助类。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值