文章目录
1.面向过程和面向对象的初步认识
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成
例如:洗衣服的过程:
人将衣服放进洗衣机中,并倒入洗衣粉,启动洗衣机,洗衣机就会完成洗衣过程并且甩干
总共有四个对象:人,衣服,洗衣机,洗衣粉
整个过程主要是:人,衣服,洗衣粉,洗衣机四个对象之间交互完成的,人不需要关心洗衣机具体是如何洗衣服的,是如何甩干的。
2.类的引入
C语言结构体内只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。比如:用C语言实现栈,结构体内只能够定义变量;现在以C++方式实现,在struct中也可以定义函数
struct Stack
{
//成员变量
int* a;
int top;
int capacity;
//成员函数
void Init(int n = 4)
{
a = (int*)malloc(sizeof(int) * n);
if (nullptr == a)
{
perror("malloc fail");
}
capacity = n;
top = 0;
}
void Push(int x)
{
a[top++] = x;
}
};
int main()
{
struct Stack st1;
st1.Init();
st1.Push(1);
st1.Push(2);
st1.Push(3);
return 0;
}
上面的结构体的定义,在C++中更喜欢用class来代替
3.类的定义
class className
{
//类体:由成员变量和成员函数组成
}; // 注意不要忘记后面的分号
class为定义类的关键字,className是类的名字,{}中为类的主体,注意定义类结束时后面的分号不能省略。
类体中内容称为类的成员,类中的变量称为类的属性或者成员变量;类中的函数称为类的方法或者成员函数
类的两种定义方法:
1.声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理
class Person
{
public:
void showInfo()
{
cout << "...";
}
public:
char* _name;
char* _sex;
char* _age;
};
2.类的声明放在.h文件中,成员函数定义放在.cpp文件中,注意成员函数名前需要加类名::
声明放在类的头文件中person.h中
class Person
{
public:
void showInfo();
public:
char* _name;
char* _sex;
char* _age;
};
//定义放在类的实现头文件person.cpp中
#include"person.h"
void person::showInfo()
{
cout << "...";
}
一般情况下更推荐采用第二种方式
4.类的访问限定符及封装
4.1访问限定符
C++实现封装的方法:用类将对象的属性与方法结合在一起,让对象更加完善,通过访问权限选择性的将接口提供给外部的用户使用
访问限定符说明
1.public修饰的成员在类内外都可以直接被访问
2.protected和private修饰的成员在类外不能直接被访问(private和protected是类似的)
3.访问权限的作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止,
如果后面没有访问限定符,作用域就到 } 即类的结束
4.class的默认权限为private,struct为public(因为struct要兼容C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
C++中的struct和class的区别是什么?
C++需要兼容C语言,所以C++中的struct可以当成结构体使用。另外C++中struct还可以用来定义类。和class定义类是一样的,区别就是struct定义类的默认访问权限是public,class定义类的默认访问权限是private
4.2封装
面向对象的三大特性:封装,多态,继承
封装:将数据和操作数据的方法进行结合,隐藏对象的属性和实现细节,仅对外提供有限的接口来和对象进行交互
封装的本质是一种管控:
1.CPP数据和方法都放在类里面
2.CPP访问限定符去对成员进行限制,想给你访问时是共有的,不想给你访问时是私有或者保护
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.类的实例化
用类类型创建对象的过程,称为类的实例化
解释:
1.类是对对象进行描述的,是一个像模型一样的东西,限定类有哪些成员,定义出一个类并没有为之分配实际的内存空间来存储它
2.一个类可以实例化多个对象出来,实例化出来的对象占用实际的内存空间,存储类成员变量。
一个类是没有占有空间的,只有类实例化出来的对象才占有一定的空间
class Person
{
public:
void showInfo()
{
cout << "showInfo" << endl;
}
char* _name;
char* _sex;
int _age;
};
int main()
{
Person man;
man._name = "jack";
man._age = 10;
man._sex = "男";
man.showInfo();
return 0;
}
7.类对象模型
7.1如何计算类对象的大小
class A
{
public:
void PrintA()
{
cout<<_a<<endl;
}
private:
char _a;
};
在介绍如何计算类对象的大小之前,我们先了解一下类的成员是如何存储的
7.2类对象的存储方式
只保存成员变量,成员函数则存放在公共的代码段
结论:一个类的大小,实际就是该类中成员变量大小之和,当然要注意内存对齐
注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象
7.3结构体内存对齐规则
struct A
{
private:
int _a;
char _i;
};
struct B
{
private:
char _i;
int _a;
};
//这里计算结构体AB大小的时候会存在内存对齐的情况
//但是内存对齐会导致空间浪费,那为什么要内存对齐
//那我们最先想到的就是提高效率,那为什么会提高效率呢?
//首先我们机器在读数据时,一次会读4个或者8个字节,也就是从整数倍开始读
// 对于上述读B中a的例子中,由于一次要读4个字节,如果a没有进行内存对齐的话,要对第一次读取的内容,要重新读取后三个字节,然后在读取最后一个字节的内容,这样效率会大大降低,所以要进行内存对齐
int main()
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
return 0;
}
结构体和类的内存对齐规则:
1.第一个成员变量在与结构体偏移量为0的地址处
2.其他变量要对其到某个数字(对齐数)的整数倍的地址处
注意对齐数=编译器默认的一个对齐数与该成员大小的较小值
VS默认的对齐数为 8
3.结构体或类的总大小为:最大对齐数(所有变量类型的最大者与默认对齐数取最小)的整数倍
如果嵌套了结构体的情况,嵌套的结构体要对其到自己的最大对齐数的整数倍
结构体的整体的大小就是所有最大对齐数的整数倍
8.this指针
8.1this指针
我们先定义一个日期类来引出this指针
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout <<_year<< "-" <<_month << "-"<< _day <<endl;
}
//隐藏的this指针说明
// void print(Date * const this)//这里const在*之后,修饰的是this表示,不能修改指针的指向,
// 但是可以修改该指针指向的内容
// {
// cout << this->_year << "/" << this->_month << "/" << this->_day << endl;
// }
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1,d2;
d1.Init(2024,8,10);
d2.Init(2024,8,11);
d1.print();
d2.print();
return 0;
}
Date类中有Init与Print两个成员函数,函数体中并没有关于不同对象的区分,那当d1调用Init函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?
C++中通过引入this指针来解决该问题:即:C++编译器给每一个非静态的成员函数增加了一个隐藏的指针参数,让该指针指向当前对象(即函数运行时调用该函数的对象),在函数体中所有的成员变量的操作,都是通过指针去访问,只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成
8.2this指针的特点
1.this指针的类型:类类型*const 指针名,即成员函数中,不能给this指针赋值
2.只能在成员函数内部使用
3.this指针的本质是成员函数的形参,当对象调用成员函数时,将对象的地址作为实参传递 给this形参,所以对象中不存储this指针。注意在形参和实参的位置上,我们不能显示写&A和this指针
4.this指针是成员函数的第一个隐藏的指针形参,一般情况下由编译器通过ecx寄存器自动传递,不需要用户传递
this指针存在哪里?
因为this指针是形参,所以一般存在栈上(也有可能存在寄存器中,因为在成员函数中有可能要频繁访问this指针,所以放在一个公共的地方可以提高效率)
常见的几个区域
堆(一般malloc或new出来的变量存放在堆上) 静态区(一般是全局变量或者静态变量) 常量区
两个简单的判断题
class A
{
public:
void PrintA()
{
cout << this << endl;
cout << _a << endl;//但由于这里的this是空指针,所以这里对空指针进行解引用这里会崩溃
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();//这里不会对p进行解引用,因为解引用的时候,我们通常是要到这个地址处进行访问一些变量,
// 但是这里的PrintA函数并不访问该地址处,所以这里只是会将p作为实参,传递给成员函数
(*p).PrintA();//同上
return 0;
}
class A
{
public:
void PrintA()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();//这里也同样的只是将p作为实参传递给形参,不会发生解引用,所以只会打印相关内容,正常运行
return 0;
}