C++类和对象 (一)、类的初步认识

一、面向过程编程和面向对象编程认识

C语言是面对过程编程的,顾名思义,是因为在C语言的编程中主要关注的是过程,
C++是面对对象编程,在C++的编程中主要关注的是对象,

举一个例子就是:怎么将大象装入冰箱?
对于C语言,
则是分三步:一、打开冰箱门->二.将大象装入冰箱->三.关上冰箱门

对于C++
则是:打开冰箱门,将大象装入冰箱,关上冰箱门。

综上,可以看出在C语言中我们并不关心执行对象是谁,只需要知道解决问题的过程并一步步执行就行。而对于C++,我们是将一件事拆分为不同对象之间的交互

二、类的引入

在C语言中,结构体只能定义变量,不能定义函数。
而C++的结构体可以定义变量与函数。
由此,我们可以将一些数据结构定义为一个结构体。
例:下面是一个栈的结构体的C++代码,其中含有变量的定义、初始化函数、访问栈顶数据函数与入栈、出栈函数。
Stack_struct.h:

#include <stdlib.h>
#include <iostream>
using namespace std;
struct Stack
{
	int* _data;
	size_t _Long;
	size_t _capacity;
	void Initialize()//初始化
	{
		_data = (int*)malloc(sizeof(int) * size);
        if(nullptr == _data)
        {
            cout << "malloc failed" << endl;
            exit(-1);
        }
		_Long = 0;
		_capacity = 0;
	}
	void reserve()//扩容
    {
        _capacity == 0 ? _capacity = 4 : _capacity *= 2;
        int* newdata = (int*)realloc(_data, sizeof(int) * _capacity);
        if(newdata == nullptr)
        {
            cout << "realloc failed" << endl;
            exit(-1);
        }
        _data = newdata;
    }
	void push(int data)//入栈
	{
		if(_Long == _capacity)
		{
			reserve();
		}
		_data[_Long++] = data;
	}
	void pop()//出栈
	{
		if(_Long == 0)
		{                       
			cout << "false" << endl;
			exit(-1);
		}
		cout << _data[--_Long] << endl;
	}
	int Top()//栈顶元素
	{
		return _data[_Long - 1];
	}
	void Destroyed()//销毁栈
	{
		free(_data);
		_Long = 0;
		_capacity = 0;
	}
};

可以看到,在C++中调用结构体中成员函数与调用结构体中的成员变量一样,都需要指明结构体变量。

而对于C语言,则只能通过向函数中传递结构体指针来实现以上功能。

三、类的定义

在C++中,class为定义类的关键词。如下:

class classname
{
    类体:主要由成员变量和成员函数组成。
};//此处与struct一样,需要带上;

类有两种定义方式:

1. 声明与定义都放在类体中

用 class 实现一个栈
Stack_class_1.h:

#include <stdlib.h>
#include <iostream>
using namespace std;
class Stack
{
	int* _data;
	size_t _Long;
	size_t _capacity;
	void Initialize()//初始化
	{
        int* newdata = (int*)realloc(_data, sizeof(int) * _capacity);
        if(newdata == nullptr)
        {
            cout << "malloc failed" << endl;
            exit(-1);
        }
		_Long = 0;
		_capacity = 0;
	}
	void reserve()//扩容
    {
        _capacity == 0 ? _capacity = 4 : _capacity *= 2;
        int* newdata = (int*)realloc(_data, sizeof(int) * _capacity);
        if(newdata == nullptr)
        {
            cout << "realloc failed" << endl;
            exit(-1);
        }
        _data = newdata;
    }
	void push(int data)//入栈
	{
		if(_Long == _capacity)
		{
			reserve();
		}
		_data[_Long++] = data;
	}
	void pop()//出栈
	{
		if(_Long == 0)
		{                       
			cout << "false" << endl;
			exit(-1);
		}
		cout << _data[--_Long] << endl;
	}
	int Top()//栈顶元素
	{
		return _data[_Long - 1];
	}
	void Destroyed()//销毁栈
	{
		free(_data);
		_Long = 0;
		_capacity = 0;
	}
};

2. 将定义与声明函数拆开,分别放在.h和.cpp文件中

如下:
Stack_class_2.h:

#include <stdlib.h>
#include <iostream>
using namespace std;
class Stack
{
    int* _data;
    size_t  _capacity;
    size_t  _Long;
	void Initialize();
	
	void reserve();
	
	void push(int data);
	
	void pop();
	
	int Top();

	~void Destroyed();
};

Stack_class.cpp:

#include "Stack_class_2.h"

void Stack::Initialize()//初始化
{
	int* newdata = (int*)realloc(_data, sizeof(int) * _capacity);
        if(newdata == nullptr)
        {
            cout << "malloc failed" << endl;
            exit(-1);
        }
	_Long = 0;
	_capacity = 0;
}

void reserve()//扩容
{
	_capacity == 0 ? _capacity = 4 : _capacity *= 2;
	int* newdata = (int*)realloc(_data, sizeof(int) * _capacity);
	if(newdata == nullptr)
	{
		cout << "realloc failed" << endl;
		exit(-1);
	}
	_data = newdata;
}

void Stack::push(int data)//入栈
{
	if(_Long == _capacity)
	{
		reserve();
	}
	_data[_Long++] = data;
}

void Stack::pop()//出栈
{
	if(_Long == 0)
	{                       
		cout << "false" << endl;
		exit(-1);
	}
		cout << _data[--_Long] << endl;
}

int Stack::Top()//栈顶元素
{
	return _data[_Long - 1];
}
void Stack::Destroyed()//销毁栈
{
	free(_data);
	_Long = 0;
	_capacity = 0;
}

要注意,声明在类内,定义在类外的函数,定义时需要使用 ( :: ) 域操作符,并且要先写函数类型。

四、类的实例化

什么是类的实例化?
我们创建一个类的对象,就是类的实例化。
如下:

#include "Stack_class_1.h"

int main ()
{
    Stack st0;//st0就是实例化的对象
    
    st0.Initialize();
    st0.Destroyed();
    return 0;
}

如果我们没有实例化,直接给类里面的对象赋值呢?

#include "Stack_class_1.h"
int main ()
{
	Stack _Long = 1;
	cout << _Long << endl;
	return 0;
}

或者

#include "Stack_class_1.h"
int main ()
{
	Stack._Long = 1;
	cout << _Long << endl;
	return 0;
}

构建失败
构建失败

可以看到,前者编译器是将 _Long 当作了实例化的对象,并不是类内的成员变量,这里的赋值无法实现,

后者第一段报错大意为:在某个地方出现了未预期的标识符(unqualified-id),第二段为:当前的作用域中没有声明名为 “_Long” 的变量、函数或类。
可以看到,没有实例化的对象,类内是无法存储数据的,只有实例化的对象能存储数据。

五、访问限定符

面对对象编程的特性之一:封装。
而C++实现封装的办法这是引入访问限定符,通过访问权限的选择性将其提供接口给外部用户。

访问限定符

说明:

  1. public:修饰的成员可以在类外被直接访问。
  2. protected:和 private: 修饰的成员不能在类外直接访问,两者效果类似。
  3. 访问限定符的作用范围为:在类中,从一个访问限定符出现的位置开始,到第二个访问限定符出现结束,以此类推,最后的访问限定符到类结束为止。
  4. class的默认访问权限为 private ,而 struct (是的,struct支持使用访问限定符) 默认为 public (struct要兼容C语言)。

**注意:**访问限定符只在编译时有效,当数据映射到内存后,没有任何权限区别。

理由

那么为什么一定要使用访问限定符呢?
原因:不让外部成员随便访问我们类内数据。

举例:现在,我们需要知道栈顶元素是什么?

对于在上文中用 struct 实现的栈,可以有不同的方式:

#include "Stack_struct.h"

int main ()
{
    Stack st0;
    st0.Initialize();
    st0.push(1);
	cout << st0.Top() << endl;
	st0.Destroyed();
	return 0;
}

但由于 struct 的成员默认为 public ,并且我们也没有添加访问限定符,所以也可以这样

#include "Stack_struct.h"

int main ()
{
    Stack st0;
    st0.Initialize();
    st0.push(1);
    cout << st0._data[st0._Long - 1] << endl;
    st0.Destroyed();
    return 0;
}

但一旦有人写成这样

#include "Stack_struct.h"

int main ()
{
    Stack st0;
    st0.Initialize();
    st0.push(1);
    cout << st0._data[st0._Long] << endl;
    st0.Destroyed();
    return 0;
}

在运行时就会报错,这是因为我们的_Long变量一直代表的是栈内有多少元素,所以这样访问会访问到栈顶元素的后一位,由于其并没有数据,所以会输出随机值。

输出随机值

而 class 实现的栈需要访问栈顶元素,该怎么做呢?
以下是一种方式

#include "Stack_class_1.h"

int main ()
{
    Stack st0;
    st0.Initialize();
    st0.push(1);
    cout << st0.Top() << endl;
    st0.Destroyed();
    return 0;
}

这时,问题来了,编译器会如下报错
'Initialize' is a private member of 'Stack'
'push' is a private member of 'Stack'
'Top' is a private member of 'Stack'
'Destroyed' is a private member of 'Stack'

并且,运行时构建失败
运行时构建失败

错误的原因在上文有写,class的默认访问权限为 private,此时我们的成员变量与成员函数都为私有,在类外无法直接访问。
所以需要添加 public 限定符。(由此又可见得,上文实例化中,就算允许类没有实例化也可以存储数据,在编译中也会报这里相同的错)

Stack_class_1.h:

#include <stdlib.h>
#include <iostream>
using namespace std;
class Stack
{
public:
    int* _data;
    size_t  _capacity;
    size_t  _Long;
	void Initialize()//初始化
	{
        _data = (int*)malloc(sizeof(int) * size);
        if(nullptr == _data)
        {
            cout << "malloc failed" << endl;
            exit(-1);
        }
		_Long = 0;
		_capacity = 0;
	}
	void reserve()//扩容
	{
		_capacity == 0 ? _capacity = 4 : _capacity *= 2;
		int* newdata = (int*)realloc(_data, sizeof(int) * _capacity);
		if(newdata == nullptr)
		{
			cout << "realloc failed" << endl;
			exit(-1);
		}
		_data = newdata;
	}
	void push(int data)//入栈
	{
		if(_Long == _capacity)
		{
			reserve();
		}
		_data[_Long++] = data;
	}
	void pop()//出栈
	{
		if(_Long == 0)
		{                       
			cout << "false" << endl;
			exit(-1);
		}
		cout << _data[--_Long] << endl;
	}
	int Top()//栈顶元素
	{
		return _data[_Long - 1];
	}
	void Destroyed()//销毁栈
	{
		free(_data);
		_Long = 0;
		_capacity = 0;
	}
};

Stack_class_2.h:

#include <stdlib.h>
#include <iostream>
using namespace std;
class Stack
{
public:
    int* _data;
    size_t  _capacity;
    size_t  _Long;
	
	void Initialize();
	
	void reserve();
	
	void push(int data);
	
	void del();
	
	int Top();

	~void Destroyed();
};

Stack_class.cpp:

#include "Stack_class_2.h"

void Stack::Initialize()//初始化
{
	_data = (int*)malloc(sizeof(int) * size);
	if(nullptr == _data)
	{
		cout << "malloc failed" << endl;
		exit(-1);
	}
	_Long = 0;
	_capacity = 0;
}

void reserve()//扩容
{
	_capacity == 0 ? _capacity = 4 : _capacity *= 2;
	int* newdata = (int*)realloc(_data, sizeof(int) * _capacity);
	if(newdata == nullptr)
	{
		cout << "realloc failed" << endl;
		exit(-1);
	}
	_data = newdata;
}

void Stack::push(int data)//入栈
{
	if(_Long == _capacity)
	{
		reserve();
	}
	_data[_Long++] = data;
}

void Stack::pop()//出栈
{
	if(_Long == 0)
	{                       
		cout << "false" << endl;
		exit(-1);
	}
		cout << _data[--_Long] << endl;
}

int Stack::Top()//栈顶元素
{
	return _data[_Long - 1];
}
void Stack::Destroyed()//销毁栈
{
	free(_data);
	_Long = 0;
	_capacity = 0;
}

重新运行代码,能得到正常输出
正常输出
但,我们此时也可以像用 struck 实现的栈来访问栈顶元素的第二种方式一样来访问栈顶元素,接下来,也会遇到相同的问题。

解决方法:我们可以将 public 访问限定符移动到成员函数前,对成员变量前添加 private 访问限定符。
如下:
Stack_class_1.h:

#include <stdlib.h>
#include <iostream>
using namespace std;
class Stack
{
private:
    int* _data;
    size_t  _capacity;
    size_t  _Long;
public:
	void Initialize()//初始化
	{
		_data = (int*)malloc(sizeof(int) * size);
        if(nullptr == _data)
        {
            cout << "malloc failed" << endl;
            exit(-1);
        }
		_Long = 0;
		_capacity = 0;
	}
	void reserve()//扩容
	{
		_capacity == 0 ? _capacity = 4 : _capacity *= 2;
		int* newdata = (int*)realloc(_data, sizeof(int) * _capacity);
		if(newdata == nullptr)
		{
			cout << "realloc failed" << endl;
			exit(-1);
		}
		_data = newdata;
	}
	void push(int data)//入栈
	{
		if(_Long == _capacity)
		{
			reserve();
		}
		_data[_Long++] = data;
	}
	void pop()//出栈
	{
		if(_Long == 0)
		{                       
			cout << "false" << endl;
			exit(-1);
		}
		cout << _data[--_Long] << endl;
	}
	int Top()//栈顶元素
	{
		return _data[_Long - 1];
	}
	void Destroyed()//销毁栈
	{
		free(_data);
		_Long = 0;
		_capacity = 0;
	}
};

Stack_class_2.h:

#include <stdlib.h>
#include <iostream>
using namespace std;
class Stack
{
private:
    int* _data;
    size_t  _capacity;
    size_t  _Long;
public:
	void Initialize();
	
	void reserve();
	
	void push(int data);
	
	void pop();
	
	int Top();

	void Destroyed();
};

Stack_class.cpp:

#include "Stack_class_2.h"

void Stack::Initialize()//初始化
{
	_data = (int*)malloc(sizeof(int) * size);
        if(nullptr == _data)
        {
            cout << "malloc failed" << endl;
            exit(-1);
        }
	_Long = 0;
	_capacity = 0;
}

void reserve()//扩容
{
	_capacity == 0 ? _capacity = 4 : _capacity *= 2;
	int* newdata = (int*)realloc(_data, sizeof(int) * _capacity);
	if(newdata == nullptr)
	{
		cout << "realloc failed" << endl;
		exit(-1);
	}
	_data = newdata;
}

void Stack::push(int data)//入栈
{
	if(_Long == _capacity)
	{
		reserve();
	}
	_data[_Long++] = data;
}

void Stack::pop()//出栈
{
	if(_Long == 0)
	{                       
		cout << "false" << endl;
		exit(-1);
	}
		cout << _data[--_Long] << endl;
}

int Stack::Top()//栈顶元素
{
	return _data[_Long - 1];
}
void Stack::Destroyed()//销毁栈
{
	free(_data);
	_Long = 0;
	_capacity = 0;
}

对于 stuck 实现的栈也是一样:
Stack_struct.h:

#include <iostream>
using namespace std;
struct Stack
{
private:
    int* _data;
    size_t  _capacity;
    size_t  _Long;
public:
	void Initialize()//初始化
	{
		_data = (int*)malloc(sizeof(int) * size);
        if(nullptr == _data)
        {
            cout << "malloc failed" << endl;
            exit(-1);
        }
		_Long = 0;
		_capacity = 0;
	}
	void reserve()//扩容
	{
		_capacity == 0 ? _capacity = 4 : _capacity *= 2;
		int* newdata = (int*)realloc(_data, sizeof(int) * _capacity);
		if(newdata == nullptr)
		{
			cout << "realloc failed" << endl;
			exit(-1);
		}
		_data = newdata;
	}
	void push(int data)//入栈
	{
		if(_Long == _capacity)
		{
			reserve();
		}
		_data[_Long++] = data;
	}
	void pop()//出栈
	{
		if(_Long == 0)
		{                       
			cout << "false" << endl;
			exit(-1);
		}
		cout << _data[--_Long] << endl;
	}
	int Top()//栈顶元素
	{
		return _data[_Long - 1];
	}
	void Destroyed()//销毁栈
	{
		free(_data);
		_Long = 0;
		_capacity = 0;
	}
};

此时,我们只可以用 Top 函数来访问栈顶元素。

但问题又来了:为什么 Top 函数能访问 private 限定的成员变量呢?
很简单,因为类里面的函数不受访问限定符的限制 (大概是定义)

tips:
一个小细节,
我创建的所有成员变量,前面都添加了 “_” ,目的是防止一个错误。
我们创建时栈并没有遇到,所有,创建一个新的类来演示:

#include <iostream>
using namespace std;

class number
{
private:
    int num;
public:
    void Init(int num)
    {
        num = num;
    }
    void print()
    {
        cout << num << endl;
    }
};

int main()
{
    number Num;
    Num.Init(2321);
    Num.print();
    return 0;
}

我们的预期输出为 2321,
但实际运行并不是如此
输出不符
这是因为在成员函数 Init 中,而我们希望的是给 Num 这个实例化对象的 num 赋值为传入的局部变量的 num,但编译器会遵守局部变量优先
这里在成员函数 Init 中,进行的是局部变量自己给自己赋值,而不是给 Num 这个实例化对象的 num 赋值,所以会输出随机值。

因此,我会在成员变量前添加 “_” 来区分变量。
当然,这并不是固定写法,所以你可以在你的成员变量里面的任意位置添加,也可以不添加 (前提是你的初始化函数中传入的局部变量与成员变量不重名)。

六、类的大小

类的大小的计算方式就是将类内的所有成员变量的大小加起来,并不会计算成员函数的大小。

#include "Stack_class_1.h"
int main ()
{
    Stack st0;
    st0.Initialize();
    st0.push(1);
    cout << sizeof(st0) << endl;
    cout << sizeof(Stack) << endl;
    st0.Destroyed();
    return 0;
}

类的大小

七、this 指针

这里,可以接上访问限定符里的 tips
如果我们一定需要传入的局部变量与成员变量重名,该怎么改动代码才能使代码可运行呢?
有如下解决方法

#include <iostream>
using namespace std;

class number
{
private:
    int num;
public:
    void Init(int num)
    {
        this->num = num;
    }
    void print()
    {
        cout << num << endl;
    }
};

int main()
{
    number Num;
    Num.Init(2321);
    Num.print();
    return 0;
}

很显然,我们添加了一个 this 指针在一个 num 变量前,这样,这个添加了 this 指针的 num 变量就代表了我们类内成员变量。
我们就能得到预期的输入值:
正确输出
但是,为什么可以这样做呢?
我们观察代码可以发现,我们的 InIt 函数中,传入的参数就只有 num :一个 int 类型的变量,并没有传入 this 指针。

这里,是因为编译器在成员函数中添加了一个传入参数:this,
我们在这里看不见这个传入参数是由于编译器将其隐藏了。
实际上我们的函数在编译器眼中,我们代码应该是这样的:

#include <iostream>
using namespace std;

class number
{
private:
    int num;
public:
    void Init(number* this, int num)
    {
        this->num = num;
    }
    void print(number* this)
    {
        cout << this->num << endl;
    }
};

int main()
{
    number Num;
    Num.Init(&Num, 2321);
    Num.print(&Num);
    return 0;
}

tips:
现在,我们可以定义多个实例化对象来观察调用的函数:

#include <iostream>
using namespace std;

class number
{
private:
    int num;
public:
    void Init(int num)
    {
        this->num = num;
    }
    void print()
    {
        cout << num << endl;
    }
};

int main()
{
    number Num;
    number Num1;
    Num1.Init(2001);
    Num1.print();
    Num.Init(2321);
    Num.print();
    return 0;
}

运行成功

这里通过反汇编可以看到:
反汇编
这里的 Init 函数与 print 函数分别调用了两次,代表的是 Num 与 Num1 这两个实例化对象分别各调用了两次Init 函数与两次 print,我们发现,很明显,这两个函数的地址并未因不同的实例化对象调用改变,所以它们调用的函数也没有改变。

它们每次调用函数的区分就是通过 this 指针实现的,并且如果每个实例化对象内都有成员函数,会造成不必要的空间浪费,因为成员函数对每个实例化对象的操作是一样的,所以只要将类内成员函数放在公共区域,让实例化对象调用就行。
这就是类的大小就是成员变量大小的原因。

补充

运行以下代码

#include <iostream>
using namespace std;

class number
{
private:
    int num;
public:
    void Init(int num)
    {
        this->num = num;
    }
    void print()
    {
        cout << "Hello World" << endl;
    }
};

int main()
{
    number* sad;
    sad = nullptr;
    sad->Init(1);
    return 0;
}

崩溃
可以看到代码崩溃了,因为传入的 this 指针为空指针,而空指针查找成员变量就会崩溃。

但只要不查找成员

#include <iostream>
using namespace std;

class number
{
private:
    int num;
public:
    void Init(int num)
    {
        this->num = num;
    }
    void print()
    {
        cout << "Hello World" << endl;
    }
};

int main()
{
    number* sad;
    sad = nullptr;
    sad->print();
    return 0;
}

正常运行
可以看到正常输出、运行。
当然,将空指针解引用也不会报错:

#include <iostream>
using namespace std;

class number
{
private:
    int num;
public:
    void Init(int num)
    {
        this->num = num;
    }
    void print()
    {
        cout << "Hello World" << endl;
    }
};

int main()
{
    number* sad;
    sad = nullptr;
    (*sad).print();
    return 0;
}

正常运行

如果将空指针解引用,查找成员变量:

#include <iostream>
using namespace std;

class number
{
private:
    int num;
public:
    void Init(int num)
    {
        this->num = num;
    }
    void print()
    {
        cout << "Hello World" << endl;
    }
};

int main()
{
    number* sad;
    sad = nullptr;
    (*sad).Init(1);
    return 0;
}

崩溃
同样崩溃。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值