1.初步认识:
C语言是面向过程
C++是基于面向对象,不是纯面向对象,可以面向对象和面向过程混编(兼容C语言)
设计学校外面系统
面向过程:
关注:点餐、接单、送餐过程。关注的是流程函数的实现
面向对象:
关注:用户、商家、骑手。关注的是对象之间的关系
2.类的引入
//C
struct Stack
{
int* a;
int top;
int capacity;
};
void StackInit(struct Stack* ps);
void StackPush(struct Stack* ps,int x);
void StackPop(struct Stack* ps);
//C++兼容 struct的语法
//C++同时将struct升级成类
//成员变量
//成员函数
struct Stack
{
void Init()
{
a = 0;
top = capacity = 0;
}
void Push(int x)
{
//...
}
void Pop()
{
//...
}
int* a;
int top;
int capacity;
};
int main()
{
struct Stack st1;
Stack st2;
st1.Init();
st1. Push(1);
return 0;
}
//定义结构的区别
//C
typedef struct ListNode
{
struct ListNode* next;
int val;
}LTNode;
//C++
struct ListNode
{
ListNode* next;
int val;
};
3.类的定义
虽然C++兼容C语言,但是在定义类中还是更喜欢用class
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
类里定义
如果符合inline条件,编译条件过短,编译器可能会当成内联函数处理声明和定义分离
规则
:
- 小函数,想成为inline,直接在类里面定义即可
- 如果是大函数,应该声明和定义分离
4.类的访问限定符及封装
4.1访问限定符
public公有
protected保护
private私有
C++实现封装的方式
:
用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用
访问限定符说明
:
- public修饰的成员在类外可以直接被访问 也能在类里访问
- protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的) 只能在类里访问(到继承这两个才会有区别)
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
- 如果后面没有访问限定符,作用域就到 } 即类结束。
- class的默认访问权限为private,struct为public(因为struct要兼容C)
注意
:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
问题:C++中struct和class的区别是什么?
解答:C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类。和class定义类是一样的
区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是
private。
4.2封装
【面试题】 面向对象的三大特性:封装、继承、多态
。
在类和对象阶段,主要是研究类的封装特性,那什么是封装呢?
封装
:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
隐藏对象
:private/protected 成员变量 意义就是封装
对外公开
:public 开放用成员函数
封装本质上是一种管理,让用户更方便使用类。
比如:对于电脑这样一个复杂的设备,提供给用户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日常事务。
但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。
C语言 没办法封装 规范使用函数访问数据,但也可以直接访问数据
不规范 松散管理
C++ 封装 必须规范使用函数访问数据 不能直接访问数据
严格管理
封装的好处:
直接访问有可能会出错
5.类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域
class Person
{
public:
void PrintPersonInfo();//声明
private:
char _name[20];//声明 对于变量而言开空间是定义,不开空间是声明
char _gender[3];
int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()//定义
{
cout << _name << " "<< _gender << " " << _age << endl;
}
6.类的实例化
用类类型创建对象的过程,称为类的实例化
- 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;
比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信息。
类就像谜语一样,对谜底来进行描述,谜底就是谜语的一个实例。
谜语:“年纪不大,胡子一把,主人来了,就喊妈妈” 谜底:山羊
- 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
Person类是没有空间的,只有Person类实例化出的对象才有具体的年龄。
声明和定义对于变量而言区别就是有没有开空间
person.h
#pragma once
#include<iostream>
using namespace std;
extern int age;//声明
//链接属性不一样
//static int age; //当前文件可见
//int age;//定义 所有文件可见
class Person
{
public:
void PrintfPersonInfo();//声明
private:
char _name[20];//声明
char _gender[3];
int _age;
};
struct ListNode
{
struct ListNode* next;//声明
int val;
};
test.h
#include "person.h"
int main()
{
cout << sizeof(Person) << endl;
Person p1;//类的实例化
Person p2;
Person p3;
return 0;
}
7.类对象模型
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
private:
char _a;
};
int main()
{
cout << sizeof(A) << endl;//1
A aa1;
A aa2;
aa1._a = 1;
aa2._a = 2;
aa1.PrintA();
aa2.PrintA();
return 0;
}
实例化的每个A对象成员变量都是独立空间,是不同变量,但是每个A对象,调用PrintA成员函数都是同一个。
所以采用1.对象中包含类的各个成员
这种存储方式就会造成浪费(一般不会采用)
2.代码只保存一份,在对象中保存存放代码的地址
(特殊情况)
3.只保存成员变量,成员函数存放在公共的代码段
编译链接时就根据函数名去公共代码区找到函数的地址。
call 函数地址
结构体内存对齐规则
- 第一个成员在与结构体偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的对齐数为8 - 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是
所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
// 类中既有成员变量,又有成员函数
class A1 {
public:
void f1(){}
private:
int _a;
};
// 类中仅有成员函数
class A2 {
public:
void f2() {}
};
// 类中什么都没有---空类
class A3
{};
sizeof(A1) : ___ 8___ sizeof(A2) : ____1 __ sizeof(A3) : ____1 __
没有成员变量的类对象,给1byte,占位不存储实际数据,标识对象存在
结论:一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐
8.this指针
//命名规则:
//1.单词和单词之间用首字母大写间隔——驼峰法 GetYear
//2.单词全部小写,单词之间用_分割 get_year
//驼峰法原则:
//a.函数名、类名等所有单词首字母大写 DateMgr
//b.变量首字母小写,后面单词首字母大写 dateMgr
//c.成员变量,首字母前面加_ _dateMgr
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;//加_为了区分
int _month;
int _day;
};
int main()
{
return 0;
}
Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,
那当d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?
C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
注意:实参和形参位置不能显示传递和接受this指针,但是可以在成员函数内部使用this指针
void Init(int year,int month, int day)
{
//this = nullptr;
cout << this << endl;
this->_year = year;
this->_month = month;
this->_day = day;
}
this指针存在哪里?
堆
栈 静态区 常量区
一般存在栈里,因为this指针是一个形参
但有时会被优化放到静态区里(vs下传递this指针,是通过ecx寄存器传递的,这样this访问可以提高效率)优化取决于编译器
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();//C
return 0;
}
// 2.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();//B
return 0;
}