类的引入
C++在C的基础上,对结构体进行了扩充,结构体中不仅可以定义变量,还可以定义函数,即成为C++中的类,与此同时,还兼容C中struct的用法。
#include <iostream>
//C中原来的库函数在C++中仍然可以使用,在不加.h的情况下可以在前面加c
//表示是原来C中的库函数
#include <cassert>
using namespace std;
//类的定义时一般成员函数的声明放在类里,实现放在外面,这里全部放里面
struct Stack
{
int* m_a;
int m_top;
int m_capacity;
//构造函数(带默认参数)
Stack(int capacity=4)
{
//成员函数中对于成员变量和成员函数的调用
m_a = (int*)malloc(sizeof(int) * capacity);
if (m_a == nullptr)//C++中用nullptr代替NULL
{
perror("malloc fail");
exit(-1);
}
m_top = 0;
m_capacity = capacity;
}
//析构函数
~Stack()
{
free(m_a);
m_a = nullptr;
m_top = 0;
m_capacity = 0;
}
void Extend()
{
assert(m_a);
int* tmp = (int*)realloc(m_a, sizeof(int) * m_capacity * 2);
if (tmp == nullptr)
{
perror("realloc fail");
exit(-1);
}
m_a = tmp;
m_capacity *= 2;
}
bool Empty()
{
assert(m_a);
if (m_top <= 0)
{
return true;
}
else
{
return false;
}
}
void Push(int x)
{
assert(m_a);
if (m_top >= m_capacity)
{
Extend();
}
m_a[m_top++] = x;
}
void Pop()
{
assert(m_a);
if (Empty())
{
cout << "stack is empty!" << endl;
return;
}
m_top--;
}
int Top()
{
assert(m_a);
if (Empty())
{
cout << "stack is empty!" << endl;
exit(-1);
}
return m_a[--m_top];
}
};
int main()
{
Stack st;
st.Push(1);
st.Push(2);
st.Push(3);
st.Push(4);
st.Push(5);
cout << st.Top() << endl;
st.Pop();
return 0;
}
类的定义
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分 号不能省略。
类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者
成员函数。
类的两种定义方式:
1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
2. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::
类中成员变量的命名:习惯在前面加_,或者加m_或者m表示member。
访问限定符
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选 择性的将其接口提供给外部的用户使用。
【访问限定符说明】
1. public修饰的成员在类外可以直接被访问。
2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)。
3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。
4. 如果后面没有访问限定符,作用域就到 }即类结束。
5. class的默认访问权限为private,struct为public(因为struct要兼容C)。
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。
#pragma once
#include <iostream>
#include <cassert>
using namespace std;
class Stack
{
private:
int* m_a;
int m_top;
int m_capacity;
public:
Stack(int capacity = 4);
~Stack();
void Extend();
bool Empty();
void Push(int x);
void Pop();
int Top();
};
#include "test.h"
Stack::Stack(int capacity)
{
m_a = (int*)malloc(sizeof(int) * capacity);
if (m_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
m_top = 0;
m_capacity = capacity;
}
Stack::~Stack()
{
free(m_a);
m_a = nullptr;
m_top = 0;
m_capacity = 0;
}
void Stack::Extend()
{
assert(m_a);
int* tmp = (int*)realloc(m_a, sizeof(int) * m_capacity * 2);
if (tmp == nullptr)
{
perror("realloc fail");
exit(-1);
}
m_a = tmp;
m_capacity *= 2;
}
bool Stack::Empty()
{
assert(m_a);
if (m_top <= 0)
{
return true;
}
else
{
return false;
}
}
void Stack::Push(int x)
{
assert(m_a);
if (m_top >= m_capacity)
{
Extend();
}
m_a[m_top++] = x;
}
void Stack::Pop()
{
assert(m_a);
if (Empty())
{
cout << "stack is empty!" << endl;
return;
}
m_top--;
}
int Stack::Top()
{
assert(m_a);
if (Empty())
{
cout << "stack is empty!" << endl;
exit(-1);
}
return m_a[--m_top];
}
#include "test.h"
int main()
{
Stack st;
st.Push(1);
st.Push(2);
st.Push(3);
st.Push(4);
st.Push(5);
cout << st.Top() << endl;
st.Pop();
return 0;
}
成员变量和成员函数的顺序是任意的,甚至可以交错定义。
成员变量和成员函数都在类域中不需要先定义或者声明,后使用,编译器把类看作一个整体,会扫描整个类。
类似于Stack::m_top=0这样的语句是错误的,即便m_top是公有的,因为Stack只是类型,相当于图纸,并没有实体,也就没有空间存放数据。只有将类实例化,才能往里面存数据。
面向对象三大特性之一:封装
类是一种比较明显的封装行为,封装是一种管理。
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来 和对象进行交互。
在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来 隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。
类的实例化
class Date
{
public:
int _year;
int _month;
int _day;
void Show()
{
cout << "good day" << endl;
}
};
int main()
{
//Date::_day = 31;//_day只是声明,类只是模板图纸,不能存储变量的值
Date::Show();// error C2352: “Date::Show”: 调用非静态成员函数需要一个对象
//成员函数被调用时,会被传入this指针,即类的实例的地址,而此处没有
//实例化,也就没有this指针可用,所以报错。
return 0;
}
类对象的存储模型
对象中只存储成员变量,成员函数存放在公共代码段。
所以,对象或者类的大小的计算就只考虑成员变量,同时考虑结构体对齐。
对于空类或者只有成员函数的类,类的大小是1,实例化时分配一个字节用来占位,标识对象的存在,使得地址可取。
this指针
class Date
{
private:
int _year;
int _month;
int _day;
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Show()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
};
int main()
{
Date d1, d2;
d1.Init(2022, 9, 25);
d2.Init(1998, 9, 1);
d1.Show();
d2.Show();
return 0;
}
d1和d2调用的是在代码段里面同一个Show函数,为什么会得到不同的结果:
原因在于C++在这里调用Show函数的时候,传入了一个隐藏的this指针,这个指针就是对应的对象的地址,即d1或者d2的地址,Show函数相当于这样被调用:
Show(Date* this); Show(&d1) Show(&d2)
注意:this指针不能显式地作为形参定义,也不能显式地作为实参传递,这些都是编译器的活 。但是,可以在成员函数中显式地使用。
this指针的特性
1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
2. 只能在“成员函数”的内部使用
3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给 this形参。所以对象中不存储this指针。
4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传
递,不需要用户传递
【面试题】
1. this指针存在哪里?
2. this指针可以为空吗?
this指针是定义在成员函数的形参里接受外面传来的实参,所以this指针一般存在成员函数栈帧里。
this指针可以为空,只要不发生this指针的解引用,就不会运行错误。