2 C++核心编程(一):类与引用基础
- 本文为视频学习笔记
- 学习的视频为B站上黑马程序员-的视频黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难;
- 该视频链接为https://www.bilibili.com/video/av41559729/?p=84,推荐前往学习;
- 侵删;
2.1 内存分区模型
-
代码区:存放函数体的二进制代码,有操作系统管理
- 程序编译后生成exe可执行程序,未执行程序前程序分为代码取和全局区
- 存放CPU执行的及其指令
- 特点:共享;只读
-
全局区:存放全局变量和静态变量以及常量,操作系统管理
-
全局变量:未写在函数体中的变量
-
静态变量:使用static关键字修饰
-
常量:字符串常量-双引号引起来的;const修饰的全局变量
#include<iostream> using namespace std; //全局变量 int g_a = 10; int g_b = 10; //const修饰的全局变量,全局 const int c_g_a = 10; int main() { //全局区 //全局变量、静态变量、常量 //创建普通局部变量 int a = 10; int b = 10; //静态变量 static int s_a = 10; cout << "局部变量a的地址为:" << (int)&a << endl; cout << "局部变量b的地址为:" << (int)&b << endl; cout << "全局变量g_a的地址为:" << (int)&g_a << endl; cout << "全局变量g_b的地址为:" << (int)&g_b << endl; cout << "静态变量s_a的地址为:" << (int)&s_a << endl; //常量 //字符串常量 cout << "字符串常量的地址为:" << (int)"hello world" << endl; //const 修饰的变量 //const 修饰的全局变量,const修饰的局部变量 const int c_g_b = 10; cout << "const修饰的全局变量地址为:" << (int)&c_g_a << endl; cout << "const修饰的局部变量地址为:" << (int)&c_g_b << endl; system("pause"); }
******* 局部变量 局部变量a的地址为:5635760 局部变量b的地址为:5635748 ******* 放于全局区 全局变量g_a的地址为:13942836 全局变量g_b的地址为:13942840 静态变量s_a的地址为:13942844 字符串常量的地址为:13933380 const修饰的全局变量地址为:13933576 ******* 局部变量 const修饰的局部变量地址为:5635736
-
-
栈区:编译器自动分配释放,存放函数的参数值、局部变量等
-
不要返回局部变量的地址,战区的开辟由编译器自动释放
-
#include<iostream> using namespace std; //栈区数据注意事项--不要返回局部变量地址 //栈区数据由编译器管理开辟和释放 int* func(int b) {//形参数据也会放在栈区 b =100; int a = 10;//局部变量存放在栈区,函数执行完后数据自动释放 return &a;//返回局部变量的地址 } int main() { //接受func函数的返回值 int* p = func(); cout << *p << endl;//编译器做了保留,仅一次 cout << *p << endl;//第二次不再保留 system("pause"); return 0; }
-
-
堆区:程序员分配和释放,若程序员不释放结束时有操作系统回收
-
在C++中主要利用new在堆区开辟内存,释放内存使用delete;
-
#include<iostream> using namespace std; int* func() { //利用new关键字可以将数据开辟到堆区 //指针本质上也是局部变量,放在栈上但是指针保存的数据放在堆区 int* p =new int(10);//new返回的时地址 return p; } int main() { //在堆区开辟数据 int* p = func(); cout << *p << endl; system("pause"); return 0; }
-
#include<iostream> using namespace std; int* func() { //利用new关键字可以将数据开辟到堆区 //指针本质上也是局部变量,放在栈上但是指针保存的数据放在堆区 int* p =new int(10);//new返回的时地址 return p; } void test01() { int* p = func(); cout << *p << endl;//堆区数据由程序员管理开辟,程序员管理释放 //利用delete释放 delete p; //cout << *p << endl;内存已经释放,无法再次访问 } void test02() { //创建10整型数据的数组 int * arr=new int[10];//10代表数组由10个数据 for (int i = 0; i < 10; i++) { arr[i] = i + 100;//给10个元素赋值 } for (int i = 0; i < 10; i++) { cout << arr[i] << endl; } //释放数组时需要加[]才可以 delete[] arr; } int main() { //test01(); test02(); system("pause"); return 0; }
-
new使用方法:新建内存地址
- 数据类型 :int* p=new int(10);返回一个内存地址;
- 数组:int * arr=new int[10];[]内为数组长度,同样返回内存地址
-
delete使用方法:释放堆栈的内存
- 数据类型:delete p;
- 数组:delete arr[];
-
-
意义:不同区域存放数据,赋予不同的生命周期,给我们更大的灵活编程
2.2 引用
-
引用的基本使用:
-
给变量起别名
-
语法:数据类型 &别名 = 原名
-
int a = 10; int& b = a; b = 20; cout << a << b << endl;//2020
-
注意:引用必须初始化,初始化之后不可改变
-
int a=10; int &b=a;//必须由一个原名赋值给别名完成初始化 int c=20; b=c;//赋值操作,并非是更改引用 //int &b=c;不可更改引用
-
-
-
引用做函数参数
-
使用引用技术 让形参修饰实参
-
可以简化指针修改实参
//1、值传递 void swap01(int a, int b) { int temp; temp = a; a = b; b = temp; } //2、地址传递 void swap02(int* a, int* b) { int temp; temp = *a; *a = *b; *b = temp; } //3、引用传递 void swap03(int &a,int &b) {//相当于int &a=a,int &b=b int temp=a; a = b; b = temp; } int main() { //test01(); //test02(); //yinyong(); int a = 10; int b = 20; swap01(a, b); cout << "swap01 a=" << a << endl; cout << "swap01 b=" << b << endl; swap02(&a, &b); cout << "swap02 a=" << a << endl; cout << "swap02 b=" << b << endl; swap03(a,b); cout << "swap03 a=" << a << endl; cout << "swap03 b=" << b << endl; system("pause"); return 0; }
-
-
引用做函数的返回值
-
注意:不要返回局部变量引用,函数可以作为左值
-
int& test1() {//返回引用 int a = 10;//局部变量存放在栈区,返回局部变量导致错误 return a; } int& test2() { static int a = 10;//静态变量,全局区 return a;//返回a的地址 } int main() { //引用做函数返回值 //1、不要返回局部变量的引用 int& ref = test1(); int& ref2 = test2(); cout << "ref=" << ref << endl; cout << "ref2=" << ref2 << endl; test2() = 1000;//如何函数的返回值是引用,则可以进行赋值 cout << "ref2=" << ref2 << endl; //2、函数的调用可以作为左值 system("pause"); return 0; }
-
-
引用的本质:在C++内部实现是一个指针常量
-
int& ref =a; //自动转换为int* const ref =&a; ref=20; //内部发现ref是引用,自动转换为 *ref=20;
-
常量引用
//打印数据
void showValue(const int & val) {//防止引用的过程中修改val
//val = 1000;
cout << val << endl;
}
//常量引用
//使用场景:用来修饰形参,防止误操作
int c = 10;
//加上const之后,编译器将代码修改 int temp=10;const int & ref=temp,自动设置了原名;
const int& ref3 = 10;//引用必须引一块合法的内存空间
//ref3 = 20;//加入const之后变为制度,不可以修改
int d = 100;
showValue(d);
2.3 函数提高
- 函数默认参数:如果我们自己传入数据,就用自己的数据,如果没有,那么用默认值
- 语法: 返回值类型 函数名 参数列表(类型 参数=默认值){}
- 注意事项:
- 如果某个位置已经有了默认参数,那么从这个位置往后,从左往右都必须由默认值
- 声明和实现只能一个有默认参数
#include<iostream>
using namespace std;
//如果我们自己传入数据,就用自己的数据,如果没有,那么用默认值
//语法:返回值类型 函数名 (形参=默认值){}
int func(int a, int b=20, int c=30) {
return a + b + c;
}
int func1(int a, int b = 20, int c =40) {
return a + b + c;
}
//注意事项
//1.如果某个位置已经有了默认参数,那么从这个位置往后,从左往右都必须由默认值
//2.如果函数声明有默认参数,函数实现就不能有默认参数
int func2(int a=10, int b=20);//声明和实现只能一个有默认参数
int func2(int a, int b) {
return a + b;
}
int main() {
cout << func(10,30) << endl;
system("pause");
return 0;
}
-
函数占位参数
-
#include<iostream> using namespace std; //占位参数 //返回值类型 函数名(数据类型){} //目前阶段占位参数还用不到 //占位参数还可以有默认参数 void func(int a,int =10) { cout<<"this is func"<<endl; } int main() { func(10,10); system("pause"); return 0; }
-
函数重载
- 作用:函数名可以相同,提高复用性
- 函数重载满足条件:
- 同一个作用域下
- 函数名称相同
- 函数参数类型不同 或者 个数不同 或者 顺序不同
- 注意:函数的返回值的类型不同或者返回变量不同不能作为函数重载的条件
-
#include<iostream> using namespace std; //函数重载的满足条件 /* - 同一个作用域下 -函数名称相同 - 函数参数 *类型不同* 或者 *个数不同* 或者 *顺序不同* */ void func() { cout << "func 的调用" << endl; } void func(int a) { cout << "func(int a) 的调用" << endl; } void func(double a) { cout << "func(double a) 的调用" << endl; } void func(int a,double b) { cout << "func(int a,double b) 的调用" << endl; } void func(double a, int b) { cout << "func(double a, int b) 的调用" << endl; } int main() { int a = 10; double b = 10.0; func(b); //根据不同的传递参数选择不同的函数,int和double对应的函数不同 func(a, b);//顺序不同 func(b, a); system("pause"); return 0; } /* 输出: func(double a) 的调用 func(int a,double b) 的调用 func(double a, int b) 的调用 */
-
引用作为重载条件
#include<iostream> using namespace std; //函数重载的注意事项 //1. 引用作为重载的条件 void func(int& a) { cout<<"func(int &a)调用"<<endl; } void func(const int& a) { cout << "func(const int &a)调用" << endl; } //2. 函数重载碰到默认参数 void func2(int a) { cout << "func2(int a)调用" << endl; } void func2(int a,int b) { cout << "func2(int a,int b)调用" << endl; } int main() { int a = 10; double b = 10.0; func(a);//func(int &a)调用 //func(10);只能func(const int &a)调用 func2(10);//当函数重载碰到默认参数,出现二义性 //默认函数参数时应该保证无歧义 system("pause"); return 0; }
-
函数重载碰到函数默认参数
2.4 类和对象
- C++面向对象的三大特性:封装、继承、多态
2.4.1 封装
-
意义:将属性和行为作为一个整体,表现生活中的事物;将属性和行为加以权限控制
-
类中的属性和行为 同一称之为 成员
- 属性: 成员属性 成员变量
- 行为 成员函数 成员方法
-
访问权限
- public: 成员类内可以访问,类外也可以访问
- protected:成员 类内可以访问 类外不可以访问
- private:成员 类内可以访问 类外不可以访问
- 对于protected和private权限区别为:在继承时子类可以继承父类的protected内容,二不可以访问private内容
-
语法:
class 类名 {访问权限: 属性 /行为}; #include <iostream> #include<string> using namespace std; const double PI = 3.1415; //设计一个圆类求圆的周长 //c=2*Pi*r class Circle { //访问权限 //公共权限 public: //属性 //半径 int m_r; //行为 //获取圆的周长 double calculatec() { return 2 * PI*m_r; } }; class Student { public: string SID; string name; void ShowStudent() { cout << "学生学号为:" <<SID<< " 学生姓名为:"<<name << endl; } }; int main() { //通过圆类 创建具体的圆(对象) Circle c1;//实例化(通过一个类创建一个对象) //给圆对象的属性进行赋值 c1.m_r = 10; //返回圆的周长 cout << "圆的周长为: "<<c1.calculatec()<<endl; Student s1;//实例化#include <iostream> #include<string> using namespace std; const double PI = 3.1415; //设计一个圆类求圆的周长 //c=2*Pi*r class Circle { //访问权限 //公共权限 public: //属性 //半径 int m_r; //行为 //获取圆的周长 double calculatec() { return 2 * PI*m_r; } }; class Student { public://公共权限 //类中的属性和行为 同一称之为 成员 //属性: 成员属性 成员变量 //行为 成员函数 成员方法 //属性 string SID; string name; void ShowStudent() { cout << "学生学号为:" <<SID<< " 学生姓名为:"<<name << endl; } private: }; int main() { //通过圆类 创建具体的圆(对象) Circle c1;//实例化(通过一个类创建一个对象) //给圆对象的属性进行赋值 c1.m_r = 10; //返回圆的周长 cout << "圆的周长为: "<<c1.calculatec()<<endl; Student s1; s1.SID = "123455667"; s1.name = "seeffreg"; s1.ShowStudent(); system("pause"); return 0; } s1.SID = "123455667"; s1.name = "seeffreg"; s1.ShowStudent(); system("pause"); return 0; }
-
struct 和class的区别
- 唯一的区别在于 默认的访问权限不同
- struct默认权限为公共
- class 默认权限为私有
- 唯一的区别在于 默认的访问权限不同
#include <iostream>
#include<string>
using namespace std;
class C1 {
int m_A;//默认权限是私有
};
struct C2 {
int m_A;//默认权限是公共
};
int main() {
//struct 和class区别
//struct 默认权限是公共 public
//class 默认权限是 私有 private
C1 c1;
//c1.m_A = 100;不可以访问
C2 c2;
c2.m_A=100;//在struct 默认的权限是公共,可以访问
system("pause");
return 0;
}
-
成员属性设置为私有
-
优点1:将所有成员属性设置为私有,可以自己控制读写权限
- 对于private内的数据可以使用public内的函数调用,从而对数据进行更改
-
优点2:对于写权限,可以检测数据的有效性
-
#include <iostream> #include<string> using namespace std; //成员属性设置为私有 //优点1:将所有成员属性设置为私有,可以自己控制读写权限 //优点2:对于写权限,可以检测数据的有效性 //设计人类 class Person { public: //写姓名 读写 void setName(string name) { m_Name = name; } //读姓名 读写 string getName() { return m_Name; } //获取年龄 只读 int getAge() { m_Age = 0;//初始值 return m_Age; } //设置情人 只写 void setLover(string lover) { m_Lover = lover; } private: //姓名 可读可写 string m_Name; //年龄 只读 int m_Age; //情人 只写 string m_Lover; }; int main() { Person p; p.setName("张三"); cout << "姓名为: "<<p.getName()<<endl; cout << "年龄为: " << p.getAge() << endl; p.setLover("sshhd"); system("pause"); return 0; }
-
-
案例1:
#include <iostream> #include<string> using namespace std; //立方体类设计 //1.创建立方体类 //2.设计属性 //3.设计行为 获取立方体面积和体积 //4.分别利用全局函数和成员函数 判断两个立方体是否相等 class Cube { public: //行为 //设置长 void setL(int l) { m_L = l; } //获取长 int getL() { return m_L; } //设置宽 void setW(int w) { m_W = w; } //获取宽 int getW() { return m_W; } //设置高 void setH(int h) { m_H = h; } //获取高 int getH() { return m_H; } //获取面积 int calculateS() { return 2 * m_L * m_W + 2 * m_W * m_H + 2 * m_L * m_H; } //获取立方体体积 int calculateV() { return m_H * m_L * m_W; } //利用成员函数判断两个立方体是否相等 bool isSameByClass(Cube &c2) { if (m_L == c2.getL() && m_W == c2.getW() && m_H == c2.getH()) { return true; } else { return false; } } private: //属性 int m_L;//长 int m_W;//宽 int m_H;//高 }; //利用全局函数判断两个立方体是否相等 bool isSame(Cube& c1, Cube& c2) { if (c1.getL() == c2.getL() && c1.getW() == c2.getW() && c1.getH() == c2.getH()) { return true; } else { return false; } } int main() { Cube c1;//创建实例对象 c1.setL(10); c1.setH(10); c1.setW(10); cout<<"c1的表面积为: "<<c1.calculateS()<<endl; cout << "c1的体积为: " << c1.calculateV() << endl; Cube c2; c2.setL(5); c2.setH(10); c2.setW(15); cout << "c2的表面积为: " << c2.calculateS() << endl; cout << "c2的体积为: " << c2.calculateV() << endl; bool ret = isSame(c1, c2); if (ret) { cout << "c1和c2是相等的" << endl; } else { cout << "c1和c2是不相等的" << endl; } //成员函数判断结果 ret = c1.isSameByClass(c2); if (ret) { cout << "c1和c2是相等的" << endl; } else { cout << "c1和c2是不相等的" << endl; } system("pause"); return 0; }
-
对象的初始化和清理
-
构造函数和析构函数:初始化和清理
-
编译器会提供空的构造函数和析构函数
-
构造函数:
类名(){}
- 没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,可以发生重载
- 程序在调用对像时会自动调用构造,无需手动调用,且只会调用一次
-
析构函数:
~类名(){}
- 没有返回值也不写void
- 函数名称与类名相同,在名称前面加上符号~
- 构造函数不可以有参数,因此不可以发生重载
- 程序在调用对像销毁前会自动调用析构,且只会调用一次
#include<iostream> #include<string> using namespace std; //对象的初始化和清理 //1.构造函数 及逆行初始化操作 class Person { //1.1 构造函数 //没有返回值也不写void // 函数名称与类名相同 //- 构造函数可以有参数,可以发生重载 //- 程序在调用对像时会自动调用构造,无需手动调用,且只会调用一次 public: Person() { cout << "Person构造函数调用: " << endl; } //2.析构函数 进行清理操作 //-没有返回值也不写void //- 函数名称与类名相同,在名称前面加上符号~ //- 构造函数不可以有参数,因此不可以发生重载 //- 程序在调用对像销毁前会自动调用析构,且只会调用一次 ~Person() { cout << "Person构造函数调用: " << endl; } }; //构造和析构都是必须有的实现,编译器会自动提供空的 void test01() { Person p;//栈上的数据,test01执行后释放对象 } int main() { test01(); system("pause"); return 0; }
-
-
构造函数的分类及调用
- 两种分类方式:
- 按参数分为:有参构造和无参构造
- 按类型分为:普通构造和拷贝构造
- 三种调用方式:
- 括号法
- 显示法
- 隐式转换法
class Student { public: int age; //构造函数 Student() { cout << "构造函数调用: " << endl; } Student(int a) { age = a; cout << "构造函数调用: " << endl; } //拷贝构造函数 Student(const Student& s) { //将传入的人身上所有属性,拷贝到自身 age = s.age; cout << "拷贝构造函数调用: " << endl; } ~Student() { cout << "析构函数调用: " << endl; } }; //调用 void test02() { //括号法 /* Student s1;//默认构造函数 Student s2(10);//有参构造函数 Student s3(s2);//拷贝构造函数 */ //注意事项 //调用默认构造函数时不要加() //Student s1();不能创建对象,会默认为函数声明 //显示法 Student s1; Student s2 = Student(10);//有参构造 Student s3 = Student(s3);//拷贝构造 Student(10);//匿名对象,特点:当前行执行结束后,系统会立即回收掉匿名对象 cout << "aaaaa" << endl; //注意事项2 //不要利用拷贝构造函数 初始化匿名对象 //编译器会认为Student(s2)=Student s2; //Student(s2); //隐式转换法 Student s4 = 10;//相当于写了一个Student s4=Student(10);有参构造 Student s5 = s4; } int main() { //test01(); test02(); system("pause"); return 0; }
- 两种分类方式:
-
拷贝构造函数调用时机
-
使用一个已经创建完成的对象来初始化一个对像
-
值传递的方式给函数参数传值
-
以值方式返回局部对象
#include<iostream> using namespace std; //拷贝构造函数调用时机 //1 使用一个已经创建完成的对象来初始化一个对象 //2 值传递的方式给函数参数传值 //3 以值方式返回局部对象 class Per { public: int m_Age; Per() { cout<<"Person 默认构造函数调用"<<endl; } Per(int age) { m_Age = age; } Per(const Per& p) { m_Age = p.m_Age; } ~Per() { cout<<"Person析构函数调用"<<endl; } }; //1 使用一个已经创建完成的对象来初始化一个对象 void test1() { Per p1(20); Per p2(p1); cout<<"P2的年龄为:"<<p2.m_Age<<endl; } //2 值传递的方式给函数参数传值 void dowork(Per p) { } void test2() { Per p; dowork(p); } //3 以值方式返回局部对象 Per dowork2() { Per p1; return p1; } void test03() { Per p = dowork2(); } int main() { //test01(); //test02(); //test1(); //test2(); test03(); system("pause"); return 0; }
-
-
构造函数调用规则
- 默认情况下,c++至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 构造函数调用规则:
- 如果用户定义有参数构造函数,c++不再提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
- 默认情况下,c++至少给一个类添加3个函数
-
深拷贝与浅拷贝
-
浅拷贝:简单的赋值拷贝操作
-
深拷贝:再堆区重新申请空间,进行拷贝操作
-
属性在堆区开辟时,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
#include<iostream> #include<string> using namespace std; //深拷贝与浅拷贝 class Person { public: int m_Age;//年龄 int* m_height; Person() { cout << "Person 的默认构造函数调用" << endl; } Person(int age,int height) { m_Age = age; m_height=new int(height); cout << "Person的有参构造函数调用" << endl; } //自己实现拷贝构造函数,解决浅拷贝带来的问题 Person(const Person &p) { cout << "Person 拷贝构造函数调用" << endl; m_Age = p.m_Age; //编译器自动提供的m_height=p.m_height语句为浅拷贝 //深拷贝操作 m_height = new int(*p.m_height); } ~Person() { //析构代码:将堆区开辟的数据做释放操作 if (m_height != NULL) { delete m_height; m_height = NULL; } cout<<"Person的析构函数调用"<<endl; } }; int main() { Person p1(18,160); Person p2(p1);//使用编译器提供的拷贝构造函数,做浅拷贝操作 //浅拷贝导致堆区的内存重复释放 //神靠被可以解决堆区内存重复释放的问题 cout<<"p1的年龄:"<<p1.m_Age<<*p1.m_height<<endl; cout<<"p2的年龄"<<p2.m_Age<<*p2.m_height<<endl; system("pause"); return 0; }
-
-
初始化列表:
-
作用:C++提供初始化列表语法,用来初始化属性
-
语法:
构造函数(): 属性(值1),属性2(值2)...() #include<iostream> #include<string> using namespace std; class Person { public: /* //传统初始化操作 Person(int a, int b, int c) { m_A = a; m_B = b; m_C = c; } */ //初始化列表初始化属性 Person() :m_A(10), m_B(20), m_C(30) { } Person(int a,int b,int c) :m_A(a), m_B(b), m_C(c) { } int m_A; int m_B; int m_C; }; void test() { // Person p(10,20,30); Person p(30,20,10); cout << "m_A="<< p.m_A<<endl; cout << "m_B=" << p.m_B << endl; cout << "m_C=" << p.m_C << endl; } int main() { test(); system("pause"); return 0; }
-
-
类对象作为类成员
class A{} class B{ A a; }
- B 类中有对象A作为成员,A为对象成员
#include<iostream> #include<string> using namespace std; //类对象作为类的成员 class Phone { public: Phone(string name) { m_Pname = name; cout << "Phone的构造函数调用" << endl; } ~Phone() { cout << "Phone的析构函数" << endl; } //手机品牌名称 string m_Pname; }; class Person { public: //Phone m_Phone=pNmae;隐式转换法 Person(string name, string Pname):m_name(name),m_Phone(Pname) { cout << "Person的构造函数调用" << endl; } ~Person() { cout << "Person的析构函数" << endl; } //姓名 string m_name; //手机 Phone m_Phone; }; //当其他类对象作为本类成员,先构造其他类对象在构造自身 // void test01() { Person p("张三","iphpne MAX"); cout<<p.m_name<<" 拿着: " << p.m_Phone.m_Pname<<endl; } int main() { test01(); system("pause"); return 0; } /* 输出: Phone的构造函数调用 Person的构造函数调用 张三 拿着: iphpne MAX Person的析构函数 Phone的析构函数 */
-
静态成员
-
静态成员:在成员变量和成员函数前加上关键字static
-
静态成员变量:
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
-
静态成员函数
- 所有对象共享一个函数
- 静态成员函数只能访问静态成员变量
-
#include<iostream> using namespace std; //静态成员函数 //所有对象共享同一个函数 //静态成员函数只能访问静态成员变量 class Person { public: //静态成员函数 static void func() { m_A = 100;//静态成员函数可以访问 静态成员变量 //m_B = 200; //静态成员函数 不可以访问 非静态成员变量 //无法区分是那个对象的非静态成员变量 //需要使用具体对象名调用 cout << "static void func" << endl; } static int m_A;//静态成员变量 int m_B;//非静态成员变量 //静态成员函数也具有访问呢权限 private : static void func2() { cout<<"静态成员函数具有访问权限"<<endl; //类外是不能访问的 } }; int Person::m_A = 0;//静态成员变量初始化 //有两种访问方式 void test01() { //1.通过对象访问 Person p; p.func(); //2.通过类名访问,因为所有对象共享一个函数 Person::func(); //Person::func2();不能访问私有权限的静态函数 } int main() { test01(); system("pause"); return 0; }
-
2.5 C++对象模型和this指针
-
成员变量和成员函数分开存储
-
只有非静态的成员变量才属于类的对象上
#include<iostream> using namespace std; //静态成员函数 //所有对象共享同一个函数 //静态成员函数只能访问静态成员变量 class Person { public: //静态成员函数 static void func() { m_A = 100;//静态成员函数可以访问 静态成员变量 //m_B = 200; //静态成员函数 不可以访问 非静态成员变量 //无法区分是那个对象的非静态成员变量 //需要使用具体对象名调用 cout << "static void func" << endl; } static int m_A;//静态成员变量 int m_B;//非静态成员变量 //静态成员函数也具有访问呢权限 private : static void func2() { cout<<"静态成员函数具有访问权限"<<endl; //类外是不能访问的 } }; int Person::m_A = 0;//静态成员变量初始化 //有两种访问方式 void test01() { //1.通过对象访问 Person p; p.func(); //2.通过类名访问,因为所有对象共享一个函数 Person::func(); //Person::func2();不能访问私有权限的静态函数 } //成员变量 和 成员函数 分开存储 class Person1 { }; void test02() { Person1 p; cout<<"size of p="<<sizeof(p)<<endl; //空对象占用的内存空间为:1 //C++编译会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置 //每个空对象也应该有一个独一无二的内存地址 } class Person2 { int m_A;//非静态成员变量 属于类的对象上 static int m_B;//静态成员变量 不属于类的对象上 void func() { }//非静态成员函数 不属于类上的对象 static void func1() { }//静态成员函数 不属于类的对象上 }; void test03(){ Person2 p; cout<<"size of p="<<sizeof(p)<<endl; //含有一个Int类型变量的类大小为4字节 } int main() { //test01(); test02(); test03(); system("pause"); return 0; }
-
-
this指针
- this 指针指向被调用的成员函数所属的对象
- this指针是隐含每一个非静态成员函数内的一种指针
- this指针不需要定义,直接使用即可
- this指针的用途:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this
#include<iostream> using namespace std; class Person { public: Person(int age) { //this指针指向被调用的成员函数所属的对象 this->age = age; } int age;//成员变量做好使用m_age命名 Person& PersonAddAge(Person &p) { this->age += p.age; //this 指向p2的指针,而*this指向的是p2这个对象本体,使用引用做为返回值 return *this; } }; //1 解决名称冲突 void test01() { Person p1(18); Person p2(10); p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1); //链式编程思想 cout << "p2的年龄为:" << p2.age << endl; cout << "p1的年龄为:" << p1.age << endl; } //2 返回对象本身用*this int main() { test01(); system("pause"); return 0; }
-
空指针访问成员函数
- C++空指针也可以调用成员函数,但是也要注意到有没有用到this指针
- 如果用到this指针,需要判断代码的健壮性
-
const 修饰成员函数
-
常函数:
- 成员函数后加const后为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字 mutable 后,在常函数中依然可以修改
-
常对象:
- 声明对象前加const称改对象为常对象
- 常对象只能调用常函数
-
#include<iostream> using namespace std; //常函数 class Person{ public: void showPerson() const//常函数,无法修改属性 //const修饰的是this指针,让指针指向的值也不可以更改 { this->m_A=100; //this的本质是指针常量,指向不可修改 } int m_A; mutable int m_B;//mutable 可以 即使在常函数中也可以修改 } void test01(){ Person p; p.showPerson(); } //常对象 void test02(){ const Person p;//对象前加const,变为常对象 // p.m_A=100;报错,常对象下不可修改 p.m_B=100;//m_B在常对象下也可以修改 p.showPerson();//常对象只能调用常函数 } int main(){ system("pause"); return 0; }
-
2.6 友元
-
有元目的:让一个函数或者类 访问另一个类中的私有成员
-
关键字: friend
-
三种实现:
- 全局函数友元
- 类做友元
- 成员函数做友元
#include<iostream> #include<string> using namespace std; class Building; //类做友元 class GoodGuy { public: GoodGuy(); void visit();//参观函数 访问Building中属性 Building* building; }; class Building { //friend class GoodGuy;//声明友元类 friend void GoodGuy::visit();//对于其他类成员函数的友元声明 //friend void goodGuy(Building* building);//声明友元,全局函数 public: string m_SittingRoom;//客厅 Building(); private: string m_Bedroom;//卧室 }; //类外写成员函数 Building::Building() { m_SittingRoom = "客厅"; m_Bedroom = "卧室"; } //全局函数做友元 void goodGuy(Building* building) { cout<<"正在访问:"<<building->m_SittingRoom <<endl; //cout << "正在访问:" << building->m_Bedroom << endl; } GoodGuy::GoodGuy() { //创建建筑物对象 building = new Building; } void GoodGuy::visit() { cout << "类正在访问: " << building->m_SittingRoom << endl; cout << "友元类正在访问: " << building->m_Bedroom << endl; } void test01() { Building building; //goodGuy(&building); GoodGuy gg; gg.visit(); } int main() { test01(); system("pause"); return 0; }
-
2.7 运算符重载
-
概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
- 对于常见类型使用已有运算符,对于class等数据需要重新定义运算符以满足不同的数据 类型需求
-
加法运算符重载
-
#include<iostream> #include<string> using namespace std; //加号运算符 class Person { public: //1 成员函数重载+号 /* Person operator+(Person& p) { Person temp; temp.m_A = this->m_A + p->m_A; temp.m_B = this->m_B + p->m_B; return temp; } */ int m_A; int m_B; }; //2 全局函数重载+号 Person operator+(Person& p1, Person& p2) { Person temp; temp.m_A = p1.m_A + p2.m_A; temp.m_B = p1.m_B + p2.m_B; return temp; } void test01() { Person p1; p1.m_A = 10; p1.m_B = 10; Person p2; p2.m_A = 10; p2.m_B = 10; //Person p3 = p1 + p2; //成员函数重载本质调用 //Person p3 = p1.operator+(p2); //全局函数重载本质调用 Person p3 = operator+(p1, p2); cout<<"p3.m_A= "<<p3.m_A<<endl; cout << "p3.m_B= " << p3.m_B << endl; } int main() { test01(); system("pause"); return 0; }
-
左移运算符重载
//左移运算符重载 class Person1 { friend void test02(); friend ostream& operator<<(ostream& cout, Person1& p); private : //利用成员函数重载 左移运算符,不成立,无法实现cout在左侧 //void operator<<() { //} int m_A; int m_B; }; //只能使用全局函数重载左移运算符 //cout的类型为ostream ostream& operator<<(ostream &cout,Person1 &p)//本质 operator<<(cout,p)简化cout<<p { cout << "m_A=" << p.m_A << " m_B" << p.m_B << endl; return cout; } void test02() { Person1 p; p.m_A = 10; p.m_B = 10; cout << p; } int main() { //test01(); test02(); system("pause"); return 0; }
-
递增运算符重载
#include<iostream> #include<string> using namespace std; //重载递增运算符 //自定义整型 class MyInteger { friend ostream& operator<<(ostream& cout, MyInteger myint); public: MyInteger() { m_Num = 0; } //重载前置++运算符 //返回一个一直可以递增的数据,链式编程 MyInteger& operator++() { //先进行++运算 ++this->m_Num; //将自身做返回 return *this;//解引用后再进行引用 } //重载后置++运算符 //返回值不作为函数重载条件;条件:同作用域;参数不同 //void operator++(int) int代表占位参数,可以用于区分前置和后置 MyInteger operator++(int) { //先 记录下当时的结果 MyInteger temp = *this; //后 递增操作 m_Num++; //再返回当时结果 return temp; } private: int m_Num = 0; }; //重载<<运算符 ostream& operator<<(ostream& cout,MyInteger myint) { cout << myint.m_Num; return cout; } void test1() { MyInteger myint; cout << myint++ << endl; cout << myint << endl; } int main() { test1(); system("pause"); return 0; }
-
赋值运算符重载
- c++编译器至少给一个类添加4个函数
-
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符 operator= 对属性进行值拷贝
-
#include<iostream> #include<iostream> using namespace std; //赋值运算符重载 class Person2 { public : Person2(int age) { m_Age= new int(age); } ~Person2() { if (m_Age != NULL) { delete m_Age; m_Age = NULL; } } //重载 赋值运算符 Person2& operator=(Person2 & p) { //编译器是提供浅拷贝 //m_Age=p.m_Age; //应该先判断是否有属性再堆区,如果有先释放干净,然后再深拷贝 if (m_Age != NULL) { delete m_Age; m_Age = NULL; } //深拷贝 m_Age = new int(*p.m_Age); //返回对象自身 return *this; } int* m_Age; }; void testp2() { Person2 p1(18); Person2 p2(20); Person2 p3(22); cout << "p1年龄为" << *p1.m_Age << endl; cout << "p2年龄为" << *p2.m_Age << endl; p3 = p2 = p1;//赋值操作 //赋值操作是浅拷贝,两次释放空间导致错误 //利用深拷贝解决问题 cout<<"p1年龄为"<< *p1.m_Age<<endl; cout << "p2年龄为" << *p2.m_Age << endl; } // //int main() { // testp2(); // //int a = 10; // //int b = 20; // //int c = 30; // //c = b = a;//允许连等 // system("pause"); // return 0; //}
- c++编译器至少给一个类添加4个函数
-
关系运算符重载
- 作用:重载关系运算符,可以让两个自定义类型对象进行对比操作
#include<iostream> #include<iostream> using namespace std; //重载关系运算符 class Person3 { public : string m_Name; int m_Age; Person3(string name, int age) { m_Name = name; m_Age = age; } //重载==号 bool operator==(Person3& p) { if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) { return true; } return false; } //重载!=号 bool operator!=(Person3& p) { if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) { return false; } return true; } }; void test11() { Person3 p1("Tom", 18); Person3 p2("Tom", 19); if (p1 != p2) { cout << "p1和p2是不相等的!" << endl; } } int main() { test11(); system("pause"); return 0; }
-
函数调用运算符重载
-
函数调用运算符()也可以重载
-
由于重载后使用方式非常像函数的调用,因此称为仿函数
-
仿函数没有固定写法,非常灵活
#include<iostream> #include<iostream> using namespace std; //打印输出类 class MyPrint { public: //重载函数调用运算符 void operator()(string test) { cout << test << endl; } }; void MyPrint02(string test) { cout << test << endl; } void test12() { MyPrint myprint; myprint("HelloWorld"); //由于使用起来非常类似于函数调用,因此称为仿函数 MyPrint02("HelloWorld!"); } //仿函数非常灵活,没有固定的写法 //加法类 class MyAdd { public: int operator()(int num1,int num2) { return num1 + num2; } }; void test13() { MyAdd myadd; int ret = myadd(100, 100); cout << "" << ret << endl; //匿名函数对象 //实际是匿名对象的仿函数 //匿名函数对象的小括号是用于构造匿名函数对象 //使用之后立即销毁 cout << MyAdd()(100, 100) << endl; } int main() { //test12(); test13(); system("pause"); return 0; }
-
-