一、前言
最近刚好有空,趁这段时刻,温习一下C++言语
,进一步夯实根底,为今后的底层开发
、音视频开发
、跨渠道开发
、算法
等方向的进一步学习埋下伏笔
咱们在上一篇文章中,已经充分阐明,C++言语是对C的扩展,树立在对C言语常识把握的根底上学习C++是事半功倍的
假设你对C言语已经淡忘,或许没有学过C言语,且一时半会没有思路怎么筛选可靠的C言语学习资料,能够借鉴我的这几篇文章:
1. C言语中心常识
- 01-温习C言语中心常识|综述
- 02-温习C言语中心常识|根本语法、数据类型、变量、常量、存储类、根本句子(判别句子、循环句子、go to句子)和运算
- 03-温习C言语中心常识|函数、效果域规矩、数组、枚举、字符与字符串、指针
- 04-温习C言语中心常识|结构体、共用体、位域、输入&输出、文件读写
- 05-温习C言语中心常识|预处理、头文件、强制类型转化、过错处理、递归、内存办理
面向目标常见常识点:
- 类
- 目标
- 成员变量、成员函数
- 封装、承继、多态
- …..
二、编程标准、类和目标
1. C++编程标准
- 每个人都能够有自己的编程标准,没有统一的标准,没有标准答案,没有最好的编程标准
- 变量名标准参考:
- 大局变量:
g_
- 成员变量:
m_
- 静态变量:
s_
- 常量:
c_
- 运用驼峰标识
- 大局变量:
2. 类和目标的根本概念
2.1 C和C++中struct差异
-
C
言语struct只要变量
-
C++
言语struct既有变量
,也有函数
2.2 类的封装
2.2.1 聚合特点和行为
- 咱们编写程序的目的是为了处理实践中的问题,而这些问题的构成都是由
各种事物组成
- 咱们在核算机中要处理这种问题,首要要做便是要将这个问题的参与者:
-
事和物
笼统到核算机程序中 - 也便是 用程序言语表明实践的事物
-
- 那么现在问题是怎么用程序言语来表明实践事物?
- 实践世界的事物所具有的共性便是,每个事物都具有
- 自身的特点
- 一些自身具有的行为
- 所以假设咱们能把事物的特点和行为表明出来,那么就能够笼统出来这个事物
- 比方咱们要表明人这个目标,在C言语中,咱们能够这么表明:
typedef struct _Person{ char name[64]; int age; }Person; typedef struct _Aninal{ char name[64]; int age; int type; //动物品种 }Ainmal; void PersonEat(Person* person){ printf("%s在吃人吃的饭!\n",person->name); } void AnimalEat(Ainmal* animal){ printf("%s在吃动物吃的饭!\n", animal->name); } int main(){ Personperson; strcpy(person.name, "小明"); person.age = 30; AnimalEat(&person); return EXIT_SUCCESS; }
- 界说一个结构体用来表明一个目标所包含的特点,函数用来表明一个目标所具有的行为,这样咱们就表明出来一个事物
-
C言语
的特点
和行为
- 在C言语中,行为和特点是分隔的,也便是说吃饭这个特点不归于某类目标,而归于一切的共同的数据
- 所以不单单是PeopleEat能够调用Person数据,AnimalEat也能够调用Person数据,那么万一调用过错,将会导致问题发生。
-
聚合特点和行为
- 从这个事例咱们应该能够体会到,特点和行为应该放在一同,一同表明一个具有特点和行为的目标
- 这个聚合操作,咱们用专业术语封装来表述
- 实践世界的事物所具有的共性便是,每个事物都具有
2.2.2 封装
- 假设某目标的某项特点不想被外界获悉,比方说
- 漂亮女孩的年纪不想被其他人知道,那么年纪这条特点应该作为女孩自己知道的特点
- 或许女孩的某些行为不想让外界知道,只需求自己知道就能够
- 那么这种状况下,封装应该再供给一种机制能够给特点和行为的拜访权限操控住。
- 所以说封装特性包含两个方面
- 一个是特点和变量组成一个全体
- 一个是给特点和函数添加拜访权限
- 面向目标特性|封装
-
- 把变量(特点)和函数(操作)组成一个全体,封装在一个类中
-
- 对变量和函数进行拜访操控
-
- 拜访权限
-
- 在
类的内部
(效果域规模内)
没有拜访权限之分,一切成员能够彼此拜访
- 在
-
- 在
类的外部
(效果域规模外)
拜访权限才有含义:public
,private
,protected
- 在
-
- 在
类的外部
只要public
润饰的成员才干被拜访
在没有触及承继与派生时,private
和protected
是同等级的,外部不允许拜访
- 在
- 事例:
//封装两层含义 //1. 特点和行为组成一个全体 //2. 拜访操控,实践事物自身有些特点和行为是不对外开放 class Person { //人具有的行为(函数) public: void Dese() { cout << "我有钱,年轻,个子又高,就爱嘚瑟!" << endl; } //人的特点(变量) public: int mTall; //多高,能够让外人知道 protected: int mMoney; // 有多少钱,只能儿子孙子知道 private: int mAge; //年纪,不想让外人知道 }; int main(){ Person p; p.mTall = 220; //p.mMoney 保护成员外部无法拜访 //p.mAge 私有成员外部无法拜访 p.Dese(); return EXIT_SUCCESS; }
-
- 成员拜访权限|总结
- 成员拜访权限、承继办法有3种
- public:公共的,任何地方都能够拜访(struct默许)
- protected:子类内部、当时类内部能够拜访
- private:私有的,只要当时类内部能够拜访(class默许)
- 子类内部拜访父类成员的权限,是以下2项中权限最小的那个
- 成员自身的拜访权限
- 上一级父类的承继办法
- 开发顶用的最多的承继办法是public,这样能保留父类原来的成员拜访权限
- 拜访权限不影响目标的内存布局
- 成员拜访权限、承继办法有3种
- 封装Tips
- 成员变量私有化,供给公共的getter和setter给外界去拜访成员变量
2.2.3 struct和class的差异?
class A{
int mAge;
};
struct B{
int mAge;
};
void test(){
Aa;
Bb;
//a.mAge; //无法拜访私有成员
b.mAge; //可正常外部拜访
}
- 在 C++ 中,
struct
和class
都表明类,可是它们不太相同: -
class
默许拜访权限为private
-
struct
默许拜访权限为public
- 主张一般用
class
来做类的声明标识符,在有需求的时分,才经过拜访权限来对外露出特点,以保证类的封装性
2.3 将成员变量设置为 private
-
- 可赋予客户端拜访数据的一致性
- 假设成员变量不是
public
- 客户端仅有能够拜访目标的办法便是经过成员函数
- 假设类中一切public权限的成员都是函数
- 客户在拜访类成员时只会默许拜访函数
- 不需求考虑拜访的成员需不需求添加(),这就省下了许多搔首弄耳的时刻
-
- 可细微划分拜访操控
- 运用成员函数可使得咱们对变量的操控处理愈加精密。
- 假设咱们让一切的成员变量为
public
- 每个人都能够读写它。
- 假设咱们设置为
private
,咱们能够- 完结“
不准拜访
”、“只读拜访
”、“读写拜访
”,甚至你能够写出“只写拜访
”。
- 完结“
- 代码示例:
class AccessLevels{ public: //对只读特点进行只读拜访 int getReadOnly(){ return readOnly; } //对读写特点进行读写拜访 void setReadWrite(int val){ readWrite = val; } int getReadWrite(){ return readWrite; } //对只写特点进行只写拜访 void setWriteOnly(int val){ writeOnly = val; } private: int readOnly; //对外只读拜访 int noAccess; //外部不行拜访 int readWrite; //读写拜访 int writeOnly; //只写拜访 };
- 操练:
- 规划一个Person类,Person类具有name和age特点,供给初始化函数(Init),并供给对name和age的读写函数(set,get),但有必要保证age的赋值在有用规模内(0-100),超出有用规模,则拒绝赋值,并供给办法输出姓名和年纪.(10分钟)
2.4 类|总结
-
C++
中能够运用struct
、class
来界说一个类 -
struct
和class
的差异- struct的默许成员权限是public
- class的默许成员权限是private
- 代码示例:
- 上面代码中person目标、p指针的内存都是在函数的栈空间,主动分配和收回的
- 能够尝试反汇编struct和class,看看是否有其他差异
- 实践开发中,用class表明类比较多
三、 面向目标程序规划事例
1. 规划立方体类
- 规划立方体类(Cube),求出立方体的面积( 2ab + 2ac + 2bc )和体积( a* b * c),分别用大局函数和成员函数判别两个立方体是否持平
- 代码示例:
//立方体类 class Cub{ public: void setL(int l){ mL = l; } void setW(int w){ mW = w; } void setH(int h){ mH = h; } int getL(){ return mL; } int getW(){ return mW; } int getH(){ return mH; } //立方体面积 int caculateS(){ return (*mLmW *+ mLmH + mWmH) 2; } //立方体体积 int caculateV(){ return mL * mW * mH; } //成员办法 bool CubCompare(Cub& c){ if (getL() == c.getL() && getW() == c.getW() && getH() == c.getH()){ return true; } return false; } private: int mL; //长 int mW; //宽 int mH; //高 }; //比较两个立方体是否持平 bool CubCompare(Cub& c1, Cub& c2){ if (c1.getL() == c2.getL() && c1.getW() == c2.getW() && c1.getH() == c2.getH()){ return true; } return false; } void test(){ Cubc1, c2; c1.setL(10); c1.setW(20); c1.setH(30); c2.setL(20); c2.setW(20); c2.setH(30); cout<< "c1面积:" << c1.caculateS() << " 体积:" << c1.caculateV() << endl; cout<< "c2面积:" << c2.caculateS() << " 体积:" << c2.caculateV() << endl; //比较两个立方体是否持平 if (CubCompare(c1, c2)){ cout<< "c1和c2持平!" << endl; } else { cout<< "c1和c2不持平!" << endl; } if (c1.CubCompare(c2)){ cout<< "c1和c2持平!" << endl; } else { cout<< "c1和c2不持平!" << endl; } }
2. 点和圆的联系
- 规划一个圆形类(AdvCircle),和一个点类(Point),核算点和圆的联系。
- 假设圆心坐标为x0, y0, 半径为r,点的坐标为x1, y1:
-
- 点在圆上:
(x1-x0)(x1-x0) + (y1-y0)(y1-y0) == r*r
- 点在圆上:
- 2)点在圆内:
(x1-x0)*(x1-x0) + (y1-y0)*(y1-y0) < r*r
- 3)点在圆外:
(x1-x0)*(x1-x0) + (y1-y0)*(y1-y0) > r*r
-
- 代码示例:
//点类
class Point{
public:
void setX(int x){ mX = x; }
void setY(int y){ mY = y; }
int getX(){ return mX; }
int getY(){ return mY; }
private:
int mX;
int mY;
};
//圆类
class Circle{
public:
void setP(int x,int y) {
mP.setX(x);
mP.setY(y);
}
void setR(int r){ mR = r; }
Point& getP(){ return mP; }
int getR(){ return mR; }
//判别点和圆的联系
void IsPointInCircle(Point& point) {
int distance = (point.getX() - mP.getX()) * *(point.getX() - mP.getX()) + (point.getY() - mP.getY()) (point.getY() - mP.getY());
int radius = mR * mR;
if (distance < radius){
cout<< "Point(" << point.getX() << "," << point.getY() << ")在圆内!" << endl;
} else if (distance > radius){
cout<< "Point(" << point.getX() << "," << point.getY() << ")在圆外!" << endl;
} else {
cout<< "Point(" << point.getX() << "," << point.getY() << ")在圆上!" << endl;
}
}
private:
PointmP; //圆心
int mR; //半径
};
void test(){
//实例化圆目标
Circlecircle;
circle.setP(20, 20);
circle.setR(5);
//实例化点目标
Pointpoint;
point.setX(25);
point.setY(20);
circle.IsPointInCircle(point);
}
四、目标的结构和析构
1. 初始化和整理
-
初始设置
- 咱们大家在购买一台电脑或许手机,或许其他的产品,这些产品都有一个初始设置
- 也便是这些产品对被创立的时分会有一个根底特点值
-
删去信息数据
- 那么跟着咱们运用手机和电脑的时刻越来越久,那么电脑和手机会慢慢被咱们手动创立许多文件数据
- 某一天咱们不必手机或电脑了,那么咱们应该将电脑或手机中咱们添加的数据删去掉,保护自己的信息数据
-
初始化和整理
- 从这样的过程中,咱们体会一下,一切的事物在起先的时分都应该有个初始状况,当这个事物完结其任务时,应该及时清除外界效果于上面的一些信息数据
- 那么咱们C++中OO思维也是来源于实践,是对实践事物的笼统模仿。具体来说:
- 当咱们创立目标的时分,这个目标应该有一个初始状况
- 当目标毁掉之前应该毁掉自己创立的一些数据
- 目标的初始化和整理也是两个十分重要的安全问题
- 一个目标或许变量没有初始时,对其运用成果是不知道
- 同样的运用完一个变量,没有及时整理,也会造成一定的安全问题
- C++为了给咱们供给这种问题的处理计划:
结构函数
和析构函数
- 这两个函数将会被编译器主动调用,完结目标
初始化
和目标整理
作业
- 不论你是否喜爱,目标的初始化和整理作业是编译器强制咱们要做的事情
- 即便你不供给初始化操作和整理操作,编译器也会给你添加默许的操作
- 这个默许初始化操作不会做任何事,所以编写类就应该趁便供给初始化函数
- 为什么初始化操作是主动调用而不是手动调用?
- 既然是有必要操作,那么主动调用会更好
- 假设靠程序员自觉,那么就会存在遗失初始化的状况呈现
2. 结构函数和析构函数
2.1 结构函数
-
结构函数(也叫结构器)
- 在目标创立的时分主动调用,一般用于完结目标的初始化作业
- 首要效果在于创立目标时为目标的成员特点赋值
- 结构函数由编译器主动调用,无须手动调用
-
结构函数语法:
- 函数名与类同名
- 无回来值(void都不能写)
- 能够有参数
- 能够重载
- 能够有多个结构函数
ClassName(){}
- 一旦自界说了结构函数,有必要用其间一个自界说的结构函数来初始化目标
- 留意
- 经过
malloc
分配的目标不会调用结构函数
- 经过
- 一个广为流传的、许多教程/书籍都推重的过错定论:
- 默许状况下,编译器会为每一个类生成空的无参的结构函数
- 正确了解:在某些特定的状况下,编译器才会为类生成空的无参的结构函数
- (哪些特定的状况?今后再提)
- 结构函数的调用
- 默许状况下,成员变量的初始化
- 假设自界说了结构函数,除了大局区,其他内存空间的成员变量默许都不会被初始化,需求开发人员手动初始化
- 成员变量的初始化
- 目标初始化
2.2 析构函数
-
析构函数(也叫析构器)
- 析构函数首要用于目标
毁掉前
系统主动调用 - 履行一些整理作业。
- 析构函数首要用于目标
-
特点
- 函数名以~开头,与类同名
- 无回来值(void都不能写)
- 无参
- 不能够重载,有且只要一个析构函数
~ClassName(){}
-
留意
- 经过malloc分配的目标free的时分不会调用析构函数
- 结构函数、析构函数要声明为
public
,才干被外界正常运用- 目标内部请求的堆空间,由目标内部收回
2.3 代码示例
class Person{
public:
Person(){
cout << "结构函数调用!" << endl;
pName = (*char)*malloc(sizeof("John"));
strcpy(pName, "John");
mTall = 150;
mMoney = 100;
}
~Person(){
cout << "析构函数调用!" << endl;
if (pName != NULL){
free(pName);
pName = NULL;
}
}
public:
char pName;
int mTall;
int mMoney;
};
void test(){
Person person;
cout << person.pName << person.mTall << person.mMoney << endl;
}
3. 结构函数的分类及调用
-
按参数类型:
- 分为
无参结构函数
和有参结构函数
- 分为
- 按类型分类:
-
一般结构函数
和仿制结构函数
(仿制结构函数)
-
- 代码示例:
class Person{ public: Person(){ cout<< "no param constructor!" << endl; mAge= 0; } //有参结构函数 Person(int age){ cout<< "1 param constructor!" << endl; mAge= age; } //仿制结构函数(仿制结构函数) 运用另一个目标初始化本目标 Person(const Person& person){ cout<< "copy constructor!" << endl; mAge= person.mAge; } //打印年纪 void PrintPerson(){ cout<< "Age:" << mAge << endl; } private: int mAge; }; //1. 无参结构调用办法 void test01(){ //调用无参结构函数 Personperson1; person1.PrintPerson(); //无参结构函数过错调用办法 //Person person2(); //person2.PrintPerson(); } //2. 调用有参结构函数 void test02(){ //第一种 括号法,最常用 Personperson01(100); person01.PrintPerson(); //调用仿制结构函数 Personperson02(person01); person02.PrintPerson(); //第二种 匿名目标(显现调用结构函数) Person(200); //匿名目标,没有姓名的目标 Personperson03 = Person(300); person03.PrintPerson(); //留意: 运用匿名目标初始化判别调用哪一个结构函数,要看匿名目标的参数类型 Personperson06(Person(400)); //等价于 Person person06 = Person(400); person06.PrintPerson(); //第三种 =号法 隐式转化 Personperson04 = 100; //Person person04 = Person(100) person04.PrintPerson(); //调用仿制结构 Personperson05 = person04; //Person person05 = Person(person04) person05.PrintPerson(); }
-
b为A的实例化目标,A a = A(b) 和 A(b)的差异?
- 当A(b) 有变量来接的时分,那么编译器认为他是一个匿名目标
- 当没有变量来接的时分,编译器认为你A(b) 等价于 A b.
- 留意: 不能调用仿制结构函数去初始化匿名目标,也便是说以下代码不正确:
class Teacher{ public: Teacher(){ cout<< "默许结构函数!" << endl; } Teacher(const Teacher& teacher) { cout<< "仿制结构函数!" << endl; } public: int mAge; }; void test(){ Teachert1; //error C2086:“Teacher t1”: 重界说 Teacher(t1); //此时等价于 Teacher t1; }
4. 声明和完结
- 声明和完结分离:
5. 仿制结构函数的调用时机
- 目标以
值传递
的办法传给函数参数
- 函数部分目标以值传递的办法从函数回来(vs debug形式下调用一次仿制结构,qt不调用任何结构)
- 用一个目标初始化另一个目标
class Person{ public: Person(){ cout<< "no paramcontructor!" << endl; mAge= 10; } Person(int age){ cout<< "param constructor!" << endl; mAge= age; } Person(const Person& person){ cout<< "copy constructor!" << endl; mAge= person.mAge; } ~Person(){ cout<< "destructor!" << endl; } public: int mAge; }; //1. 旧目标初始化新目标 void test01(){ Personp(10); Personp1(p); Personp2 = Person(p); Personp3 = p; // 相当于Person p2 = Person(p); } //2. 传递的参数是一般目标,函数参数也是一般目标,传递将会调用仿制结构 void doBussiness(Person p){} void test02(){ Personp(10); doBussiness(p); } //3. 函数回来部分目标 Person MyBusiness(){ Personp(10); cout<< "部分p:" << (int*)&p << endl; return p; } void test03(){ //vs release、qt下没有调用仿制结构函数 //vs debug下调用一次仿制结构函数 Personp = MyBusiness(); cout<< "部分p:" << (int*)&p << endl; }
-
Test03成果阐明:
- 编译器存在一种对回来值的优化技能,RVO(Return Value Optimization).
- 在vs debug形式下并没有进行这种优化
- 所以函数MyBusiness中创立p目标,调用了一次结构函数,
- 当编译器发现你要回来这个部分的目标时,编译器经过调用仿制结构生成一个暂时Person目标回来,然后调用p的析构函数
- 咱们从常理来剖析的话,这个匿名目标和这个部分的p目标是相同的两个目标,
- 那么假设能直接回来p目标,就会省去一个仿制结构和一个析构函数的开支
- 在程序中一个目标的仿制也是十分耗时的
- 假设削减这种仿制和析构的次数,那么从另一个角度来说,也是编译器对程序履行功率上进行了优化
- 所以在这里,编译器悄悄帮咱们做了一层优化:
- 当咱们这样去调用: Person p = MyBusiness();
- 编译器悄悄将咱们的代码更改为:
void MyBussiness(Person& _result){ _result.X:X(); //调用Person默许仿制结构函数 //.....对_result进行处理 return; } int main(){ Person p; //这里只分配空间,不初始化 MyBussiness(p); }
- 编译器存在一种对回来值的优化技能,RVO(Return Value Optimization).
6. 结构函数调用规矩
- 默许状况下,C++编译器至少为咱们写的类添加3个函数
- 1.
默许结构函数
(无参,函数体为空) - 2.
默许析构函数
(无参,函数体为空) - 3.
默许仿制结构函数
,对类中非静态成员特点简略值仿制
- 1.
- 假设用户界说仿制结构函数,C++不会再供给任何默许结构函数
- 假设用户界说了一般结构(非仿制),C++不再供给默许无参结构,可是会供给默许仿制结构
7.深仿制和浅仿制
7.1 浅仿制
- 同一类型的目标之间能够赋值,使得两个目标的成员变量的值相同,两个目标仍然是独立的两个目标,这种状况被称为
浅仿制
- 一般状况下,浅仿制没有任何副效果,可是当类中有指针,并且指针指向动态分配的内存空间,析构函数做了动态内存开释的处理,会导致内存问题。
7.2 深仿制
- 当类中有指针,并且此指针有动态分配空间,析构函数做了开释处理,往往需求自界说仿制结构函数,自行给指针动态分配空间,
深仿制
。 - 代码示例:
class Person{ public: Person(*char name*,int age){ pName = (char)malloc(strlen(name) + 1); strcpy(pName,name); mAge = age; } //添加仿制结构函数 Person(const Person& person){ pName = (*char)*malloc(strlen(person.pName) + 1); strcpy(pName, person.pName); mAge = person.mAge; } ~Person(){ if (pName != NULL){ free(pName); } } private: char pName; int mAge; }; void test(){ Person p1("Edward",30); //用目标p1初始化目标p2,调用c++供给的默许仿制结构函数 Person p2 = p1; }
8. 多个目标结构和析构
8.1 初始化列表
- 结构函数和其他函数不同,除了有姓名,参数列表,函数体之外还有初始化列表。
- 初始化列表简略运用:
class Person{ public: #if 0 //传统办法初始化 Person(int a,int b,int c) { mA= a; mB= b; mC= c; } #endif //初始化列表办法初始化 Person(int a, int b, int c):mA(a),mB(b),mC(c){} void PrintPerson() { cout<< "mA:" << mA << endl; cout<< "mB:" << mB << endl; cout<< "mC:" << mC << endl; } private: int mA; int mB; int mC; };
-
留意
:初始化成员列表(参数列表)只能在结构函数运用。
8.2 类目标作为成员
-
类中的成员
- 在类中界说的数据成员一般都是根本的数据类型
- 可是类中的成员也能够是目标,叫做目标成员。
-
目标的初始化
- C++中对目标的初始化是十分重要的操作,当创立一个目标的时分,C++编译器有必要保证调用了一切子目标的结构函数
- 假设一切的子目标有默许结构函数,编译器能够主动调用他们
- 可是假设子目标没有默许的结构函数,或许想指定调用某个结构函数怎么办?
- 那么是否能够在类的结构函数直接调用子类的特点完结初始化呢?
- 可是假设子类的成员特点是私有的,咱们是没有办法拜访并完结初始化的。
- 处理办法十分简略:
- 关于子类调用结构函数,C++为此供给了专门的语法,即
结构函数初始化列表
- 当调用结构函数时
- 首要按各目标成员在类
界说中的次序(和参数列表的次序无关
)依次调用它们的结构函数,对这些目标初始化 - 最终再调用自身的函数体
- 也便是说,
先调用目标成员的结构函数,再调用自身的结构函数
- 首要按各目标成员在类
- 关于子类调用结构函数,C++为此供给了专门的语法,即
-
目标的反初始化
- 析构函数和结构函数调用次序相反,先结构,后析构。
- 代码示例:
//轿车类 class Car { public: Car(){ cout<< "Car 默许结构函数!" << endl; mName= "大众轿车"; } Car(string name){ cout<< "Car 带参数结构函数!" << endl; mName= name; } ~Car(){ cout<< "Car 析构函数!" << endl; } public: string mName; }; //拖拉机 class Tractor { public: Tractor(){ cout<< "Tractor 默许结构函数!" << endl; mName= "爬土坡专用拖拉机"; } Tractor(string name) { cout<< "Tractor 带参数结构函数!" << endl; mName= name; } ~Tractor() { cout<< "Tractor 析构函数!" << endl; } public: string mName; }; //人类 class Person{ public: #if 1 //类mCar不存在适宜的结构函数 Person(string name){ mName= name; } #else //初始化列表能够指定调用结构函数 Person(string carName, string tracName, string name) : mTractor(tracName), mCar(carName), mName(name){ cout<< "Person 结构函数!" << endl; } #endif void GoWorkByCar(){ cout<< mName << "开着" << mCar.mName << "去上班!" << endl; } void GoWorkByTractor() { cout<< mName << "开着" << mTractor.mName << "去上班!" << endl; } ~Person(){ cout<< "Person 析构函数!" << endl; } private: string mName; Car mCar; Tractor mTractor; }; void test(){ //Person person("宝马", "东风拖拉机", "赵四"); Personperson("刘能"); person.GoWorkByCar(); person.GoWorkByTractor(); }
9. explicit
关键字
- C++供给了关键字explicit,制止经过结构函数进行的隐式转化
- 声明为explicit的结构函数不能在隐式转化中运用。
- explicit留意
- explicit用于润饰结构函数,避免隐式转化
- 是针对单参数的结构函数(或许除了第一个参数外其余参数都有默许值的多参结构)而言
- 代码示例:
class MyString{ public: explicit MyString(int n){ cout<< "MyString(int n)!" << endl; } MyString(*const char str*){ cout<< "MyString(const char str)" << endl; } }; int main(){ //给字符串赋值?仍是初始化? //MyString str1 = 1; MyStringstr2(10); //涵义十分明确,给字符串赋值 MyStringstr3 = "abcd"; MyStringstr4("abcd"); return EXIT_SUCCESS; }
10. 动态目标创立
-
数组目标事例剖析:
- 当咱们创立数组的时分,总是需求提早预定数组的长度,然后编译器分配预定长度的数组空间
- 在运用数组的时,会有这样的问题
- 数组或许空间太大了,糟蹋空间,或许空间不足
- 所以关于数组来讲,
假设能根据需求来分配空间巨细再好不过
-
动态性问题
- 所以动态的意思意味着不确认性
- 为了处理这个普遍的编程问题,在运转中能够创立和毁掉目标是最根本的要求
- 当然
C
早就供给了动态内存分配(dynamic memory allocation)- 函数
malloc
和free
能够 在运转时从堆中分配存储单元
- 函数
- 然而这些函数在
C++
中不能很好的运转,因为它不能帮咱们完结目标的初始化作业。
内存
- 每个运用都有自己独立的内存空间,其内存空间一般都有以下几大区域
- 代码段(代码区)
- 用于寄存代码
- 数据段(大局区)
- 用于寄存大局变量等
- 栈空间
- 每调用一个函数就会给它分配一段连续的栈空间,等函数调用完毕后会主动收回这段栈空间
- 主动分配和收回
- 堆空间
- 需求主动去请求和开释
- 代码段(代码区)
-
堆空间
- 在程序运转过程,为了能够自由操控内存的生命周期、巨细,会常常运用堆空间的内存
- 堆空间的请求\开释
malloc \ free
new \ delete
new [] \ delete []
- 留意
- 请求堆空间成功后,会回来那一段内存空间的地址
- 请求和开释有必要是1对1的联系,否则可能会存在内存走漏
- 现在的许多高档编程言语不需求开发人员去办理内存(比方Java),屏蔽了许多内存细节,利弊一起存在
- 利:进步开发功率,避免内存运用不当或走漏
- 弊:不利于开发人员了解实质,永远停留在API调用和表层语法糖,对功能优化无从下手
- 下图是X86环境(32bit)
- 堆空间的初始化
-
memset
- memset函数是将较大的数据结构(比方目标、数组等)内存清零的比较快的办法
-
- 目标的内存
- 目标的内存能够存在于3种地方
- 大局区(数据段):大局变量
- 栈空间:函数里面的部分变量
- 堆空间:动态请求内存(malloc、new等)
10.1 目标创立
- 当创立一个
C++
目标时会发生两件事:-
- 为目标分配内存
-
- 调用结构函数来初始化那块内存
-
- 第一步咱们能保证完结,需求咱们保证第二步一定能发生
-
C++
强迫咱们这么做是因为运用未初始化的目标是程序出错的一个重要原因
10.2 目标的内存布局
- 考虑:假设类中有多个成员变量,目标的内存又是怎么布局的?
10.3 C动态分配内存办法
-
C动态分配事例剖析
- 为了在运转时动态分配内存,C在他的标准库中供给了一些函数:
-
malloc
以及它的变种calloc
和realloc
,开释内存的free
- 这些函数是有用的、可是原始的,需求程序员了解和小心运用
- 为了运用C的动态内存分配函数在堆上创立一个类的实例,咱们有必要这样做:
class Person{ public: Person(){ mAge= 20; pName= (*char)*malloc(strlen("john")+1); strcpy(pName, "john"); } void Init(){ mAge= 20; pName= (char)malloc(strlen("john")+1); strcpy(pName, "john"); } void Clean(){ if (pName != NULL){ free(pName); } } public: int mAge; char* pName*; }; int main() { //分配内存 Person person = *(Person)malloc(sizeof(Person)); if(person == NULL){ return 0; } //调用初始化函数 person->Init(); //整理目标 person->Clean(); //开释person目标 free(person); return EXIT_SUCCESS; }
- 问题:
-
- 程序员有必要确认目标的长度
-
- malloc回来一个
void*
指针,C++不允许将void*
赋值给其他任何指针,有必要强转
- malloc回来一个
-
- malloc可能请求内存失败,所以有必要判别回来值来保证内存分配成功
-
- 用户在运用目标之前有必要记住对他初始化,结构函数不能显现调用初始化(结构函数是由编译器调用),用户有可能忘掉调用初始化函数
-
-
C
的动态内存分配函数太复杂,简略令人混淆,是不行承受的,C++中咱们推荐运用运算符new
和delete
10.4 new
operator
-
C++
中处理动态内存分配的计划是把创立一个目标所需求的操作都结合在一个称为new
的运算符里 - 当用new创立一个目标时,它就在堆里为目标分配内存并调用结构函数完结初始化
Person person = new Person; 相当于: Person person = (Person*)malloc(sizeof(Person)); if(person == NULL){ return 0; } person->Init();
-
new
操作符能确认在调用结构函数初始化之前内存分配是成功的,一切不必显式确认调用是否成功。 - 现在咱们发现在堆里创立目标的过程变得简略了,只需求一个简略的表达式,它带有内置的长度核算、类型转化和安全查看。这样在堆创立一个目标和在栈里创立目标相同简略。
10.5 delete operator
-
new
表达式的反面是delete
表达式 -
delete
表达式 先调用析构函数,然后开释内存 - 正如
new
表达式回来一个指向目标的指针相同,delete
需求一个目标的地址 -
delete
只适用于由new
创立的目标 - 假设运用一个由
malloc
或许calloc
或许realloc
创立的目标运用delete
,这个行为是未界说的 - 因为大多数
new
和delete
的完结机制都运用了malloc
和free
,所以很可能没有调用析构函数就开释了内存 - 假设正在删去的目标的指针是
NULL
,将不发生任何事- 因而主张在删去指针后,立即把指针赋值为NULL,避免对它删去两次
- 对一些目标删去两次可能会发生某些问题
- 代码示例:
class Person { public: Person(){ cout << "无参结构函数!" << endl; pName = (char*)malloc(strlen("undefined") + 1); strcpy(pName, "undefined"); mAge = 0; } Person(char* name, int age){ cout << "有参结构函数!" << endl; pName= (char*)malloc(strlen(name) + 1); strcpy(pName, name); mAge = age; } void ShowPerson(){ cout << "Name:" << pName << "Age:" << mAge << endl; } ~Person(){ cout << "析构函数!" << endl; if (pName != NULL){ delete pName; pName = NULL; } } public: char* pName; int mAge; }; void test(){ Person* person1 = new Person; Person* person2 = new Person("John",33); person1->ShowPerson(); person2->ShowPerson(); delete person1; delete person2; }
10.6 用于数组的new和delete
- 运用
new
和delete
在堆上创立数组十分简略
- 当创立一个目标数组的时分,有必要对数组中的每一个目标调用结构函数,除了在栈上能够聚合初始化,有必要供给一个默许的结构函数
- 代码示例:
class Person{ public: Person(){ pName= (*char)*malloc(strlen("undefined") + 1); strcpy(pName, "undefined"); mAge= 0; } Person(char name, int age){ pName= (*char)*malloc(sizeof(name)); strcpy(pName, name); mAge= age; } ~Person(){ if (pName != NULL){ delete pName; } } public: char pName; int mAge; }; void test(){ //栈聚合初始化 Person person[] = { Person("john", 20), Person("Smith", 22) }; cout<< person[1].pName << endl; //创立堆上目标数组有必要供给结构函数 Person* workers = new Person[20]; }
10.7 delete void*
可能会出错
- 假设对一个
void*
指针履行delete
操作,这将可能成为一个程序过错,除非指针指向的内容是十分简略的,因为它将不履行析构函数.以下代码未调用析构函数,导致可用内存削减class Person{ public: Person(*char name*, int age){ pName= (char)malloc(sizeof(name)); strcpy(pName,name); mAge= age; } ~Person(){ if (pName != NULL){ delete pName; } } public: char* pName*; int mAge; }; void test(){ void person = new Person("john",20); delete person; }
- 问题:malloc、free和new、delete能够混搭运用吗?也便是说malloc分配的内存,能够调用delete吗?经过new创立的目标,能够调用free来开释吗?
10.8 运用new和delete采用相同形式
- 代码示例:
Person * person = new Person[10]; delete person;
- 以上代码有什么问题吗?(vs下直接中断、qt下析构函数调用一次)
- 运用了
new
也搭配运用了delete
,问题在于Person
有10个目标,那么其他9个目标可能没有调用析构函数,也便是说其他9个目标可能删去不完全,因为它们的析构函数没有被调用。 - 咱们现在清楚运用
new
的时分发生了两件事:- 一、分配内存
- 二、调用结构函数
- 那么调用
delete
的时分也有两件事:- 一、析构函数;
- 二、开释内存。
- 那么方才咱们那段代码最大的问题在于:
- person指针指向的内存中到底有多少个目标,因为这个决议应该有多少个析构函数应该被调用。
- 换句话说,person指针指向的是一个单一的目标仍是一个数组目标,因为单一目标和数组目标的内存布局是不同的。
- 更明确的说,数组所用的内存一般还包含“数组巨细记载”,使得delete的时分知道应该调用几回析构函数。
- 单一目标的话就没有这个记载。
- 单一目标和数组目标的内存布局可了解为下图:
- 本图仅仅为了阐明,编译器不一定如此完结,可是许多编译器是这样做的
- 当咱们运用一个delete的时分,咱们有必要让delete知道指针指向的内存空间中是否存在一个“数组巨细记载”的办法便是咱们告诉它。当咱们运用delete[],那么delete就知道是一个目标数组,然后清楚应该调用几回析构函数。
- 定论:
- 假设在new表达式中运用[],有必要在相应的delete表达式中也运用[].假设在new表达式中不运用[], 一定不要在相应的delete表达式中运用[].
11. 静态成员
在类界说中,它的成员(包含成员变量和成员函数),这些成员能够用关键字static声明为静态的,称为静态成员。
不论这个类创立了多少个目标,静态成员只要一个仿制,这个仿制被一切归于这个类的目标同享
11.1 静态成员变量
- 在一个类中,若将一个成员变量声明为static,这种成员称为静态成员变量。
- 与一般的数据成员不同,不论树立了多少个目标,都只要一个静态数据的仿制。静态成员变量,归于某个类,一切目标同享
- 静态变量,是在编译阶段就分配空间,目标还没有创立时,就已经分配空间。
- 静态成员变量有必要在类中声明,在类外界说
- 静态数据成员不归于某个目标,在为目标分配空间中不包含静态成员所占空间
- 静态数据成员能够经过类名或许目标名来引证
- 代码示例:
class Person{ public: //类的静态成员特点 static int sNum; private: static int sOther; }; //类外初始化,初始化时不加static int Person::sNum = 0; int Person::sOther = 0; int main() { //1. 经过类名直接拜访 Person::sNum = 100; cout<< "Person::sNum:" << Person::sNum << endl; //2. 经过目标拜访 Personp1, p2; p1.sNum = 200; cout<< "p1.sNum:" << p1.sNum << endl; cout<< "p2.sNum:" << p2.sNum << endl; //3. 静态成员也有拜访权限,类外不能拜访私有成员 //cout <<"Person::sOther:" << Person::sOther << endl; Personp3; //cout <<"p3.sOther:" << p3.sOther << endl; system("pause"); return EXIT_SUCCESS; }
11.2 静态成员函数
- 在类界说中,前面有static阐明的成员函数称为静态成员函数。静态成员函数运用办法和静态变量相同,同样在目标没有创立前,即可经过类名调用。静态成员函数首要为了拜访静态变量,可是,不能拜访一般成员变量。
- 静态成员函数的含义,不在于信息同享,数据沟通,而在于办理静态数据成员,完结对静态数据成员的封装
- 静态成员函数只能拜访静态变量,不能拜访一般成员变量
- 静态成员函数的运用和静态成员变量相同
- 静态成员函数也有拜访权限
- 一般成员函数可拜访静态成员变量、也能够拜访非常常成员变量
- 代码示例:
class Person{
public:
//一般成员函数能够拜访static和non-static成员特点
void changeParam1(int param){
mParam= param;
sNum= param;
}
//静态成员函数只能拜访static成员特点
static void changeParam2(int param){
//mParam = param; //无法拜访
sNum= param;
}
private:
static void changeParam3(int param){
//mParam = param; //无法拜访
sNum= param;
}
public:
int mParam;
static int sNum;
};
//静态成员特点类外初始化
int Person::sNum = 0;
int main(){
//1. 类名直接调用
Person::changeParam2(100);
//2. 经过目标调用
Personp;
p.changeParam2(200);
//3. 静态成员函数也有拜访权限
//Person::changeParam3(100);//类外无法拜访私有静态成员函数
//Person p1;
//p1.changeParam3(200);
return EXIT_SUCCESS;
}
11.3 const静态成员特点
- 假设一个类的成员,既要完结同享,又要完结不行改动,那就用static const 润饰。界说静态const数据成员时,最好在类内部初始化。
- 代码示例:
class Person{ public: //static const int mShare =10; const static int mShare = 10; //只读区,不行修正 }; int main(){ cout<< Person::mShare << endl; //Person::mShare = 20; return EXIT_SUCCESS; }
11.4 静态成员完结单例形式
- 单例形式是一种常用的软件规划形式
- 在它的中心结构中只包含一个被称为单例的特别类。
- 经过单例形式能够保证系统中一个类只要一个实例而且该实例易于外界拜访,然后便利对实例个数的操控并节约系统资源。
- 假设希望在系统中某个类的目标只能存在一个,单例形式是最好的处理计划。
- Singleton(单例):
- 在单例类的内部完结只生成一个实例,一起它供给一个静态的getInstance()工厂办法,让客户能够拜访它的仅有实例;
- 为了避免在外部对其实例化,将其默许结构函数和仿制结构函数规划为私有;
- 在单例类内部界说了一个Singleton类型的静态目标,作为外部同享的仅有实例。
- 事例:
用单例形式,模仿公司职工运用打印机场景,打印机能够打印职工要输出的内容,并且能够累积打印机运用次数。 - 代码:
class Printer{ public: static Printer* getInstance(){ return pPrinter;} void PrintText(string text){ cout<< "打印内容:" << text << endl; cout<< "已打印次数:" << mTimes << endl; cout<< "--------------" << endl; mTimes++; } private: Printer(){ mTimes = 0; } Printer(const Printer&){} private: static Printer* pPrinter; int mTimes; }; Printer* Printer::pPrinter = new Printer; void test(){ Printer* printer = Printer::getInstance(); printer->PrintText("离职报告!"); printer->PrintText("入职合同!"); printer->PrintText("提交代码!"); }
五、目标相关|大杂烩
1. 目标的内存布局
- 父类的成员变量在前,子类的成员变量在后
2. 初始化列表
- 特点
- 一种便捷的初始化成员变量的办法
- 只能用在结构函数中
- 初始化次序只跟成员变量的声明次序有关
- 图片中的2种写法是等价的
-
考虑
- m_age、m_height的值是多少
- 10、170
- m_age、m_height的值是多少
- 不知道、180
- m_age、m_height的值是多少
- 初始化列表与默许参数配合运用
- 假设函数声明和完结是分离的
- 初始化列表只能写在函数的完结中
- 默许参数只能写在函数的声明中
- 假设函数声明和完结是分离的
3. 结构函数的互相调用
留意:下面的写法是过错的,初始化的是一个暂时目标
4. 父类的结构函数
- 子类的结构函数默许会调用父类的无参结构函数
- 假设子类的结构函数显式地调用了父类的有参结构函数,就不会再去默许调用父类的无参结构函数
- 假设父类缺少无参结构函数,子类的结构函数有必要显式调用父类的有参结构函数
- 承继系统下的结构函数示例
5. 结构、析构次序
6. 仿制结构函数(Copy Constructor)
- 仿制结构函数是结构函数的一种
- 当运用已存在的目标创立一个新目标时(类似于仿制),就会调用新目标的仿制结构函数进行初始化
- 仿制结构函数的格局是固定的,接纳一个const引证作为参数
7. 调用父类的仿制结构函数
- 仿制结构函数
- car2、car3都是经过仿制结构函数初始化的,car、car4是经过非仿制结构函数初始化
- car4 = car3是一个赋值操作(默许是浅仿制),并不会调用仿制结构函数
8. 浅仿制、深仿制
- 编译器默许的供给的仿制是浅仿制(shallow copy)
- 将一个目标中一切成员变量的值仿制到另一个目标
- 假设某个成员变量是个指针,只会仿制指针中存储的地址值,并不会仿制指针指向的内存空间
- 可能会导致堆空间屡次free的问题
- 假设需求完结深仿制(deep copy),就需求自界说仿制结构函数
- 将指针类型的成员变量所指向的内存空间,仿制到新的内存空间
- 深仿制示例
9. 目标型参数和回来值
- 运用目标类型作为函数的参数或许回来值,可能会发生一些不必要的中间目标
10. 匿名目标(暂时目标)
- 匿名目标:没有变量名、没有被指针指向的目标,用完后马上调用析构
11. 隐式结构
- C++中存在隐式结构的现象:某些状况下,会隐式调用单参数的结构函数
- 能够经过关键字explicit制止掉隐式结构
12. 主动生成的结构函数
- 编译器主动生成的结构函数
- C++的编译器在某些特定的状况下,会给类主动生成无参的结构函数,比方
- 成员变量在声明的一起进行了初始化
- 有界说虚函数
- 虚承继了其他类
- 包含了目标类型的成员,且这个成员有结构函数(编译器生成或自界说)
- 父类有结构函数(编译器生成或自界说)
- 总结一下
- 目标创立后,需求做一些额定操作时(比方内存操作、函数调用),编译器一般都会为其主动生成无参的结构函数
- C++的编译器在某些特定的状况下,会给类主动生成无参的结构函数,比方
六、C++面向目标模型初探
1. 成员变量和函数的存储
- C言语程序成员变量和函数的存储
- 在C言语中,成员变量和函数是“分隔来声明的
- 也便是说,言语自身并没有支持“数据”和“函数”之间的关联性咱们把这种程序办法称为“程序性的”:
- 由一组“分布在各个以功能为导航的函数中”的算法驱动,它们处理的是共同的外部数据
- C++言语程序成员变量和函数的存储
- C++完结了“封装”
- 那么数据(成员特点)和操作(成员函数)是什么样的呢?
- “数据”和“处理数据的操作(函数)”是分隔存储的。
- C++中的非静态数据成员直接内含在类目标中,就像C struct相同。
- 成员函数(member function)虽然内含在class声明之内,却不呈现在目标中。
- 每一个非内联成员函数(non-inline member function)只会诞生一份函数实例
- 代码示例:
class MyClass01{ public: int mA; }; class MyClass02{ public: int mA; static int sB; }; class MyClass03{ public: void printMyClass(){ cout<< "hello world!" << endl; } public: int mA; static int sB; }; class MyClass04{ public: void printMyClass(){ cout<< "hello world!" << endl; } static void ShowMyClass(){ cout<< "hello world!" << endl; } public: int mA; static int sB; }; int main(){ MyClass01mclass01; MyClass02mclass02; MyClass03mclass03; MyClass04mclass04; cout<< "MyClass01:" << sizeof(mclass01) << endl; //4 //静态数据成员并不保存在类目标中 cout<< "MyClass02:" << sizeof(mclass02) << endl; //4 //非静态成员函数不保存在类目标中 cout<< "MyClass03:" << sizeof(mclass03) << endl; //4 //静态成员函数也不保存在类目标中 cout<< "MyClass04:" << sizeof(mclass04) << endl; //4 return EXIT_SUCCESS; }
- 经过上面的事例,咱们能够的得出:C++类目标中的变量和函数是分隔存储
2. this 指针
2.1 this指针作业原理
- 经过上例咱们知道,C++的数据和操作也是分隔存储,并且每一个非内联成员函数(non-inlinemember function)只会诞生一份函数实例,也便是说多个同类型的目标会共用一块代码
- 那么问题是:这一块代码是怎么区别那个目标调用自己的呢?
- C++经过供给特别的目标指针,this指针,处理上述问题。this指针指向被调用的成员函数所属的目标。
- C++规定,this指针是隐含在目标成员函数内的一种指针。
- 当一个目标被创立后,它的每一个成员函数都含有一个系统主动生成的隐含指针this,用以保存这个目标的地址,也便是说虽然咱们没有写上this指针,编译器在编译的时分也是会加上的。
- 因而this也称为“指向本目标的指针”,this指针并不是目标的一部分,不会影响sizeof(目标)的成果。
- this指针是C++完结封装的一种机制,它将目标和该目标调用的成员函数衔接在一同,在外部看来,每一个目标都拥有自己的函数成员。
- 一般状况下,并不写this,而是让系统进行默许设置
- 成员函数经过this指针即可知道操作的是那个目标的数据。
this指针是一种隐含指针,它隐含于每个类的非静态成员函数中
。this指针无需界说,直接运用即可 - 留意: 静态成员函数内部没有this指针,静态成员函数不能操作非静态成员变量
2.2 this指针的运用
- 当形参和成员变量同名时,可用this指针来区别
- 在类的非静态成员函数中回来目标自身,可运用return *this
- 代码示例:
class Person{ public: //1. 当形参名和成员变量名相一起,this指针可用来区别 Person(string name,int age){ //name = name; //age = age; //输出过错 this->name = name; this->age = age; } //2. 回来目标自身的引证 //重载赋值操作符 //其实也是两个参数,其间躲藏了一个this指针 Person PersonPlusPerson(Person& person){ string newname = this->name + person.name; int newage = this->age + person.age; Person newperson(newname, newage); return newperson; } void ShowPerson(){ cout<< "Name:" << name << " Age:" << age << endl; } public: string name; int age; }; //3. 成员函数和大局函数(Perosn目标相加) Person PersonPlusPerson(Person& p1,Person& p2) { stringnewname = p1.name + p2.name; int newage = p1.age + p2.age; Personnewperson(newname,newage); return newperson; } int main(){ Personperson("John",100); person.ShowPerson(); cout<< "---------" << endl; Personperson1("John",20); Personperson2("001", 10); //1.大局函数完结两个目标相加 Personperson3 = PersonPlusPerson(person1, person2); person1.ShowPerson(); person2.ShowPerson(); person3.ShowPerson(); //2. 成员函数完结两个目标相加 Personperson4 = person1.PersonPlusPerson(person2); person4.ShowPerson(); system("pause"); return EXIT_SUCCESS; }
2.3 const润饰成员函数
- 用const润饰的成员函数时,const润饰this指针指向的内存区域,成员函数体内不能够修正本类中的任何一般成员变量,
- 当成员变量类型符前用mutable润饰时例外。
- 代码示例:
//const润饰成员函数 class Person{ public: Person(){ this->mAge = 0; this->mID = 0; } //在函数括号后边加上const,润饰成员变量不行修正,除了mutable变量 void sonmeOperate() const{ //this->mAge = 200; //mAge不行修正 this->mID = 10; } void ShowPerson(){ cout<< "ID:" << mID << " mAge:" << mAge << endl; } private: int mAge; mutable int mID; }; int main(){ Personperson; person.sonmeOperate(); person.ShowPerson(); system("pause"); return EXIT_SUCCESS; }
2.4 const润饰目标(常目标)
- 常目标只能调用const的成员函数
- 常目标可拜访 const 或非 const 数据成员,不能修正,除非成员用mutable润饰
- 代码示例:
class Person{ public: Person(){ this->mAge = 0; this->mID = 0; } void ChangePerson() const{ mAge = 100; mID= 100; } void ShowPerson(){ this->mAge = 1000; cout<< "ID:" << this->mID << " Age:" << this->mAge << endl; } public: int mAge; mutable int mID; }; void test(){ const Person person; //1. 可拜访数据成员 cout<< "Age:" << person.mAge << endl; //person.mAge = 300; //不行修正 person.mID = 1001; //可是能够修正mutable润饰的成员变量 //2. 只能拜访const润饰的函数 //person.ShowPerson(); person.ChangePerson(); }
2.5 this指针|总结
- this是指向当时目标的指针
- 目标在调用成员函数的时分,会主动传入当时目标的内存地址
- 考虑
- 能够运用this.m_age来拜访成员变量么?
- 不能够,因为this是指针,有必要用this->m_age
- 能够运用this.m_age来拜访成员变量么?
2.6 指针拜访的实质
- 考虑:最终打印出来的每个成员变量值是多少?
- 答案:
- 10 40 50
- 考虑 假设将
person.display()
换成p->display()
呢?
专题系列文章
1. iOS底层原理前常识
- 01-探求iOS底层原理|综述
- 02-探求iOS底层原理|编译器LLVM项目【Clang、SwiftC、优化器、LLVM】
- 03-探求iOS底层原理|LLDB
- 04-探求iOS底层原理|ARM64汇编
2. 根据OC言语探求iOS底层原理
- 05-探求iOS底层原理|OC的实质
- 06-探求iOS底层原理|OC目标的实质
- 07-探求iOS底层原理|几种OC目标【实例目标、类目标、元类】、目标的isa指针、superclass、目标的办法调用、Class的底层实质
- 08-探求iOS底层原理|Category底层结构、App启动时Class与Category装载过程、load 和 initialize 履行、关联目标
- 09-探求iOS底层原理|KVO
- 10-探求iOS底层原理|KVC
- 11-探求iOS底层原理|探求Block的实质|【Block的数据类型(实质)与内存布局、变量捕获、Block的品种、内存办理、Block的润饰符、循环引证】
- 12-探求iOS底层原理|Runtime1【isa详解、class的结构、办法缓存cache_t】
- 13-探求iOS底层原理|Runtime2【音讯处理(发送、转发)&&动态办法解析、super的实质】
- 14-探求iOS底层原理|Runtime3【Runtime的相关运用】
- 15-探求iOS底层原理|RunLoop【两种RunloopMode、RunLoopMode中的Source0、Source1、Timer、Observer】
- 16-探求iOS底层原理|RunLoop的运用
- 17-探求iOS底层原理|多线程技能的底层原理【GCD源码剖析1:主行列、串行行列&&并行行列、大局并发行列】
- 18-探求iOS底层原理|多线程技能【GCD源码剖析1:dispatch_get_global_queue与dispatch_(a)sync、单例、线程死锁】
- 19-探求iOS底层原理|多线程技能【GCD源码剖析2:栅门函数dispatch_barrier_(a)sync、信号量dispatch_semaphore】
- 20-探求iOS底层原理|多线程技能【GCD源码剖析3:线程调度组dispatch_group、事情源dispatch Source】
- 21-探求iOS底层原理|多线程技能【线程锁:自旋锁、互斥锁、递归锁】
- 22-探求iOS底层原理|多线程技能【原子锁atomic、gcd Timer、NSTimer、CADisplayLink】
- 23-探求iOS底层原理|内存办理【Mach-O文件、Tagged Pointer、目标的内存办理、copy、引证计数、weak指针、autorelease
3. 根据Swift言语探求iOS底层原理
关于函数
、枚举
、可选项
、结构体
、类
、闭包
、特点
、办法
、swift多态原理
、String
、Array
、Dictionary
、引证计数
、MetaData
等Swift根本语法和相关的底层原理文章有如下几篇:
- 01-Swift5常用中心语法|了解Swift【Swift简介、Swift的版本、Swift编译原理】
- 02-Swift5常用中心语法|根底语法【Playground、常量与变量、常见数据类型、字面量、元组、流程操控、函数、枚举、可选项、guard句子、区间】
- 03-Swift5常用中心语法|面向目标【闭包、结构体、类、枚举】
- 04-Swift5常用中心语法|面向目标【特点、inout、类型特点、单例形式、办法、下标、承继、初始化】
- 05-Swift5常用中心语法|高档语法【可选链、协议、过错处理、泛型、String与Array、高档运算符、扩展、拜访操控、内存办理、字面量、形式匹配】
- 06-Swift5常用中心语法|编程范式与Swift源码【从OC到Swift、函数式编程、面向协议编程、呼应式编程、Swift源码剖析】
4. C言语中心语法
- 01-温习C言语中心常识|综述
- 02-温习C言语中心常识|根本语法、数据类型、变量、常量、存储类、根本句子(判别句子、循环句子、go to句子)和运算
- 03-温习C言语中心常识|函数、效果域规矩、数组、枚举、字符与字符串、指针
- 04-温习C言语中心常识|结构体、共用体、位域、输入&输出、文件读写
- 05-温习C言语中心常识|预处理、头文件、强制类型转化、过错处理、递归、内存办理
5. C++中心语法
- 01-C++中心语法|C++概述【C++简介、C++起源、可移植性和标准、为什么C++会成功、从一个简略的程序开端认识C++】
- 02-C++中心语法|C++对C的扩展【::效果域运算符、姓名操控、struct类型加强、C/C++中的const、引证(reference)、函数】
- 03-C++中心语法|面向目标1【 C++编程标准、类和目标、面向目标程序规划事例、目标的结构和析构、C++面向目标模型初探】
- 04-C++中心语法|面向目标2【友元、内部类与部分类、强化训练(数组类封装)、运算符重载、仿函数、模板、类型转化、 C++标准、过错&&异常、智能指针】
- 05-C++中心语法|面向目标3【 承继和派生、多态、静态成员、const成员、引证类型成员、VS的内存窗口】
6. Vue全家桶
- 01-Vue全家桶中心常识|Vue根底【Vue概述、Vue根本运用、Vue模板语法、根底事例、Vue常用特性、归纳事例】
- 02-Vue全家桶中心常识|Vue常用特性【表单操作、自界说指令、核算特点、侦听器、过滤器、生命周期、归纳事例】
- 03-Vue全家桶中心常识|组件化开发【组件化开发思维、组件注册、Vue调试东西用法、组件间数据交互、组件插槽、根据组件的
- 04-Vue全家桶中心常识|多线程与网络【前后端交互形式、promise用法、fetch、axios、归纳事例】
- 05-Vue全家桶中心常识|Vue Router【根本运用、嵌套路由、动态路由匹配、命名路由、编程式导航、根据vue-router的事例】
- 06-Vue全家桶中心常识|前端工程化【模块化相关标准、webpack、Vue 单文件组件、Vue 脚手架、Element-UI 的根本运用】
- 07-Vue全家桶中心常识|Vuex【Vuex的根本运用、Vuex中的中心特性、vuex事例】
7. 音视频技能中心常识
- 01-音视频技能中心常识|了解音频技能【移动通讯技能的开展、声音的实质、深入了解音频】
- 02-音视频技能中心常识|建立开发环境【FFmpeg与Qt、Windows开发环境建立、Mac开发环境建立、Qt开发根底】
- 03-音视频技能中心常识|Qt开发根底【
.pro
文件的配置、Qt控件根底、信号与槽】 - 04-音视频技能中心常识|音频录制【命令行、C++编程】
- 05-音视频技能中心常识|音频播映【播映PCM、WAV、PCM转WAV、PCM转WAV、播映WAV】
- 06-音视频技能中心常识|音频重采样【音频重采样简介、用命令行进行重采样、经过编程重采样】
- 07-音视频技能中心常识|AAC编码【AAC编码器解码器、编译FFmpeg、AAC编码实战、AAC解码实战】
- 08-音视频技能中心常识|成像技能【重识图片、详解YUV、视频录制、显现BMP图片、显现YUV图片】
- 09-音视频技能中心常识|视频编码解码【了解H.264编码、H.264编码、H.264编码解码】
- 10-音视频技能中心常识|RTMP服务器建立【流媒体、服务器环境】
其它底层原理专题
1. 底层原理相关专题
- 01-核算机原理|核算机图形烘托原理这篇文章
- 02-核算机原理|移动终端屏幕成像与卡顿
2. iOS相关专题
- 01-iOS底层原理|iOS的各个烘托框架以及iOS图层烘托原理
- 02-iOS底层原理|iOS动画烘托原理
- 03-iOS底层原理|iOS OffScreen Rendering 离屏烘托原理
- 04-iOS底层原理|因CPU、GPU资源耗费导致卡顿的原因和处理计划
3. webApp相关专题
- 01-Web和类RN大前端的烘托原理
4. 跨渠道开发计划相关专题
- 01-Flutter页面烘托原理
5. 阶段性总结:Native、WebApp、跨渠道开发三种计划功能比较
- 01-Native、WebApp、跨渠道开发三种计划功能比较
6. Android、HarmonyOS页面烘托专题
- 01-Android页面烘托原理
- 02-HarmonyOS页面烘托原理 (
待输出
)
7. 小程序页面烘托专题
- 01-小程序框架烘托原理