1、面向过程和面向对象初步认识
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题
C++是基于面向对象的,关注的是对象,将一件事拆分成不同的对象,靠对象之间的交互完成
2、类
#include<iostream>
using namespace std;
struct Queue
{
void Init()
{
}
};
struct Stack
{
//成员函数
//不需要加前缀
void Init(int defaultCapacity=4)
{
a=(int*)malloc(sizeof(int)*defaultCapacity);
if(a==nullptr)
{
perror("malloc fail");
return;
}
capacity=defaultCapacity;
top=0;
}
//成员变量
int* a;
int top;
int capacity;
};
//struct中可以定义成员变量也可以定义成员函数,
//成员变量的位置可以随意,不一定要在成员函数之前,因为类域是一个整体
//类定义的是一个类域,不同类域中可以有同名函数
int main()
{
//C++兼容C语言,所以struct以前的用法都可以继续使用
struct Stack st1;
//同时struct升级成了类
Stack st2;
st2.Init();
}
但是在C++中更喜欢用class来代替struct
3、类的定义
class className
{
//类体:由成员函数和成员变量组成
}; //一定要注意后面的分号
class为定义类的关键字,className为类的名字,{}中为类的主体,注意类定义结束的后面分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的方法或者成员函数
类是可以声明和定义分离的,类的两种定义方式:
1、声明和定义全部放在类体中,需注意:成员函数如果在类中定义,默认为内联函数,但是否为inline最终还是由编译器决定
2、类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::
4、类的访问限定符及封装
将上面的代码用class来替代struct以后会发现程序报错,无法运行,这是因为类是有一些权限的限定
访问限定符:
注意:
1、public修饰的成员在类外可以直接被访问
2、protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的,在后面的继承中,这两个限定符就会有区别)
3、访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4、如果后面没有访问限定符,作用域就到 } 即类结束
5、class的默认访问权限为private,struct为public(因为struct要兼容C)
#include<iostream>
using namespace std;
struct Queue
{
void Init()
{
}
};
class Stack
{
public:
//成员函数
//不需要加前缀
void Init(int defaultCapacity=4)
{
a=(int*)malloc(sizeof(int)*defaultCapacity);
if(a==nullptr)
{
perror("malloc fail");
return;
}
capacity=defaultCapacity;
top=0;
}
private:
//成员变量
int* a;
int top;
int capacity;
};
//struct中可以定义成员变量也可以定义成员函数,
//成员变量的位置可以随意,不一定要在成员函数之前,因为类域是一个整体
//类定义的是一个类域,不同类域中可以有同名函数
int main()
{
Stack st2;
st2.Init();
}
所以,C++中struct和class的区别是什么:
C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类的默认访问权限是public,class定义的类的默认访问权限是private。而在继承和模板参数列表位置,struct和class也有区别
封装:
面向对象的三大特性:封装、继承、多态
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互
封装本质上是一种管理,让用户更方便使用类,杜绝一些不规范的行为
5、类的作用域
在不同的域中可以定义同名函数,编译器搜索顺序为局部域->类域->全局域/命名空间域,局部域和全局域会影响生命周期,类域和空间域不会影响。
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用:: 作用域操作符指明成员属于哪个类域
6、类的实例化
用类类型创建对象的过程,称为类的实例化
类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图
7、类对象模型
1>如何计算类对象的大小
首先C++与C语言一样,要坚持内存对齐,否则会造成效率的损失,成员函数不参与进行计算
#include<iostream>
using namespace std;
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
private:
char _a;
};
int main()
{
A aa1;
A aa2;
cout << sizeof(A) << endl;
cout << sizeof(aa1) << endl;
}
2>类对象的存储方式
如果对象中包含类的各个成员
缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间,因此这种方式不合适,应当采用另一种方式:
只保存成员变量,成员函数存放在公共的代码段,成员变量在对象中,而成员函数不在对象中
对不同对象分别获取大小:
一个类的大小,实际就是该类中“成员变量”之和,当然要注意内存对齐
注意空类的大小,即没有成员变量的类对象,空类比较特殊,需要1byte,是为了占位,表示对象存在,但不存储有效数据
结构体内存对齐规则:
1、第一个成员在与结构体偏移量为0的地址处。
2、其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
注意:对齐数=编译器默认的一个对齐数与该成员大小的较小值,VS中默认的对齐数为8
3、结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍
4,、如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
那么为什么要内存对齐呢?
因为计算机在读取数据时,并不是随意访问的,而是固定一次访问相同的大小(这与计算机具体的硬件有关),且读取的起始位置必须为固定访问大小的倍数位置
假设CPU读取数据时一次读取4个字节,那么第一次读取为0~3,第二次读取为4~7,按照此种方式读取,那么_ch和_a都只需要读取一次就可以完整读取,但是如果不对齐,那么访问_a需要读两次,并将第一次读取的部分与第二次读取的部分进行整合才能实现完整的读取,这样效率明显就降低了,因此内存对齐是以空间换时间的方法。
因为内存对齐会导致一定程度上的空间浪费,在内存空间比时间更重要时,可以修改默认对齐数,但是这改变的仅仅是存储方式,CPU的读取方式是不变的,此种方式为以时间换空间。
8、this指针
先定义一个日期类Date
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 a;
};
int main()
{
Date d1, d2;
d1.Init(2022, 1, 11);
d2.Init(2022, 1, 12);
d1.Print();
d2.Print();
return 0;
}
d1和d2调用的为公共区域的同一个Print函数,为什么调用的结果不同?
因为这其中有一个隐含的this指针,也就是编译器会对成员函数进行一些处理
并且在调用时也会进行一些处理
但是规定this不能再形参和实参显示传递,但是可以在函数内部显示使用
实际上this的类型为Date* const this,const修饰this本身,指针变量本身不可以被改,但是指向的内容可以被改,所以不可以this=nullptr
this是形参,所以this指针是跟普通参数一样存在函数调用的栈帧里面,所以当函数调用结束了,this就销毁了。VS下对this指针传递,进行优化,对象地址是放在ecx,ecx存储this指针的值,但不是所有的编译器都这样做
//下面程序编译运行结果是?A.编译报错 B.运行崩溃 C.正常运行
class A
{
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
}
此处应该选C。首先排除A,因为这里即使有问题也会是空指针的问题,属于是运行错误,运行错误不会报编译错误;p调用Print,不会发生解引用,因为Print的地址不在对象中。p会作为实参传递给this指针,p为空指针,传递一个空指针不会报错,但是对空指针进行解引用会报错。因此这里正常运行的原因在于,this指针是空的,但是函数内没有对this指针解引用
//下面程序编译运行结果是?A.编译报错 B.运行崩溃 C.正常运行
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
}
此处应该选B。与上一题相同,首先排除A。p调用Print,不会发生解引用,因为Print的地址不在对象中。p会作为实参传递给this指针,p为空指针,传递一个空指针不会报错,但是对空指针进行解引用会报错,而在调用Print函数时,虽然this指针是空的,但是函数内访问_a,本质是this->_a,那么就对p这个空指针解引用了,无法正常运行
C语言和C++实现Stack的对比:
C语言中没有封装,数据和方法是分离的,可以通过函数或者对象或者结构体变量去访问数据,函数访问需要传递变量
C++中数据和方法是封装在一起的,可以定义公有和私有,C++调用函数更加简洁一些
从底层来说,C和C++没有什么区别