一、面向过程编程和面向对象编程认识
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++实现封装的办法这是引入访问限定符,通过访问权限的选择性将其提供接口给外部用户。
说明:
- public:修饰的成员可以在类外被直接访问。
- protected:和 private: 修饰的成员不能在类外直接访问,两者效果类似。
- 访问限定符的作用范围为:在类中,从一个访问限定符出现的位置开始,到第二个访问限定符出现结束,以此类推,最后的访问限定符到类结束为止。
- 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;
}
这时,问题来了,编译器会如下报错
并且,运行时构建失败
错误的原因在上文有写,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;
}
同样崩溃。