大纲
初步认识类和对象
c语言是面向过程的 关注的是过程 分析出求问题的步骤 通过调用函数逐步解决 c++是基于面向对象的 关注的是对象 将一件事情拆分成不同的对象 靠对象之间的交互完成 举个栗子
把大象关进冰箱。 面向过程的分析过程: 第一步:把冰箱门打开; 第二步:将大象放进冰箱; 第三步:把冰箱门关闭; 面向对象的分析过程: 第一步:分析动作是由那些实体发出的; 人 ,冰箱,大象 第二步:定义主体,为其增加属性和功能; 人,人需要有打开关闭冰箱,及将大象放入冰箱的功能; 冰箱,冰箱需要具有能开门和关门的属性; 大象,大象需要具有能够进入冰箱的功能 面向过程关注点在 开门 把大象装进去 关门 这三个过程中 面向对象 关注的是人 冰箱 大象 这三个类对象之间的关系
类的引入
在c语言中 结构体只能定义变量 但是在c++中 结构体不仅可以定义变量 还可以定义函数 比如
cpp
struct test
{
int a = 10;
int Add(int x, int y)
{
return x + y;
}
};
上面使用结构体定义类 在C++中一般用class来定义
类的定义
class 为定义类的关键字
cpp
class Test
{
//...
};
Test 为类的名字(自定义) 类中的元素称为类的成员 类中的变量称为类的属性或者成员变量 类中的函数称为类的函数或者类的方法
类的定义的两种方式
1.类的定义和声明全部方在类体中。
cpp
//test.cpp文件
class Test
{
void Init()
{
//实现...
}
char _name;
int _id;
int _age;
};
2.声明放在头文件中、定义放在源文件中
cpp
//test.h文件
//声明
class Test
{
void Init();
char _name;
int _id;
int _age;
};
cpp
//test.cpp
//定义
#include "test.h"
void Test::Init()
{
//实现...
}
一般更建议使用第二种方法
类的访问限定符
public 修饰的成员在类外面可以直接被访问 protected 和private修饰的成员在类外面不能直接被访问 访问权限作用域从该限定符出现的位置到下一个访问限定符出现的位置为止 如果后面没有访问限定符 作用域到}就结束了 class的默认权限是private struct默认是public 访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
cpp
class Test
{
public:
void Init()
{
//实现...
}
private:
char _name;
int _id;
int _age;
};
类的作用域
类定义了一个新的作用域 类的所有成员都在类的作用域中 在类外面定义成员时 需要使用作用域限定符指明成员属于哪一个类 比如
cpp
class Test
{
public:
void Init();
private:
char _name;
int _id;
int _age;
};
//这里需要说明Init属于Test类
void Test::Init()
{
cout << _name << endl;
}
类的实例化
用类创建对象称为类的实例化 类只是一个模型一样的东西 限定了类有哪些成员 定义出一个类并没有分配实际的存储空间 一个类可以实例化多个对象 对象将占用实际的空间来存储类的成员变量 类和对象的关系是一对多的 就好比现实生活中用图纸造房子 可以用一个图纸造n个房子 图纸就是类 房子就是对象
cpp
class Test
{
public:
void Init();
private:
char* _name;
int _id;
int _age;
};
void test()
{
Test t1;
Test t2;
Test t3;
Test t4;
Test t5;
}
t1 t2 t3 t4 t5 都是实例化对象
类对象模型
类对象的大小
一个类中既有成员变量 又有成员函数 那么类的大小应该如何计算 解决这个问题之前 先看一下类对象在内存中的存储方式
类对象的存储方式
用类定义对象时 系统会为每个对象分配存储空间 如果一个类即包含成员变量和成员函数 需要分别为他们分配空间 这样分配空间太奢侈了 c++会只给成员变量分配空间 成员函数放在公共代码段 通过几个例子进行验证
cpp
#include<iostream>
using namespace std;
class Test
{
public:
int Add(int x, int y)
{
return x + y;
}
int _a;
int _b;
};
int main()
{
cout << sizeof(Test) << endl;
return 0;
}
上面程序的结果是8 成员函数的大小并没有计算
cpp
class Test2
{
void fun()
{
}
};
int main()
{
cout << sizeof(Test2) << endl;
return 0;
}
一个类中只有成员函数时 大小为1
cpp
class Test3
{
};
int main()
{
cout << sizeof(Test3) << endl;
return 0;
}
类中什么也没有-空类 大小也是1
cpp
class Test
{
public:
int Add(int x, int y)
{
return x + y;
}
char _a;
int _b;
};
这个类的大小也是8 在类中也要注意内存对齐 结论:一个类的大小,就是成员变量之和不包含成员函数所占的空间,也要注意内存对齐 空类的大小比较特殊 编译器给空类一个字节来标识这个类的对象
this指针
cpp
#include<iostream>
using namespace std;
class Data
{
private:
int _year;
int _month;
int _day;
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month<< "-" << _day << endl;
}
};
int main()
{
Data d1, d2;
d1.Init(1,1,1);
d2.Init(2023,10,20);
d1.Print();
d2.Print();
return 0;
}
上面Data类中实例化两个对象d1和d2 d1调用成员函数Init的时候 该函数是如何知道调用的是d1对象 而不是d2对象呢? 在c++中引入this指针解决该问题: c++编译器给每个非静态的成员函数增加了一个隐藏的指针参数,让该指针指向当前对象 在函数体中 所有成员变量的操作都是由该指针去访问的。用户不需要自己传递,编译器自动完成 上面的代码可以看成这样 成员函数写成这样也是可以的
cpp
//void Init(Data* this,int year, int month, int day)//编译器处理成员函数隐藏的this指针
void Init(int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
//void Print(Data* this)
void Print()
{
cout << this->_year << "-" << this->_month<< "-" << this->_day << endl;
}
this指针的特征
1、this指针的类型:类的类型* const,即成员函数中 不能改变this指针 2、只能在成员函数内部使用 3、this指针本质上是成员函数形参 当对象调用成员函数时 将对象地址作为实参传递给this指针 所以对象中不存储this指针 4、this指针时采成员函数第一个隐含的指针形参 一般由编译器通过ecx寄存器自动传递 不需要用户传
通过下面的例子更好的认识this指针
cpp
#include<iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << "Print()" << endl;
}
void PrintA()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
p->PrintA();
return 0;
}
可以看出p是一个空指针 当程序执行p->Print();时程序并不会崩溃 代码并没有对空指针p进行解引用,因为Print等成员函数地址并没有存到对象里面,成员函数的地址是存在公共代码段的。 当执行p->PrintA();时程序会崩溃 调用成员函数PrintA()没有问题 但是 PrintA()里面打印了成员变量_a;成员变量需要通过this指针引用 而此时this指针是空指针 对空指针进行解引用必然会导致程序崩溃