我们说C语言是面向过程的,C++是面向对象的,从而分割出了面向过程编程和面向对象编程,那如何理解呢?
我们可以这样理解,举例放水果进冰箱这个问题
面向过程编程方法:分析出求解问题的步骤,通过函数调用逐步解决问题
1.打开冰箱
2.放入水果
3.关闭冰箱
面向对象编程方法:将一件事情拆分成不同的对象,靠对象之间的交互完成
1.人:打开冰箱放入水果再关闭冰箱
2.水果:被人放进冰箱
3.冰箱:被人打开关闭
面向对象三大特性:封装、继承、多态
C++是C语言的扩展和升级,因为C语言的结构体只能定义变量而不能定义函数,导致非常的不方便,且项目的安全性不好,所以C++引入了类来解决这些问题
#include
using namespace std;
struct Student//在C语言中struct是结构体,在C++中struct被升级为了类(class)
{
char name1[20];
char gender1[3];
int age1;
//设置学生基本信息
void Set_StudentInfo(const char* name,const char* gender,int age)
{
strcpy(name1,name);
strcpy(gender1,gender);
age1=age;
}
//打印学生信息
void Print_StudentInfo()
{
cout<<name1<<" "<<gender1<<" "<<age1<<endl;
}
}
int main()
{
Student s;
s.Set_StudentInfo("小张","男",18);
s.Print_StudentInfo();
return 0;
}
上面结构体的定义,struct结构体名称可以作为类型,在C++中更喜欢用class来定义类
class为定义类的关键字,className为类的名字,{}中为类的主体
class类中元素是类的成员,类中数据称为类的属性或者成员变量,类中的函数称为类的方法或者成员函数
class className
{
类体:成员变量+成员函数(类的属性+类的方法)
};//一定要加分号
类的方法有两种定义方式:1.同时声明定义 2.分开声明定义
//1.声明和定义全部放在类体中
class Dog//狗类
{
public:
//在类里面定义的函数默认是inline函数(重要)
void ShowInfo()//显示基本信息
{
cout<<name<<" "<<sex<<" "<<age<<endl;
}
public:
//成员变量是声明---定义需要开辟空间否则是声明
char* name;//姓名
char* sex;//性别
int age;//年龄
}
//2.声明放在.h文件中,类的定义放在cpp文件中
//Dog.h中
class Dog
{
public:
void ShowInfo();
public:
char* name;
char* sex;
int age;
}
//Dog.cpp
#include"Dog.h"
void Dog::ShowInfo()
{
cout<<name<<" "<<sex<<" "<<age<<endl;
}
总结:一般情况下,短小函数可以直接在类中定义,长一点的函数声明和定义分离
一般我们使用第二种方法,因为第一种方法如果成员函数在类中定义,编译器可能会将其当作内联函数来处理,从而出现链接失败问题
在我们使用类的时候,其实就重新定义了作用域
class Cat
{
public:
void Print_CatInfo();
private:
char name[20];
char sex[3];
int age;
};
//通过使用Print_CatInfo()就必须要Cat::,从而达到了重新定义作用域的目的
void Cat::Print_CatInfo()
{
cout<<name<<" "<<sex<<" "<<age<<endl;
}
用类去创建对象的过程叫做类的实例化
1.类只是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它
2.一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间、存储类成员变量
3.类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间
4.类是一个声明,类的实例化才是定义(分配实际空间存储类对象)
类的引入原因之一就是安全性问题,从而定义了类的访问限定符
C++实现封装是通过:用类将对象的属性与方法结合在一起,通过访问权限选择性的将其接口提供给外部的用于使用
1.public修饰的成员在类外可以直接被访问
2.protected和private修饰的成员在类外不能直接被访问
3.访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4.class的默认访问权限为private,struct为public(因为struct要兼容C语言)
扩展:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
一般情况下,设计类的成员数据都是私有或者保护的,想给访问的函数是公有的,不想给访问的函数是私有或保护的
封装是将数据和方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口和对象进行交互
封装本质上是一种管理,我们把类数据和方法都封装一下,不给别人看见。比如我们使用protected或private把成员封装起来,开放一些共有的成员函数对成员合理访问,从而让程序更加安全
类的对象模型我们主要需要关注三个问题:
1.如何计算类对象的大小
2.如何猜测类对象的存储方式
3.结构体内存对齐方式(C中经常讨论)
class A
{
public:
void Print()
{
cout<<x<<endl;
}
private:
char x;
};
//只计算成员变量大小(要进行内存对齐),不计算成员函数大小---由存储方式决定
类对象的存储方式主要有两种:编译器通常使用第二种方法
1.对象中包含类的各个成员
2.只保留成员变量,成员函数存放在公共的代码段
第一种方式图解:
这种方式,每个对象中成员变量是不同的,但是调用同一份函数,如果按照这种方式存储,当一个类创建多个对象时,每个对象中都会保存一份函数的代码,相同代码保存多次,浪费空间
第二种方式图解:
这种方法,每个对象只存成员变量,函数存在公共代码区,比方法一更加节约空间
从上图来看,一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类
总结:
1.同一个类的不同对象,调用成员变量是不同的(地址不同),调用成员函数是相同的(地址相同)
2.成员变量地址放在对象中,成员函数不在对象中,在公共代码区
3.编译器给空类一个字节来标识这个类
举例:
// 类中既有成员变量,又有成员函数
class A1
{
public:
void f1(){}
private:
int _a;
char _aa;
};
// 类中仅有成员函数
class A2
{
public:
void f2(){}
};
// 类中什么都没有---空类
class A3
{};
//计算类的长度
sizeof(A1)=8 sizeof(A2)=1 sizeof(A3)=1
第一个成员在与结构体偏移量为0的地址处
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处—注:对齐数=编译器默认的一个对齐数与该成员大小的较小值 VS中默认的对齐数为8
结构体总大小为(重点):最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
//定义一个日期类Date
class Date
{
public:
void Display()
{
cout <<_year<< "-" <<_month << "-"<< _day <<endl;
}
void SetDate(int year , int month , int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
int main()
{
Date s1, s2;
s1.SetDate(2018,5,1);
s2.SetDate(2018,7,1);
s1.Display();
s2.Display();
return 0;
}
Date类中有SetDate与Display两个成员函数,函数体中没有关于不同对象的区分,那当s1调用SetDate函数时,该函数是如何知道应该设置s1对象,而不是设置s2对象呢?
实现方法:void Dispaly()会被编译器默认处理成void Display(Date * this) 严格来说是Date * const this(const修饰this)
//原函数
void Display()
{
cout <<_year<< "-" <<_month << "-"<< _day <<endl;
}
//隐含this指针的原函数
//Date* this谁调用访问谁的成员
void Display(Date* this)
{
cout <<this->_year<< "-" <<this->_month << "-"<<this->_day <<endl;
}
int main()
{
Date s1, s2;
s1.SetDate(2018,5,1);
s2.SetDate(2018,7,1);
s1.Display(&s1);
s2.Display(&s2);
return 0;
}
总结:
- C++中通过引入this指针解决区分对象的问题,即:C++编译器给每个“非静态的成员函数”增加了一个隐藏的this指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问
- this指针编译器自动完成,自己不能实现
this指针的类型:类型是*const(this指针被const修饰,本身不能修改,但指向的对象可以被修改)
只能在“成员函数”的内部使用
this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针
this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
注:this指针存放在栈里
解释:
1.push是压栈的意思
2.pop是出栈的意思
3.lea是存储器操作数mem的4位16进制偏移地址送到指定的寄存器
4.call是用于调用其他函数
5.add是将数据累加到寄存器中
类型:类型是*const(this指针被const修饰,本身不能修改,但指向的对象可以被修改)
只能在“成员函数”的内部使用
this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针
this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递