目录
一、面向过程和面向对象初步认识
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
二、类的引入
在C语言中有结构体struct,下面定义一个学生的结构体:只能定义变量
struct Student
{
char _name[20];
char _gender[3];
int _age;
};
在C++中,由于继承了C语言的语法,struct可以作为结构体使用;同时,也可以作为类来使用,因此在结构体中可以定义函数,其中的变量叫做成员变量。
struct Student
{
//可以在结构体中定义函数
void Init(char* name, char* gender, int age)
{
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
void Print()
{
cout << _name << " " << _gender << " " << _age;
}
char _name[20];
char _gender[3];
int _age;
};
C++中对结构体升级成类的特点
- 结构体名称可以做类型
- 里面可以定义函数
对于如上的结构体或类的变量定义如下代码:
int main()
{
//在C语言中的写法
struct Student S1;
//在C++中的写法
Student s1;
return 0;
}
C++里面可以访问其成员也可以访问其函数
struct Student
{
void Init(const char* name,const char* gender, int age)
{
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
void Print()
{
cout << _name << " " << _gender << " " << _age << endl;
}
char _name[20];
char _gender[3];
int _age;
};
int main()
{
Student s1;
s1.Init("张三", "男", 18);
s1.Print();
cout << s1._age << endl;
//访问成员变量
s1._age = 10;
cout << s1._age << endl;
return 0;
}
代码运行结果如下:
- 注意:
这里的成员变量可以定义在任何位置,不会像C语言那样受到约束,因为C++这里把struct升级成了类,类是一个整体,它会在这个整体内寻找。
虽然C++把struct升级成了类,不过C++还是喜欢用class来代替struct以此来定义类。
- 如下代码:
int main() { Student s1; return 0; }
我们把Student叫做类名,先前C语言s1叫做变量,而到了C++要称之为对象。
三、类的定义
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
- class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。
类体中内容称为 类的成员 :类中的变量称为 类的属性 或 成员变量 ; 类中的函数称为 类的方法 或者 成员函数 。- 注:成员函数的定义:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
在类中定义成员函数:
在.h和.cpp文件中分开定义:
.h文件
.cpp文件
在日常的代码书写中,如果代码简短可以直接在类中定义,否则更加推荐采用第二种定义方式。
四、访问限定符及封装
1.访问限定符号
在上文介绍类定义的时候其实已经提前用到了访问限定符,Student类中的public、private便是访问限定符。
C++实现封装的方式: 用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。访问限定符有三个,分别是 public,private,protected。
访问限定符说明:
public修饰的成员在 类外可以直接被访问。 protected和private修饰的成员在类外 不能直接被访问 (此处protected和private是类似的)。 访问权限 作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止 如果后面没有访问限定符,作用域就到 } 即类结束。 class的默认访问权限为private,struct为public(因为struct要兼容C)
- 面试问题: C++中struct和class的区别是什么?
2.封装
C++是一门面向对象的语言,面向对象的语言有三大特点:封装、继承、多态。接下来我们对封装进行详细的讲解。
封装:将数据和操作数据的方法进行有机结合, 隐藏对象的属性和实现细节 , 仅对外公开接口来和对象进行交互 。封装的本质是一种管理,让用户更方便的使用类 。例:对于电脑这样一个复杂的设备,提供给用户的就只有开关机键、通过键盘输入,显示器,USB 插孔等,让用户和计算机进行交互,完成日常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。我们并不关心电脑内的各个元件如何工作, 计 算机厂商在出厂时,在外部套上壳子, 将内部实现细节隐藏起来 , 仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可 。可见,各个元件被电脑封装了起来,而鼠标、键盘等就能类比为对象, C++中通过对象来访问类,不需要了解其中被封装起来的细节。
五、类的作用域
//栈
class Stack
{
public:
void push(int x)
{
}
};
//队列
class Queue
{
public:
void push(int x)
{
}
};
这两段代码之中都定义了push函数,但是编译器并不会发生报错,因为类Stack和类Queue是两个不同的作用域。
这时上文的一段代码,类的成员函数在分离定义时需要加域作用限定符: :,来确定这个函数是哪个类的作用域里的。
六、类的实例化
用类类型创建对象的过程,称为类的实例化
- 类只是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它
一个类可以实例化出多个对象, 实例化出的对象占用实际的物理空间,存储类成员变量 做个比方。 类实例化出对象就像现实中使用建筑设计图建造出房子, 类就像是设计图 ,只设计出需要什么东西,但是 并没有实体的建筑存在 ,同样类也只是一个设计, 实例化出的对象才能实际存储数据,占用物理空间
七、类对象模型
1.类对象的存储方式
- 在存储对象时,栈中只会开辟出成员变量的空间,成员函数放在公共代码区。
原因:一个类可以实例化多个对象,每个对象中各自的成员变量可能各不相同,但是对象的成员函数都是一样的,为了防止重复存储应该单独存放。如果每个对象把成员函数和成员变量存储在一起,会非常浪费空间。正确的存储方式如下图:
2.类对象的大小
结论:一个 类的大小,实际就是该类中”成员变量”之和 ,当然要注意 内存对齐 , 注意空类的大小 ,空类比较特殊,编译器 给了空类一个字节来唯一标识这个类的对象 。
代码验证:
// 类中既有成员变量,又有成员函数
class A1 {
public:
void f1() {}
private:
int _a;
};
// 类中仅有成员函数
class A2 {
public:
void f2() {}
};
// 类中什么都没有---空类
class A3
{};
int main()
{
cout << sizeof(A1) << endl; //4
cout << sizeof(A2) << endl; //1
cout << sizeof(A3) << endl; //1
return 0;
}
运行结果如下:
八、this指针
1.this指针的引出
如下为一个日期类:
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout <<_year<< "-" <<_month << "-"<< _day <<endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
Date d1, d2;
d1.Init(2022,1,11);
d2.Init(2022, 1, 12);
d1.Print();
d2.Print();
return 0;
}
结果如下:
通过上文的学习,我们了解到,对象d1和对象d2调用的成员函数都是存放在公共代码区的同一个函数。他们传递的实参类型都完全相同,那么编译器是如何判断是哪个对象在调用这个函数呢?
class Date
{
public:
//原代码
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//编译器处理后
void Init(Data const* this, int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
//原代码
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//编译器处理后
void Print(Data const* this)
{
cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
Date d1, d2;
//原代码
d1.Init(2022, 1, 11);
d2.Init(2022, 1, 12);
//编译器处理后
d1.Init(&d1, 2022, 1, 11);
d2.Init(&d2, 2022, 1, 12);
//原代码
d1.Print();
d2.Print();
//编译器处理后
d1.Print(&d1);
d2.Print(&d2);
return 0;
}
- 注:this和&都不能明着在实参和形参的位置写出来,编译器会自己帮我们完成,我们无需操作 ,你不能抢了编译器的饭碗。但是我们可以在函数里面显示出this指针,当然你也可以不写,因为编译器会帮你完成。
2.this指针的特性
this指针的特性:
- this指针的类型:类类型* const
- 只能在“成员函数”的内部使用
- this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递