本文主要是介绍C++失传千年经典系列(二):类,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
C++失传千年经典系列(一):基础语法认知
C++失传千年经典系列(二):类
一:C++中类认知:OOP面向对象编程
在实例中深化理解各种语法: 学--> 思--> 做--> 构建"一套体系化知识脉络走廊"
C++类认知升维:
(1)C++相对于C语言,引入了面向对象的编程思维,使用面向对象编程更符合人们思考和处理问题的思维方式,C++作为一门高级编程语言,引入了OOP面向对象编程的特性;
类时对现实事|物的特征,状况进行抽象,把事|物的特性数据化描述为类的成员变量;事|物的变化活动,变化状态等抽象为类的成员函数或者成员方法; 某个类可以
看成具有某些共性的实例对象的集合,要使用类的成员变量及成员函数,需要实例化该类的实例对象,通过实例对象去操作类的成员变量与成员函数,来达到完成显示业务
逻辑目的;
(2)类是一种数据类型,既然是数据类型,就可以使用该类声明变量,也存在内存分配的问题,一般情况下会在栈上分配内存;
(3)类通常在实例化时,会自动调用类的构造函数,构造函数大致分为两种:
(3.1)默认的构造函数,没有参数列表的构造函数叫默认的构造函数,当我们不显示在类中声明构造函数时,系统会自动提供一个预置的默认构造函数;
默认的构造函数相当于把类中的所有成员变量赋予该成员变量数据类型的默认初始值;
(3.2)重载版本的构造函数,重载: 通常指同一个类中,方法签名相同,参数列表不同的方法;同样,非默认构造函数,把具体指定的类成员变量赋值为给定的值,
未指定的字段成员被赋予该变量的默认数据类型的初始值;
(4)访问类的成员:
(4.1)点访问运算符"."
(4.2)指针专用的成员访问符"->"
#include <iostream>
#include <string>
using namespace std;
/*在实例中深化理解各种语法: 学--> 思--> 做--> 构建"一套体系化知识脉络走廊"C++类认知升维:(1)C++相对于C语言,引入了面向对象的编程思维,使用面向对象编程更符合人们思考和处理问题的思维方式,C++作为一门高级编程语言,引入了OOP面向对象编程的特性;类时对现实事|物的特征,状况进行抽象,把事|物的特性数据化描述为类的成员变量;事|物的变化活动,变化状态等抽象为类的成员函数或者成员方法; 某个类可以看成具有某些共性的实例对象的集合,要使用类的成员变量及成员函数,需要实例化该类的实例对象,通过实例对象去操作类的成员变量与成员函数,来达到完成显示业务逻辑目的;(2)类是一种数据类型,既然是数据类型,就可以使用该类声明变量,也存在内存分配的问题,一般情况下会在栈上分配内存;(3)类通常在实例化时,会自动调用类的构造函数,构造函数大致分为两种:(3.1)默认的构造函数,没有参数列表的构造函数叫默认的构造函数,当我们不显示在类中声明构造函数时,系统会自动提供一个预置的默认构造函数;默认的构造函数相当于把类中的所有成员变量赋予该成员变量数据类型的默认初始值;(3.2)重载版本的构造函数,重载: 通常指同一个类中,方法签名相同,参数列表不同的方法;同样,非默认构造函数,把具体指定的类成员变量赋值为给定的值,未指定的字段成员被赋予该变量的默认数据类型的初始值;(4)访问类的成员: (4.1)点访问运算符"."(4.2)指针专用的成员访问符"->"
*/
// 定义类的成员函数与成员变量
class Time {
public: // 构造函数访问控制符Time(int hr, int min, int sec) { // 构造函数this->hour = hr;this->minute = min;this->second = sec; // 构造函数初始化成员变量}// 定义类的成员函数void printTime() const {cout << "时间是: " << "时" << minute << "分" << second << "秒" << endl;}// 定义类的成员变量
private:int hour; // 小时int minute; // 分钟int second; // 秒
};class Champion {
public: // 访问限制修饰符Champion(int id, string nm, int hp, int mn, int dmg) {ID = id;name = nm;HP = hp;mana = mn;damage = dmg;}// 定义公开的方法void attack(Champion& chmp) {chmp.takeDamage(this->damage);}void takeDamage(int incomingDmg) {HP -= incomingDmg;}int getHP() {return HP;}private:int ID;string name;int HP; // 血量int mana; // 魔法值int damage; // 伤害值
};int main(){// 创建类Time的实例对象time;或者时声明并初始化一个Time数据类型的变量time实例Time time(2, 32, 56); // 这种创建实例对象与java,python的区别,联想类比加深记忆理解// 对象调用方法printTime()time.printTime();cout << "" << endl; // 输出空行cout << "创建Champion实例对象,并调用类的实例对象的成员变量及成员方法" << endl;Champion galen(1, "老杨", 800, 100, 10); // 创建数量类型为Champion的实例对象galenChampion ash(2, "老高", 700, 150, 7); // 创建数量类型为Champion的实例对象ashcout << "ash的初始血量:" << ash.getHP() << endl;// 调用galen实例的attack()方法galen.attack(ash);cout << "Ash受到Galen攻击后的血量:"<<ash.getHP() << endl;return 0;}
运行效果:
时间是: 时32分56秒
创建Champion实例对象,并调用类的实例对象的成员变量及成员方法
ash的初始血量:700
Ash受到Galen攻击后的血量:690D:\program_file\C++_workspace\ProjectCodeOOPClass01\x64\Debug\ProjectCodeOOPClass01.exe (进程 6408)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
二:C++中指针访问符访问类的成员
#include <iostream>
#include <string>using namespace std;
// 指针访问类实例对象的成员
class Champion {
public:Champion(int id, string nm, int hp, int mn, int dmg) {ID = id;name = nm;HP = hp;mana = mn;damage = dmg;}// 攻击成员函数void attack(Champion &chmp) {chmp.takeDamage(this->damage);}void takeDamage(int incomingDmg) {HP -= incomingDmg;}// 获取血压值int getHP() {return HP;}
private:int ID;string name;int HP; // 血压int mana; // 魔法值int damage; // 伤害值
};int main() {// 实例化对象galenChampion galen(11, "老板",1000, 100, 10);Champion ash(2, "老表", 700, 150, 7);cout << "Ash的初始血量:" << ash.getHP() << endl;// 定义指向Champion的指针Champion *chmpPtr = &galen;(*chmpPtr).attack(ash);// 指针专用的成员访问法访问类的成员方法;chmpPtr->attack(ash);cout << "Ash受到Galen攻击后的血量: " << ash.getHP() << endl;}
运行效果:
Ash的初始血量:700
Ash受到Galen攻击后的血量: 680D:\program_file\C++_workspace\ProjectCodeToClassChapter02\x64\Debug\ProjectCodeToClassChapter02.exe (进程 7860)已退出, 代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
三:C++中关键字this认知
定义类的同时初始化实例对象;这种初始化或者实例化对象与C,Go语言结构体类似
this关键字表示当前类的指针: 类中的成员函数访问该类的成员变量时,有一个隐含的指针变量this,它的
类型是指向当前实例的指针,指向的是调用成员函数的对象;
3.1 this关键字访问类成员属性
#include <iostream>
using namespace std;
/*(1)定义类的同时初始化实例对象;这种初始化或者实例化对象与C,Go语言结构体类似this关键字表示当前类的指针: 类中的成员函数访问该类的成员变量时,有一个隐含的指针变量this,它的类型是指向当前实例的指针,指向的是调用成员函数的对象;(2)
*/
class MyClass {// 类的构造函数
public:MyClass() {aNumber = 1000010;aname = "老王";}// this指针的显示使用MyClass(string aname, string address) {this->aname = aname;this->address = address;}// 获取名称string getName() {return this->aname;}// 获取居住地址的函数string getAddress() {return this->address;}// 类的成员变量
private:int aNumber; // 编号string aname; // 名称string address; // 地址// 定义类公有的方法
public:int getANumber() {return this->aNumber;}
}myclass;int main() {cout << "aNumber编号是: " <<myclass.getANumber()<< endl;cout << "名称是: " << myclass.getName() << endl;cout << "address返回string数量类型的默认值: " << myclass.getAddress() << endl;cout << "" << endl; // 输出空字符串并换行MyClass tempMyClass("老杨","北京市海淀区马连洼街道21号院3000路");cout << "名称是: " << tempMyClass.getName() << endl;cout << "地址是: " << tempMyClass.getAddress() << endl;return 0;
}
运行效果:
aNumber编号是: 1000010
名称是: 老王
address返回string数量类型的默认值:名称是: 老杨
地址是: 北京市海淀区马连洼街道21号院3000路D:\program_file\C++_workspace\ProjectCodeToClassDecleard\x64\Debug\ProjectCodeToClassDecleard.exe (进程 5588)已退出,代 码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
3.2 this关键字作为指针返回
#include <iostream>
using namespace std;// this指针作为返回值
class Person {
public:Person(int id,string userName,int sex,string address,string college) {this->pNumber =id;name = userName;this->sex = sex;this->address = address;this->college = college;}// 定义返回当前类的指针: this作为返回值Person *getPerson() {return this;}Person getCopy() {return *this;}int getPersonSex() {return sex;}string getName() {return this->name;}string getAddress() {return address;}string getCollege() {return this->college;}
private:int pNumber; // 身份证号string name; // 名称int sex; // 性别string address; // 地址string college; // 毕业学校
};int main() {// 声明一个persion实例对象,并赋予初始值Person persion(100020, "老高", 1, "北京市朝阳区三里屯北路10001号院", "清华大学");Person *ptrPersion = persion.getPerson();cout << "姓名: " << ptrPersion->getName() << endl;cout << "性别为(1-男;0-女): " << ptrPersion->getPersonSex() << endl;cout << "居住地址: " << ptrPersion->getAddress() << endl;cout << "毕业学校: " << ptrPersion->getCollege() << endl;cout << " " << endl;Person copyPerson = persion.getCopy();cout << "copyPerson非指针类型的实例 " << endl;cout << "性别为(1-男;0-女): " << copyPerson.getPersonSex() << endl;cout << "居住地址: " << copyPerson.getAddress() << endl;cout << "毕业学校: " << copyPerson.getCollege() << endl;cout << " " << endl;return 0;
}
运行效果:
姓名: 老高
性别为(1-男;0-女): 1
居住地址: 北京市朝阳区三里屯北路10001号院
毕业学校: 清华大学copyPerson非指针类型的实例
性别为(1-男;0-女): 1
居住地址: 北京市朝阳区三里屯北路10001号院
毕业学校: 清华大学
D:\program_file\C++_workspace\ProjectCodeToClassPerson\x64\Debug\ProjectCodeToClassPerson.exe (进程 19544)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
四:C++类与结构体的区别
类与结构体的区别:
在C语言的时代,结构体还没有构造函数和成员函数这些面向对象的元素;而在C++设计出了类后,结构体域类的区别
只剩下默认的访问控制符了。这是因为在C++向前兼容的时候,C语言结构体中的变量都是直接访问的,所以结构体的
默认访问控制符是public,而类的则是private。
#include <iostream>using namespace std;/*类与结构体的区别:在C语言的时代,结构体还没有构造函数和成员函数这些面向对象的元素;而在C++设计出了类后,结构体域类的区别只剩下默认的访问控制符了。这是因为在C++向前兼容的时候,C语言结构体中的变量都是直接访问的,所以结构体的默认访问控制符是public,而类的则是private。
*/
// 定义结构体
struct MyStruct {MyStruct(int a, int b) {this->a = a;this->b = b;}int a; // 定义结构体成员aint b; // 定义结构体成员b
};
// 定义类
class MyClass {
public:MyClass(int a,int b) {this->a = a;this->b = b;}// 类的成员变量声明为public,可以直接访问,如果访问修饰符为private,则main()函数中不能访问
public:int a;int b;
};int main() {// 定义结构体变量MyStruct myStruct(20,100);MyClass myClass(300, 100); // 实例化对象myClasscout << "a的值是: " << myClass.a << endl;cout << "b的值是: " << myClass.b << endl;cout << "" << endl;cout << "a的值是: " << myStruct.a << endl;cout << "b的值是: " << myStruct.b << endl;
}
运行效果:
a的值是: 300
b的值是: 100a的值是: 20
b的值是: 100D:\program_file\C++_workspace\ProjectCodeToClassDifferStruct\x64\Debug\ProjectCodeToClassDifferStruct.exe (进程 16368)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
五:C++类的构造函数认知
构造函数认知:
(1)默认构造函数,一般的构造函数都会有参数用来初始化类的成员,而默认构造函数是没有参数的构造函数;
在创建对象的时候如果对象名后面不加括号,那么系统就会调用默认构造函数。
(2)默认构造函数没有参数时,实例化时可以可以省略初始化参数类别,像声明变量一样;
(3)重载构造函数,构造函数重载的时候要满足一般函数的重载规则
(4)由于调用是参数个数不同,编译器可以很容易地找到相应的版本;
#include <iostream>using namespace std;/*构造函数认知:(1)默认构造函数,一般的构造函数都会有参数用来初始化类的成员,而默认构造函数是没有参数的构造函数;在创建对象的时候如果对象名后面不加括号,那么系统就会调用默认构造函数。(2)默认构造函数没有参数时,实例化时可以可以省略初始化参数类别,像声明变量一样;(3)重载构造函数,构造函数重载的时候要满足一般函数的重载规则(4)由于调用是参数个数不同,编译器可以很容易地找到相应的版本;
*/
class Area {
public:Area(){this->sideA = 10;this->sideB = 2;this->area = sideA * sideB;} // 无参构造函数// 重载构造函数:长方形Area(int a, int b) { this->sideA = a;this->sideB = b;this->area = sideA * sideA;}Area(int a) {this->sideA = a;this->area = sideA * sideA; // 面积}int getArea() {return this->area;}private:int sideA;int sideB;int area; // 面积
};
int main() {int a = 3;int b = 4;int c = 5;// 省略参数列表创建实例对象Area area;cout << "默认构造函数的面积: " << area.getArea() << endl;Area area1(a, b);Area area2(c);cout << "边长为" << a << "和" << b << "的长方形的面积为:" << area1.getArea() << endl;cout << "边长为" << c << "的正方形的面积为:" << area2.getArea() << endl;return 0;
}
运行效果:
默认构造函数的面积: 20
边长为3和4的长方形的面积为:9
边长为5的正方形的面积为:25D:\program_file\C++_workspace\ProjectCodeToClassOfConstructor\x64\Debug\ProjectCodeToClassOfConstructor.exe (进程 8900) 已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
六:C++类初始化成员列表
类初始化操作认知
构造函数与普通的函数类似,类成员初始化工作都在函数体中使用赋值进行。
然而对应这一种特殊的构造函数来说,也可以使用初始化列表来初始化;
初始化列表位于参数列表和函数体之间,以一个冒号":"开始,并用逗号隔开数个类似调用构造
函数的初始化表达式。其实在实际编程中,也可以使用类似的语法对基本数据类型的变量进行初始化。
#include <iostream>
using namespace std;/*类初始化操作认知构造函数与普通的函数类似,类成员初始化工作都在函数体中使用赋值进行。然而对应这一种特殊的构造函数来说,也可以使用初始化列表来初始化;初始化列表位于参数列表和函数体之间,以一个冒号":"开始,并用逗号隔开数个类似调用构造函数的初始化表达式。其实在实际编程中,也可以使用类似的语法对基本数据类型的变量进行初始化。
*/
class CurrentTime {
public:// 初始化化类的成员变量CurrentTime(int hr,int min,int sec): hour(hr), minute(min),second(sec){}int getHour() {return this->hour;}int getMinute() {return this->minute;}int getSecond() {return this->second;}
private:int hour;int minute;int second;
};
int main() {CurrentTime time(12, 24, 36); // 创建实例对象cout << "时间是:" << time.getHour() << "时"<< time.getMinute() << "分"<< time.getSecond() << "秒" << endl;cout << "" << endl; // 输出空字符串并换行cout << "类似于构造函数的变量初始化:" << endl;int num(5); // 表示定义变量的num同时赋予该变量的初始化值为5;float fnum(1.4); // 定义变量并赋予初始值char ch('h'); // 定义变量并赋予初始值bool bval(false); // 定义变量并赋予初始值cout << "变量num = " << num << " ,变量fnum = " << fnum << " ,字符串ch = " << ch << " ,布尔值bval = " << bval << endl;return 0;
}
运行效果:
时间是:12时24分36秒
类似于构造函数的变量初始化:
变量num = 5 ,变量fnum = 1.4 ,字符串ch = h ,布尔值bval = 0D:\program_file\C++_workspace\ProjectCodeToClassInstanceParams\x64\Debug\ProjectCodeToClassInstanceParams.exe (进程 20248)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
七:类的默认构造无参函数,类初始化时也会被调用
通常类的默认无参函数,在无参数列表时,系统会默认调用无参构造函数,声明无参函数的类的变量时,可以省略括号,像声明普通数据类型的变量一样声明类的对象实例;
#include <iostream>
using namespace std;/*初始化列表的调用顺序
*/class MyClassB {
public:MyClassB() { // 显示构造无参构造函数cout << "MyClassB无参的构造函数被调用!" << endl;num = 0; // 在构造函数中给成员变量num赋予初始值;}
private:int num; // 定义类的成员变量num
}; // 末尾的分号不能省略与Java类作类比;class MyClassA {
public:MyClassA(MyClassB myClassB) { // 构造函数this->myClassB = myClassB; // 给成员对象myClassB赋值string address("北京市海淀区马连洼街道办事处20号院1002号路");cout << "欢迎来到: " << address <<"做客!" << endl;scoures = 90.5;}// 定义私有成员变量
private:MyClassB myClassB; // 相当于在类MyClassA中实例化一个数据类型为MyClassB的对象myClassB;int num =10;float scoures; // 在定义类中的成员变量时,初始化赋值不能float sources(1.4);这种初始化形式
};int main() {MyClassB myClassb; // 调用无参构造函数实例化对象时,可以省略括号;像定义普通变量那样声明类实例化对象cout << "创建MyClassA的对象之前!" << endl;MyClassA myClassa(myClassb);return 0;
}
运行效果:
MyClassB无参的构造函数被调用!
创建MyClassA的对象之前!
MyClassB无参的构造函数被调用!
欢迎来到: 北京市海淀区马连洼街道办事处20号院1002号路做客!D:\program_file\C++_workspace\ProjectCodeToClassInitCallConstructor\x64\Debug\ProjectCodeToClassInitCallConstructor.exe (进程 18536)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
提示: MyClassB的无参构造函数被调用了两次, 第一次调用无参是才main函数中的第一次声明变量myClassB调用无参构造函数,第二次是实例化MyClassA时,由于该类成员变量包含了MyClassB类,因此会调用无参构造函数;
八:C++中类的析构函数
析构函数认知:
既然类有特殊的用来初始化的构造函数,就会有用来收尾和释放内存的函数,这个函数叫作析构函数;
析构函数的语法:
~类名(){语句块};
析构函数与默认的构造函数一样,都是系统自动调用;
#include <iostream>
using namespace std;/*析构函数认知:既然类有特殊的用来初始化的构造函数,就会有用来收尾和释放内存的函数,这个函数叫作析构函数;析构函数的语法:~类名(){语句块};析构函数与默认的构造函数一样,都是系统自动调用;
*/// 关注析构函数
class MyClass {
public: // 类的构造函数,并初始化类的成员变量;MyClass(int ela, int elb) :elementA(ela), elementB(elb) {cout <<"MyClass类的构造函数被调用!" << endl;}// 定义类的析构函数~MyClass() {cout << "MyClass类的析构函数被调用" << endl;}// 定义外界可以访问成员变量值的函数void printElementAB() {cout << "elementA的值: " << this->elementA << " ,elementB的值: " << this->elementB << endl;}
private:int elementA; // 定义成员变量Aint elementB;
};int main() {MyClass myClass(10, 30); // 显示定义了有参构造函数时,默认无参构造函数会被覆盖;此时声明的变量myclass不能省略括号;myClass.printElementAB();return 0;
}
运行效果:
MyClass类的构造函数被调用!
elementA的值: 10 ,elementB的值: 30
MyClass类的析构函数被调用D:\program_file\C++_workspace\ProjectToClassDestructor\x64\Debug\ProjectToClassDestructor.exe (进程 18752)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
九:C++中动态分配类对象的内存
C++语言中类的动态分配对象内存
对于类的实例来说,在使用new关键字创建对象时,系统会自动调用构造函数;而在使用delete关键字时,
系统会自动调用析构函数来释放对象所占用的内存;
9.1 实例一:动态分配对象内存
#include <iostream>using namespace std;/*C++语言中类的动态分配对象内存对于类的实例来说,在使用new关键字创建对象时,系统会自动调用构造函数;而在使用delete关键字时,系统会自动调用析构函数来释放对象所占用的内存;
*/class MyClass {
public:MyClass(int a, int b) :a(a), b(b) {cout<<"MyClass类的构造函数被调用!" << endl;}~MyClass() {cout << "MyClass类的析构函数被调用!" << endl;}void printAB() {cout << "A的值是: " << this->a << " ,B的值是: " <<b << endl;}private:int a;int b;
};int main() {// 定义指针变量MyClass* prt = new MyClass(20, 200); // 使用new关键字创建对象实例// 使用指针调用对象实例的printAB()方法prt->printAB();// 释放指针实例对象所占用的内存delete prt;cout << "" << endl;prt = new MyClass(50, 800);prt->printAB();// 释放指针实例对象所占用的内存delete prt;return 0;
}
MyClass类的构造函数被调用!
A的值是: 20 ,B的值是: 200
MyClass类的析构函数被调用!MyClass类的构造函数被调用!
A的值是: 50 ,B的值是: 800
MyClass类的析构函数被调用!D:\program_file\C++_workspace\ProjectCodeToDynamicMallocMemony\x64\Debug\ProjectCodeToDynamicMallocMemony.exe (进程 17160)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
9.2 实例二:类内部动态内存分配
类内部动态内存分配:
每次使用new关键字创建对象时,构造函数都会被调用,每次使用delete的时候析构函数则会被自动调用;
虽然在类的内部使用动态内存分配,但由于对象创建在栈上,因此析构函数会自动被调用,不用担心类内部是否有
内存泄露。而类的设计者只需要保证内存在析构函数中被安全释放就行了,这也体现了类的封装性;
#include <iostream>
using namespace std;
/*类内部动态内存分配:每次使用new关键字创建对象时,构造函数都会被调用,每次使用delete的时候析构函数则会被自动调用;虽然在类的内部使用动态内存分配,但由于对象创建在栈上,因此析构函数会自动被调用,不用担心类内部是否有内存泄露。而类的设计者只需要保证内存在析构函数中被安全释放就行了,这也体现了类的封装性;
*/class MyInnerMemoryClass {
public:MyInnerMemoryClass(int size):size(size) {arr = new int[size];for (int i = 0; i < size; i++) {arr[i] = i;}}// 析构函数,系统会自动调用~MyInnerMemoryClass() {delete arr;}// 循环输出数组void printArr() {for (int i = 0; i < size; i++) {cout << arr[i] << "";}cout << endl; // 输出完在换行}
private:int size;int *arr; // 定义指向整形数组的指针变量
};int main() {// 定义变量obj,并初始化MyInnerMemoryClass obj(5);obj.printArr();return 0;
}
0 1 2 3 4
D:\program_file\C++_workspace\ProjectToClassInnerMemory\x64\Debug\ProjectToClassInnerMemory.exe (进程 7408)已退出,代码 为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
十:C++类的作用域操作符认知
类的作用域认知:
一个类的创建界定了一块独一无二的作用域;两个类直接可以声明同名的成员函数和变量不会产生冲突;
这是因为我们大多数情况下都是通过类的实例化对象来访问成员函数及成员变量的;但是例外也会存在,为
避免作用域冲突问题,引入了作用域操作符,并通过名称来查找的规则;在类定义体"{}"大括号框住的区域的成员变量和成员函数
都在类的作用域中,但是我们在该类作用域外定义类的成员变量或者成员函数时,就需要使用类的作用域操作符,而在类的作用域之外的成员函数位于
全局作用域中,这样的场景中需要使用作用域操作符来区分同名的成员;
作用域访问符: "::"
#include <iostream>using namespace std;/*类的作用域认知:一个类的创建界定了一块独一无二的作用域;两个类直接可以声明同名的成员函数和变量不会产生冲突;这是因为我们大多数情况下都是通过类的实例化对象来访问成员函数及成员变量的;但是例外也会存在,为避免作用域冲突问题,引入了作用域操作符,并通过名称来查找的规则;在类定义体"{}"大括号框住的区域的成员变量和成员函数都在类的作用域中,但是我们在该类作用域外定义类的成员变量或者成员函数时,就需要使用类的作用域操作符,而在类的作用域之外的成员函数位于全局作用域中,这样的场景中需要使用作用域操作符来区分同名的成员;作用域访问符: "::"*/
class OutInnerClass {
public:OutInnerClass() {}static int numNo; // 定义类的静态成员变量// 公开访问权限static void printOutInnerClass() { // 定义静态方法cout << "print OutInnerClass!" << endl;}
private:
};
class OutInnerClassB {
public:OutInnerClassB(){}// 定义公共方法void printB() {cout << "" << endl;OutInnerClass::printOutInnerClass(); // 作用域操作符;【静态方法,通过类名::静态方法();】}
private:int num;
};
// 在类的作用域外定义全局的类的同名静态成员变量
int OutInnerClass::numNo = 250; // 先在类中申明一次;再在类的外部定义同名的成员变量;
// 普通的全局变量
int numNo = 500; // 定义全局的与类OutInnerClass中的成员numNo同名的变量int main() {cout << "OutInnerClass::numNo的值为: " << OutInnerClass::numNo << endl;cout << "全局numNo变量的值为: " << numNo << endl;OutInnerClassB b;b.printB();return 0;
}
运行效果:
OutInnerClass::numNo的值为: 250
全局numNo变量的值为: 500print OutInnerClass!
D:\program_file\C++_workspace\ProjectToClassDoField\x64\Debug\ProjectToClassDoField.exe (进程 6884)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
十一:C++中类静态成员认知
类的静态成员认知升维:
(1)我们知道函数的静态变量,它与函数的普通局部变量的不同之处在于静态变量的声明周期是贯穿整个程序的,而不是一次函数调用;
函数的非静态变量的作用域只在函数体内,当函数调用后就会被销毁,而函数的静态变量在函数调用后仍然存在,其声明周期贯穿整个程序;
而静态变量与全局变量的不同之处在于静态变量的作用域仅限于函数的内部,只有在函数内部才是可见的;同样,我们在定义类时,可以在类的内部定义
静态成员变量与静态成员函数;
(2)类的静态成员变量与函数的静态变量类似,其声明周期与类(而不是具体的对象)绑定。静态成员变量的作用域也是类,也就是在类的所有实例对象中都可见;
(3)调用静态成员函数时候,需要加上类的限定符,否则如果两个类拥有重名的函数将无法区分;
(4)类的静态成员函数中只能访问类的静态函数成员变量和其他静态成员函数,一般的成员变量和函数是不能访问的(也包括this指针),因为它只是一个作用域限于类的函数;
调用时并没有和任何一个具体的对象关联,即是静态成员只属于类,而不属于每个具体的对象;这点与java类似;
(5)调用类的静态成员时,需要使用类名::静态成员的形式;
#include <iostream>using namespace std;
/*类的静态成员认知升维:(1)我们知道函数的静态变量,它与函数的普通局部变量的不同之处在于静态变量的声明周期是贯穿整个程序的,而不是一次函数调用;函数的非静态变量的作用域只在函数体内,当函数调用后就会被销毁,而函数的静态变量在函数调用后仍然存在,其声明周期贯穿整个程序;而静态变量与全局变量的不同之处在于静态变量的作用域仅限于函数的内部,只有在函数内部才是可见的;同样,我们在定义类时,可以在类的内部定义静态成员变量与静态成员函数;(2)类的静态成员变量与函数的静态变量类似,其声明周期与类(而不是具体的对象)绑定。静态成员变量的作用域也是类,也就是在类的所有实例对象中都可见;(3)调用静态成员函数时候,需要加上类的限定符,否则如果两个类拥有重名的函数将无法区分;(4)类的静态成员函数中只能访问类的静态函数成员变量和其他静态成员函数,一般的成员变量和函数是不能访问的(也包括this指针),因为它只是一个作用域限于类的函数;调用时并没有和任何一个具体的对象关联,即是静态成员只属于类,而不属于每个具体的对象;这点与java类似;(5)调用类的静态成员时,需要使用类名::静态成员的形式;*/
class StaticStudent {
public:StaticStudent(int id, string name, int age) :id(id), name(name), age(age) {cout << "StaticStudent构造函数被调用!" << endl;}// 定义学号生产机,static int generateId() {return gobalNo++; // 静态常量不能使用this指针操作;}// 定义获取名称的方法string getName() {return this->name;}// 定义返回序号的方法int getId() {return this->id;}
private:int id;string name; // 名称int age; // 年龄static int gobalNo; // 静态变量: 学号 };
int StaticStudent::gobalNo = 1000; // 必须使用作用域操作符给类的静态成员变量赋值,否则编译报错
int main() {// 定义StaticStudent类型的实例对象StaticStudent student(100010, "老杨", 25);// 使用类名"::"调用类的静态成员;cout << "序号: " << student.getId() << endl;cout << "学生名称: " << student.getName() <<endl;cout << "学号: " << StaticStudent::generateId() << endl;cout << "" << endl;StaticStudent student1(100011, "老李", 30);// 使用类名"::"调用类的静态成员;cout << "序号: " << student1.getId() << endl;cout << "学生名称: " << student1.getName() << endl;cout << "学号: " << StaticStudent::generateId() << endl;
}
StaticStudent构造函数被调用!
序号: 100010
学生名称: 老杨
学号: 1000StaticStudent构造函数被调用!
序号: 100011
学生名称: 老李
学号: 1001D:\program_file\C++_workspace\ProjectToClassStaticElement\x64\Debug\ProjectToClassStaticElement.exe (进程 15328)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
十二:C++中类继承认知
C++中类继承体系认知:
(1)我们知道面向对象编程有三大特性:封装,继承,多态;
面向对象编程中的一种重要思想是继承。继承可以省略类之间的共同部分,并使用户可以采用相同的方式在不同类之中实现不相同但是类似的行为;
(2)继承的语法由两部分组成:
一是紧跟在类名和冒号":"后面的访问控制符;
二是基类的名字; 这里的基类可以不止一个,可以用逗号分隔的一个基类列表;
(3)只需要访问控制权限允许,一个类在继承了某个基类以后就可以无须声明地使用基类的成员;
12.1: 类继承体系中基类必须明确定义
#include <iostream>using namespace std;/*C++中类继承体系认知:(1)我们知道面向对象编程有三大特性:封装,继承,多态;面向对象编程中的一种重要思想是继承。继承可以省略类之间的共同部分,并使用户可以采用相同的方式在不同类之中实现不相同但是类似的行为;(2)继承的语法由两部分组成:一是紧跟在类名和冒号":"后面的访问控制符;二是基类的名字; 这里的基类可以不止一个,可以用逗号分隔的一个基类列表;(3)只需要访问控制权限允许,一个类在继承了某个基类以后就可以无须声明地使用基类的成员;*/// 定义基类
class Base; // 基类只声明,没有定义,编译器会报错,所有基类必须需要明确定义// 定义派生类
class Derived : public Base {
public:Derived() {}
protected:int a;
};
int main() {return 0;
}
上面代码编译不通过,Base只是什么一个类,未一定要明确的实现,故而编译不通过..........
12.2: 派生类与基类转换
派生类与基类的转换:
派生类的对象可以直接访问基类的成员,这是因为派生类实际上是包含基类的,而基类的成员将会排列在考前的位置。
如果这时候要将派生类转换成基类,派生类自己的成员将会被截掉;所以从输出结果中可以看到,派生类放进基类的容器时
发生了强制类型转换,大小由8个字节变为了4个字节,也就是基类中一个成员的大小。这个时候就无法访问派生类中的d变量了,
因为当前这个对象的类型是Base; 派生类可以直接转换为基类,因为派生类中包含基类的内容;相反基类就不具备派生类的所有成员;
所有基类不能直接转换为派生类;但是可以用指向基类的指针指向派生类,然后再确定当前类的类型时将基类指针转换成派生类的指针;
#include <iostream>
#include <vector>using namespace std;
/*派生类与基类的转换:派生类的对象可以直接访问基类的成员,这是因为派生类实际上是包含基类的,而基类的成员将会排列在考前的位置。如果这时候要将派生类转换成基类,派生类自己的成员将会被截掉;所以从输出结果中可以看到,派生类放进基类的容器时发生了强制类型转换,大小由8个字节变为了4个字节,也就是基类中一个成员的大小。这个时候就无法访问派生类中的d变量了,因为当前这个对象的类型是Base; 派生类可以直接转换为基类,因为派生类中包含基类的内容;相反基类就不具备派生类的所有成员;所有基类不能直接转换为派生类;但是可以用指向基类的指针指向派生类,然后再确定当前类的类型时将基类指针转换成派生类的指针;*/class Base {
public:Base() { b = 0; }protected:int b; // 定义基类的成员变量b;
};// 定义派生类
class Derived :public Base {
public:Derived() { d = 0; }int d; // 声明公共成员变量d;
};
int main() {vector<Base> baseVector; // 定义一个向量,向量中装载的元素时Base;Base base; // 定义基类的实例对象base;// 定义子类的实例变量Derived derived;cout << "derived实例对象所占的字节数大小: " << sizeof(derived) <<" 字节" << endl;baseVector.push_back(derived);baseVector.push_back(base);cout <<"放入baseVector向量容器中的derived的大小为:"<< sizeof(baseVector.back()) <<" 字节" << endl;
}
运行效果:
derived实例对象所占的字节数大小: 8 字节
放入baseVector向量容器中的derived的大小为:4 字节D:\program_file\C++_workspace\ProjectCodeToBaseClassChange\x64\Debug\ProjectCodeToBaseClassChange.exe (进程 19548)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
12.3:通过指针访问派生类的成员变量
#include <iostream>
#include <vector>using namespace std;
/*派生类与基类的转换:派生类的对象可以直接访问基类的成员,这是因为派生类实际上是包含基类的,而基类的成员将会排列在考前的位置。如果这时候要将派生类转换成基类,派生类自己的成员将会被截掉;所以从输出结果中可以看到,派生类放进基类的容器时发生了强制类型转换,大小由8个字节变为了4个字节,也就是基类中一个成员的大小。这个时候就无法访问派生类中的d变量了,因为当前这个对象的类型是Base; 派生类可以直接转换为基类,因为派生类中包含基类的内容;相反基类就不具备派生类的所有成员;所有基类不能直接转换为派生类;但是可以用指向基类的指针指向派生类,然后再确定当前类的类型时将基类指针转换成派生类的指针;*/class BaseDerived {
public:BaseDerived() { b = 0; }protected:int b; // 定义基类的成员变量b;
};// 定义派生类
class Derived :public BaseDerived {
public:Derived() { d = 0; }int d; // 声明公共成员变量d;
};
int main() {// 定义子类的实例变量Derived derived;BaseDerived* basePtr = &derived; // 声明一个Base类型的指针变量cout << "basePtr指向的对象的大小为: " << sizeof(*basePtr) << " 字节" << endl;Derived* derivedPtr = (Derived*)basePtr; cout << "d的值为: " << derivedPtr->d << endl;cout << "派生类转换为基类时,没有对失自己的成员,这个情况下我们知道对象属于什么类型\n就可以安全地进行转换;" << endl;cout << "但在实际编程中,这样的做法是比较危险的;除非我们给类的每个对象标识符它们的类型;\n需要注意的是," << endl;cout << "这个basePtr指向的对象大小是4而不是8,这是因为系统在这个时候只有指针的类型信息;还是把这个对象当成了基类;" << endl;cout << "计算其大小时也是按照基类的大小计算;" << endl;return 0;
}
运行效果:
basePtr指向的对象的大小为: 4 字节
d的值为: 0
派生类转换为基类时,没有对失自己的成员,这个情况下我们知道对象属于什么类型
就可以安全地进行转换;
但在实际编程中,这样的做法是比较危险的;除非我们给类的每个对象标识符它们的类型;
需要注意的是,
这个basePtr指向的对象大小是4而不是8,这是因为系统在这个时候只有指针的类型信息;还是把这个对象当成了基类;
计算其大小时也是按照基类的大小计算;D:\program_file\C++_workspace\ProjectToClassDerived\x64\Debug\ProjectToClassDerived.exe (进程 1536)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
十三:继承体系下的构造析构函数调用状况
#include <iostream>
using namespace std;
/*C++中继承体系下构造函数|析构函数的调用概况在实际编程中,我们应该尽量使用指针来操作对象,这样是在实现多态所必需的;*/class InheriBase {
public:InheriBase(int bb) :b(bb) {cout << "基类的构造函数被调用!" << endl;}~InheriBase() {cout << "基类的析构函数被调用!" << endl;}
protected:int b;
};// 派生类
class DerivedInherived :public InheriBase {
public:DerivedInherived(int bb, int dd) : InheriBase(bb), d(dd) {cout << "派生类的构造函数被调用!" << endl;}~DerivedInherived() {cout << "派生类的析构函数被调用!" << endl;}protected:int d;
};
int main() {DerivedInherived derived(23, 11);return 0;
}
运行效果:
基类的构造函数被调用!
派生类的构造函数被调用!
派生类的析构函数被调用!
基类的析构函数被调用!D:\program_file\C++_workspace\ProjectToClassConstructorAndDetoryed\x64\Debug\ProjectToClassConstructorAndDetoryed.exe ( 进程 18412)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
忙着去耍帅,后期补充完整..............
这篇关于C++失传千年经典系列(二):类的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!