提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
1.面向过程和面向对象初步认识
之前我们学习的C语言是面向过程的,关注的是过程,分析出解决问题的步骤,然后调用函数将问题解决。
**C++**是面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
就好比是洗衣服:
C语言是面向过程。
C++是面向对象: 总共有四个对象:人,洗衣粉,洗衣机,衣服 整个洗衣服的过程:人将衣服放入洗衣机,放入洗衣粉,启动洗衣机,洗衣机就会完成洗衣服并且甩干。
整个过程是:人,衣服,洗衣粉,洗衣机四个对象交互完成的,人不用关心洗衣机是怎么样洗衣服的,怎么样甩干的。
2.类的引入
之前在C语言中,结构体里面只能够是定义变量,不能定义函数,在C++中,结构体里面可以定义变量也可以定义函数
struct Stack
{
void Init(int capacity)
{
_arr = (int*)malloc(sizeof(int) * capacity);
if (_arr == NULL)
{
printf("malloc fail\n");
exit(-1);
}
_capacity = capacity;
_size = 0;
}
void Push(int x)
{
if (_size == _capacity)
{
_arr = (int*)realloc(_arr, sizeof(int) * _capacity * 2);
_capacity = _capacity * 2;
}
_arr[_size] = x;
_size++;
}
void Pop()
{
if (_size == 0)
{
printf("NULL");
return;
}
_size--;
}
int Top()
{
return _arr[_size - 1];
}
int* _arr;
int _size;
int _capacity;
};
int main()
{
Stack st;
st.Init(10);
st.Push(1);
st.Push(2);
st.Push(3);
st.Push(4);
cout << st.Top() << endl;
return 0;
}
这样函数和定义都在结构体里面了,是不是很方便呢 上述的结构体,C++更喜欢用class来代替
3.类的定义
我们怎么定义类呢,参考结构体,很简单:
class为类的关键字,classNume是类的名字(可以自己取),{}中就是类的主体,别忘记{}后面的分号。
类体中的内容称为类的成员,类的变量称为类的属性或者成员变量,类的函数称为类的方法或者成员函数。
类有两种方式定义
1、将声明和定义全部放入类体重,需要主要的是,如果将成员函数放入类中定义,编译器可能会将其当成内联函数处理
2、声明和定义分离
一般情况下,我们更推荐使用第二种
成员变量命名的建议
所以我们应该摒弃这个命名:
class PerInfo
{
public:
void ShowInfo(int age)
{
_age = age;
}
private:
int _age;//成员变量前加_
};
也可以是:
class PerInfo
{
public:
void ShowInfo(int age)
{
mAge = age;
}
private:
int mAge;//首字母变成大写并且前面加个m
//加什么取决于公司要求
};
4.类的访问限定符及封装
访问限定符
至于前面类体中所出现的public和private就是访问限定符
访问限定符说明:
1、public修饰的成员在类外可以直接访问
2、pretected和private修饰的成员在类外不可以直接访问
3、访问权限作用域是从该访问限定符出现的位置到下一个访问限定符出现的位置
4、如果后面没有访问限定符,那么类就到 } 结束
5、class的默认访问权限是private,而struct 为public(因为要兼容C)
注意:访问限定符只在编译的时候有用,当数据映射到内存后, 没有任何访问限定符上的区别
面试题:
问题:C++中struct和class的区别是什么?
解答:C++需要兼容C语言,struct在C++中可以当成结构体使用,C++中可以用struct定义类,也可以class定义类,其区别就是访问限定符不一样,在struct中访问限定符是public,而class中访问限定符是private。
另外在继承和模板参数列表位置,struct和class也有区别,后序会给大家介绍
封装
面试题:面向对象的三大特征:封装,继承,多态(今天讲封装)
在类和对象阶段,主要是研究类的特性,那么什么是封装呢?
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅仅对方开放接口和对象进行交互。
就好比是一台电脑主机,里面非常复杂,它的外面给你留很多usb接口,开关机键,让用户和计算机进行交互,完成日常的使用,但是实际上电脑真正工作的却是CPU,显卡,内存等一些硬件。
封装本质上是一种管理,让用户更方便使用类。
5.类的作用域
类定义了一个新的作用域,类的所有成员都在域的作用域中,在类体外定义成员时,需要使用 :: 作用域操作符来指明是属于哪个作用域的。
class PerInfo
{
public:
void ShowInfo();
private:
int mAge;
};
//这里需要指明ShowInfo是属于哪个作用域的
void PerInfo::ShowInfo()
{
//...
}
6.类的实例化
用类类型创建对象的过程,称为类的实例化
1、类是对对象来描述的,是一个虚拟模型一样的东西,限定了类的成员,定义出一个类并没有分配实际的内存空间来储存它。
类就想是谜语一样,对谜底进行描述,谜底就是谜语的一个实例。
2.一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量
例如:
int main()
{
PerInfo.age = 20;//PerInfo类时没有空间的,之后PerInfo类实例化出的对象才有年龄
return 0;
}
3.打个比方,类实例化就像是用建筑设计图去设计建筑,类就像是设计图,只需要设计出需要什么东西就行了,并不会有真是的建筑存在,实例化的对象就好比是建好的房子,只有建好了的房子才真是的占据了物理空间。
所以上面的例子我们这样修改:
7.类的对象大小的计算
如何计算类对象的大小
类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么呢?那么类的大小该如何计算呢?
通过一个小程序在证实一下:
这里可以猜测类的大小跟成员函数应该是无关的,只跟成员变量有关
类对象储存方式猜测
1、对象中包含类的各个成员
2、代码只保存一份,在对象中只保存存放函数代码的地址
3、只保存成员变量,成员函数放在公共的代码段
举个例子感受一下:
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
void fun()
{
cout << "void fun()" << endl;
}
//private:
int _a;
};
int main()
{
A* ptr = nullptr;
ptr->fun();
return 0;
}
这个答案是什么呢?
A、程序崩溃
B、正常运行
C、编译报错
答案是正常运行
第二种方式和第三种方式都会有采用,但是第一种尽可能不采用
这里重点提一下编译连接,如果有老铁对这不太熟悉的,可以了解一下,我后期会更新
既然对上述三种计算机储存方式已经有了初步的了解,那么接下来再深入了解一下。 我们再分别对不同的对象分析大小:
直接得出结论:一个类的大小,就是成员变量之和,当然要注意的是内存对齐。
特别注意的是空类,编译器会给空类一个字节
内存对齐的规则
前面在C语言已经学习过内存对齐,这里就概述一下。
如果有老铁不了解的话,可以直接跳到我的另一篇文章:
关于结构体大小计算
- 第一个成员在与结构体偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的对齐数为83. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
同时复习完之后可以考虑一下这几道面试题:
- 结构体怎么对齐? 为什么要进行内存对齐?
- 如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?
- 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景**
8.类成员函数的this指针
this指针的引出
对于上述问题,当Date类中都有Init和Print两个成员函数,函数体中没有关于不同对象的区分,那如果当调用d1的Init函数的时候,该函数是如何知道设置d1对象的呢,为什么不会设置d2对象呢?
这时候C++就引入了this指针这一方法:
就是给每一个“非静态的成员函数”增加了一个隐藏的this指针参数,让该指针指向当前对象,在函数体中进行对该对象的操作,都是通过这一指针去访问进行的,只不过所有的操作都是计算机进行的,我们不用管,编译器自动会编译完成,我们要知道存在着这一个this指针。
编译器处理成员函数隐含的this指针
this指针的特性
1、this指针的类型:类类型*const,即成员函数中,不能给this指针赋值 2、只能在成员函数的内部使用
3、this指针实质上是成员函数的形参,当对象调用成员函数时候,将对象地址作为实参传递给this形参,所以对象中不存储this指针
4、this指针是成员函数的第一个隐含的指针形参,一般情况下由编译器通过ecx寄存器自动传递,不需要用户传递。
接下来看几道面试题,往年公司面试出到过:
1. // 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void Print()
{
//cout<<this<<endl;
cout << "Print()" << endl;
}
private: int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void PrintA()
{
cout<<_a<<endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
return 0;
}
上面两个题的结果是:
1、正常运行
因为 p->Print();没有解引用就不会到对象里面找, 而是在公共代码段里面找函数,直接Call
Print()的地址,在编译链接的时候就已经确定它的地址了,然后知道Call地址。(空指针可以存在,但是解引用空指针就不行了)
2、程序崩溃
void PrintA()
{
cout<<this<<endl;
cout<<this->_a<<endl;
}
这也是没有解引用空指针,上述隐含的this指针,this->_a是这个对象里面的成员变量,因为没有解引用指针,所有不可以访问,所以就程序崩溃。
如有错误,多多指教,希望我的文章可以解决你的疑惑!
祝天天开心,天天进步。