⭐️ 类和对象
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交换完成。
🌟 类的定义
类的定义有两种方式:一种是以 struct
、一种是 class
,在C语言中结构体里只能定义变量,在C++中,结构体中不仅可以定义变量,也可以定义函数。但是更喜欢使用 class
来代替。
struct Person {
char _name[10];
char _sex;
int _age;
void sayName() {
cout << _name << endl;
}
};
在C语言中定义出一个结构体,使用结构体类型时要加上 struct
,但是在C++中可以省去了。
c:
struct Node {
struct Node * next;
int val;
};
c++:
struct Node {
Node * next;
int val;
};
clss
定义类的语法:
class className {
// 类域
// 由成员函数和成员变量构成
}; // 需要分号
class
为定义类的关键字,className
为类的名字,{}
中的内容都被称为在类域中,类后一定要有分号
类的两种定义方式:
- 声明和定义全部放在类中,如果成员函数在类中定义且当函数规模较小时,编译器可能会将其当成内联函数处理。
class Person {
public:
void showInfo() {
cout << _name << _sex << _age << endl;
}
private:
char* _name;
char* _sex;
int _age;
};
- 类声明放在
.h
文件中,成员函数定义放在.cpp
文件中,但是需要注意的是成员函数名前需要加类名::
.h文件
class Person {
public:
void showInfo();
private:
char* _name;
char* _sex;
int _age;
};
.cpp文件
#include <iostream>
void Person::showInfo() {
std::cout << _name << _sex << _age << std::endl;
}
🌟 类的访问限定修饰符
面向对象主要体现在三个特性上:封装、继承、多态。而访问限定修饰符主要的作用是实现封装的方式,通过访问修饰符选择性的将其接口提供给外部使用。
public
修饰的成员在类外可以直接被访问protected
和private
修饰的成员在类外不能直接被访问- 访问权限作用域从该访问限定修饰符的位置开始到下一个访问修饰限定符结束。
class
的默认访问权限是private
,而struct
默认访问权限是public
(因为要兼容c
)
c++
中 struct
和 class
的区别是什么?
c++
需要兼容 c
,所以 c++
中 struct
可以当成结构体使用,也可以用来定义类。和 class
定义类是一样的,它们的区别是 struct
定义类的默认访问权限是 public
,而 class
定义类的默认访问权限是 private
。
什么是封装呢?
封装就是将数据和操作的方法进行结合,隐藏对象的实现细节,仅对外公开接口来访问对象和对象进行交互。这样做的目的是不容易出错,相对安全。
🌟 类的实例化
使用定义的类来创建对象时,称为类的实例化。一个类可以实例化出多个对象。定义类时是不开辟空间的,只有在实例化对象时才会开辟空间。
#include <iostream>
using namespace std;
class Person {
public:
void showInfo() {
cout << _name << "-" << _sex << "-" << _age << endl;
}
void init(char * name , char * sex , int age) {
_name = name;
_sex = sex;
_age = age;
}
private:
char* _name;
char* _sex;
int _age;
};
int main() {
char name[10] = "sunwukong";
char sex[10] = "male";
Person p1; // 类的实例化
p1.init(name , sex , 18 );
p1.showInfo();
Person p2; // 类的实例化
Person p3; // 类的实例化
return 0;
}
🌟 类的存储模式
类中既有成员变量,又有成员函数。那么类是怎么存储的呢?
类的成员变量在类的对象中存储,而类的成员函数会单独放在这个类的函数表当中,为什么会这样呢?因为类中的成员函数是公共的(每一个实例化对象使用的函数都是相同的),如果在类的每个实例化对象中都会保存同一个函数,相同的函数代码浪费空间。
类的大小: 类的大小,实际就是类中的成员变量的大小,但是不是简单的把成员变量的字节都加起来,而是和 c
的结构体一样要注意内存对齐。而只有函数的类和空类比较特殊,因为类里的成员函数是存放在当前类的函数表中,所以类中的函数并不算在大小中。和空类一样,编译器会给一个字节来当作标记来标识这个类的对象。
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指针
当存在一个类的多个实例后,而类中又没有明显的区分,那么当调用不同的类方法时,这个方法怎么知道设置的到底是第一个对象还是第二个对象呢?
其实 c++
是在非静态的成员函数里增加了一个隐藏的指针参数,让该指针指向当前的对象,在函数体中所有的成员变量都是通过这个指针 this
去访问。
本质上在每一个成员函数的第一个形参前会默认加入一个 this
指针。真实摸样:void init(Person * const this , int age);
。而在类方法中访问成员时,编译器会默认在前面加上 this
真实摸样:this->_age = age;
。
this
指针的类型:类的类型 *const this
,在成员函数中,不能给this
赋值。this
指针本质上是成员函数的形参,当调用成员函数时,将对象地址作为参数传递给this
形参,所以对象中不存储this
指针。this
是编译器通过ecx
寄存器自动传递的,不需要用户传递。
ps:
this
指针的存放是取决编译器的,常规理解 this
指针是一个形参,存放在栈区当中,而在 vs
环境中,this
指针是通过寄存器来传递的。
🌠 例1
class A {
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main() {
A* p = nullptr;
p->Print();
return 0;
}
ps:
定义一个类指针赋值为 nullptr
,在使用类指针访问成员函数是可以正常运行的,因为类的成员函数并不存在类的实例对象中,而是存放在当前类的成员函数表中,所以直接在当前类的成员函数表中寻找函数的地址直接调用的。
🌠 例2
class A {
public:
void Print()
{
cout << _a << endl;
}
private:
int _a;
};
int main() {
A* p = nullptr;
p->Print();
return 0;
}
ps:
而上面这段程序,是会运行崩溃的。因为成员变量是存放在当前类的实例中,访问成员变量和访问成员函数是不同的,访问成员变量会隐藏的使用 this
指针来访问成员变量,而 this
指针又是在调用成员函数时,隐含的将对象的地址传入给函数,所以这里的 this
为空指针,造成空指针的解引用。而访问成员函数虽然 this
是空指针,但是在内部并没有访问。