目录
面向对象的编程
类的引入
简介
类的定义
简介
访问限定符
命名规则
封装
简介
类的作用域
类的大小及存储模型
this指针
简介
面向对象的编程
C++与C语言不同,C++是面向对象的编程,那么什么是面向对象的编程呢?
C语言编程,规定了编程的每一步指令,程序从上而下一步一步按照指令,最终达到想要的结果,而面向对象是另一种思路,将一件事情拆分成不同的对象,任务需要依靠对象之间的交互完成,也就是说关注模块和模块之间的关系。
类的引入
简介
类是C++中重要的概念,从C语言的结构体升级而来,C语言的结构体只能定义变量,C++中结构体不仅可以定义变量还能定义函数(成员变量/成员属性,成员函数/成员方法)
typedef int DataType;
struct Stack
{
void Init(size_t capacity)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(const DataType& data)
{
// 扩容
_array[_size] = data;
++_size;
}
DataType Top()
{
return _array[_size - 1];
}
void Destroy()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
DataType* _array;
size_t _capacity;
size_t _size;
};
int main()
{
Stack s;
s.Init(10);
s.Push(1);
s.Push(2);
s.Push(3);
cout << s.Top() << endl;
s.Destroy();
return 0;
}
类的定义
C++中用class作为关键字定义类,其结构如下:
class classname
{
//类体
//成员变量(成员属性)
//成员函数(成员方法)
};
访问限定符
C++中设置了访问限定符,其作用是设置类体的属性,访问限定符作用域从当前限定符开始直到下个访问限定符出现结束,如果没有访问限定符则直到类结束;
1、public 修饰的类体,可以直接被外部访问;
2、protected(保护)与private(私有)修饰的类体有同样的特征不允许被外部访问;
3、struct与class在默认的访问限定符不同,struct为了兼容c语言,默认的是public,class默认的是private。
命名规则
先看一下下面代码
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 print()
{
printf("%d-%d-%d\n", year, month, day);
}
};
void init(int year, int month, int day)
{
year = year;
month = month;
day = day;
}
这段代码阅读起来很不方便,形参与类中的成员变量无法区别,为了更好的阅读,在成员变量命名时可以加以区分;
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 print()
{
printf("%d-%d-%d\n", _year, _month, _day);
}
};
封装
简介
C++中存在多种特性,面向对象的最主要的是封装,继承,多态;
类和对象的阶段,主要是封装的特性,那么什么是封装呢?
简单的说,封装是一种管理行为,将程序的属性与方法结合在一起,隐藏对象的属性和细节,仅保留对外接口和对象进行交互,封装的特性在C++的类中体现的很明显;
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 print()
{
printf("%d-%d-%d\n", year, month, day);
}
};
int main()
{
Date s;
s.print();
system("pause");
return 0;
}
上面的代码中,成员变量无法通过外部进行修改,只能通过init函数进行修改;
类的大小及存储模型
先看下面的代码:
class Date
{
public:
int _year;
int _month;
int _day;
void init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
printf("%d-%d-%d\n", _year, _month, _day);
}
};
int main()
{
Date s;
printf("类的大小:%d\n", sizeof(s));
system("pause");
return 0;
}
上面这段代码的结果为12。为什么是12而不是20(包含两个函数的指针),这由类的存储模型决定的。
类的存储模型
内存通常分为栈区,堆区,静态区,常量区等,(栈区的空间非常小)为了节省空间,编译器会将成员函数放在常量区(代码段)中,使用时寻找函数。
this指针
先看下面的代码及运行结果
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year=1, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
printf("%d-%d-%d\n", _year, _month, _day);
}
};
int main()
{
Date d1(2023,5,7);
Date d2(2024, 6, 8);
d1.print();
d2.print();
system("pause");
return 0;
}
成员函数存储在常量区,那为什么d1和d2调用时会打印出不同的结果呢?
这是因为类中的函数有一个隐藏的参数this指针。
void thisprint(Date *this)//this指针隐藏在类的函数中,不能手写,编译器自动完成
那么this指针又是什么呢?
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year=1, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
printf("%d-%d-%d\n", _year, _month, _day);
}
void thisprint()
{
printf("this指针:%p\n", this);
}
};
int main()
{
Date d1(2023,5,7);
Date d2(2024, 6, 8);
printf("d1的地址: %p\n", &d1);
d1.thisprint();
printf("d2的地址: %p\n", &d2);
d2.thisprint();
system("pause");
return 0;
}
通过上面代码的结果,this指针就是类的地址,那么可以由以下结论;
void print()
{
printf("%d-%d-%d\n", this->_year, this->_month, this->_day);
}
//类能通过常量区的成员函数打印值,是通过指针调用完成的。
为了保护this指针的值不被修改,this指针会用const修饰,写成const int * this(指针指向的值无法被修改 int * const this指针指向的变量不能被修改);
this指针也是存在栈区中,其作用域与生命周期随着函数的调用而产生,随着函数的销毁而消失,VS下通常优化在寄存器ecx中;
this 指针可以为空吗?
//先看下这段代码
class A
{
public:
void Print()
{
std::cout << "Print()" << std::endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
system("pause");
return 0;
}
//结构体的指针为nullptr,this指针为nullptr,在调用函数时Print函数从常量区中调用,与this指针无关,所以该程序可以正常运行;
//再看下面这段代码
class A
{
public:
void PrintA()
{
std::cout << _a << std::endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
return 0;
}
//虽然PrintA函数被成功调用,但是因为p指向nullptr,所以_a是无法读取内存的,因此运行时程序会崩溃
类的作用域
类的作用域是{}中的部分,但是它只是虚拟的,这是因为类类似于图纸;
class Date
{
public:
int _year;
int _month;
int _day;
void init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
printf("%d-%d-%d\n", _year, _month, _day);
}
};
namespace date
{
int _year = 2023;
int _month = 5;
int _day = 1;
}
int main()
{
Date s;
/*s.print();*/
printf("%d-%d-%d\n", date::_year, date::_month, date::_day);
printf("%d-%d-%d\n", Date::_year, Date::_month, Date::_day);//该语句无法通过编译
system("pause");
return 0;
}
上面的代码中,命名空间的变量可以通过作用域运算符::来找到对应的变量,但是类中定义的变量不能通过,其原因就是类没有被真实创建,类定义的只是图纸。
事实上不光是成员属性不能使用作用域运算符,成员方法也不行,这是因为this指针,虽然类中创建了成员函数,但是类没有被创建,没有this指针,所以运行崩溃。
成员函数的创建
定义成员函数时,如果将成员函数都放在类的内部,那么阅读起来会非常麻烦,通常只在类中声明,在外部定义;
class A
{
public:
void PrintA();
private:
int _a=10;
};
void A:: PrintA()
{
std::cout << _a << std::endl;
}
int main()
{
A _a;
_a.PrintA();
system("pause");
return 0;
}