开发网站公司门户网站,书店网站建设策划书,关于建设门户网站的通知,wordpress qq音乐一、 内存分区模型
C程序在执行时#xff0c;将内存大方向划分为4个区域
代码区#xff1a;存放函数体的二进制代码#xff0c;由操作系统进行管理 全局区#xff1a;存放全局变量和静态变量以及常量 栈区#xff1a;由编译器自动分配释放#xff0c;存放函数的参数值、…一、 内存分区模型
C程序在执行时将内存大方向划分为4个区域
代码区存放函数体的二进制代码由操作系统进行管理 全局区存放全局变量和静态变量以及常量 栈区由编译器自动分配释放存放函数的参数值、局部变量等 堆区由程序员分配和释放若程序员不释放程序结束时由操作系统回收 内存四区的意义不同区域存放的数据赋予不同的生命周期给我们更大的灵活编程
1、 程序执行前
在程序编译后生成了.exe课执行程序未执行该程序前分为两个区域
代码区
存放CPU执行的机械命令 代码区是共享的共享的目的是对于频繁被执行的程序只需要在内存中有一份代码即可 代码区是只读的使其只读的原因是防止程序意外地修改了它的指令 全局区
全局变量和静态变量存放在此 全局区还包括了常量区、字符串常量和其他常量也存放在此 该区域的数量在程序结束后由操作系统释放
#include iostream
using namespace std;// 全局变量
int global_a 10;
int global_b 10;// 全局常量
const int c_g_a 10;
const int c_g_b 10;int main() {// 全局区// 全局变量、静态变量、常量// 静态变量static int static_a 10;static int static_b 10;// 常量// 字符串常量cout 字符串常量的地址为 (int)hello endl;// const修饰的变量// const修饰的全局变量// const修饰的局部变量const int c_l_a 10;const int c_l_b 10;// 创建普通局部变量int a 10;int b 10;cout 局部变量a的地址为 (int)a endl;cout 局部变量b的地址为 (int)b endl;cout 局部常量c_l_a的地址为 (int)c_l_a endl;cout 局部常量c_l_b的地址为 (int)c_l_b endl;cout 全局变量global_a的地址为 (int)global_a endl;cout 全局变量global_b的地址为 (int)global_b endl;cout 静态变量static_a的地址为 (int)static_a endl;cout 静态变量static_b的地址为 (int)static_b endl;cout 全局常量c_g_a的地址为 (int)c_g_a endl;cout 全局常量c_g_b的地址为 (int)c_g_b endl;
}2、 程序运行后
栈区
由编译器自动分配释放存放函数的参数值全局变量等 注意事项不要返回局部变量的地址栈区开辟的数据由编译器自动释放
#include iostream
using namespace std;int* func(int b) { // 形参数据也会放在栈区b 100;int a 10; // 局部变量在栈区在函数运行完成之后就被清除return a; // 返回局部变量的地址非法函数
}int main() {cout *func() endl; // 第一次可以打印正确的数字是因为编译器做了留cout *func() endl; // 第二次的数据可能不会保留
}堆区
由程序员分配释放若程序员不释放程序结束时由操作系统回收 在C中主要利用new在堆区开辟内存
#include iostream
using namespace std;int* func() {// 利用new关键字 可以将数据开辟到堆区int* p new int(10); // 指针本质也是局部变量放在栈上指针保存的数据放在堆区return p;
}int main() {// 在堆区开辟数据int* p func();cout *p endl;
}3、 new操作符 C中利用new操作符在堆区开辟数据
堆区开辟的额数据由程序员手动开辟手动释放释放利用操作符delete
语法new 数据类型
利用new创建的数据会返回该数据对应的类型的指针
#include iostream
using namespace std;int* func() {// new方法开辟一个数组int* p new int(10); // 返回的是该数据类型的指针return p;
}void test() {//创建10整型数据的数组在堆区 int* arr new int[10]; // 10代表数组有10个元素for (int i 0; i 10; i) {arr[i] i; // 给数组赋值}// 释放堆区数组释放数组的时候要加[]才可以delete[] arr;
}int main() {int* p func();cout *p endl; // 堆区的数据由程序员管理数据delete p; // 数据已经释放再次访问就是非法操作cout *p endl;test();
}二、 引用
1、 基本使用 作用给变量起别名
语法数据类型 别名 变量名;
#include iostream
using namespace std;int main() {// 引用基本语法// 数据类型 别名 变量名int a 10;int b a;cout b endl;b 100;cout a endl;
}2、 注意事项 引用必须初始化 引用在初始化后不可以改变
#include iostream
using namespace std;int main() {int a 10;// 引用必须要初始化int b a;// int b; // 非法操作// 引用一旦初始化后就不可以更改其地址但是可以赋值
}3、 引用做函数参数
作用函数传参时可以利用引用的技术让形参修饰实参
优点可以简化指针修改实参
#include iostream
using namespace std;// 交换函数
// 1、值传递
void swap(int a,int b) {int temp a;a b;b temp;
}// 2、 地址传递
void swap1(int* a, int* b) {int temp *a;*a *b;*b temp;
}// 3、 引用传递
void swap3(int a, int b) { // a即为别名别名可以和变量名字一样int temp a;a b;b temp;
}int main() {int a 10;int b 20;cout a b endl;cout 运行函数 endl;swap(a, b);cout a b endl;// 实参并没有改变cout a b endl;cout 运行函数 endl;swap1(a, b);cout a b endl;// 实参发生改变cout a b endl;cout 运行函数 endl;swap3(a, b);cout a b endl;// 实参发生改变
}通过引用参数产生的效果同按地址传递是一样的引用的语法更加清楚简单
4、 引用做函数的返回值
作用引用是可以作为函数的返回值存在的
注意不要返回局部变量的引用
用法函数的调用作为左值
#include iostream
using namespace std;// 引用做函数的返回值
// 1、 不要返回局部变量的引用
int test1() { // 加相当于以引用的方式返回int a 10; // 局部变量存放在四区中的 栈区return a;
}// 2、 函数的调用可以作为左值
int test2() {static int a 10; // 静态变量存放在全局区全局区上的数据在程序结束后系统释放return a;
}int main() {int b test1();cout b endl;cout b endl;int c test2();cout c endl;test2() 1000; // 内存地址修改同时函数的调用可以作为左值cout c endl;
}5、 本质
本质引用的本质在C内部的实现是一个指针常量
6、 常量引用
作用常量引用主要用来修饰形参防止误操作
在函数形参列表中使用
#include iostream
using namespace std;// 打印数据
void showValue(int a) { // 可以加入const防止误操作a 1100;cout a endl;
}int main() {// 常量引用用来修饰形参防止误操作int a 10;const int ref 10; // 加const之后编译器将代码修改 int temp 10; const int ref temp;// ref 20; // 加入const之后变为只读showValue(a);cout a endl;
}三、 函数提高
1、 默认参数 在C中函数的形参列表中的形参是可以有默认值的
语法返回值类型 函数名 (参数 默认值) {}
#include iostream
using namespace std;// 函数的默认参数
void func(int a , int b 10) { // 自己也可以传入参数如果传入参数就使用自定义参数否则使用默认参数cout a \t b endl;
}// 注意事项
// 如果莫个位置已经有了默认参数那么从这个位置以后都必须要有默认值
// 如果函数声明有默认参数那么函数的实现就不能有默认参数不能重定义参数即声明和实现只能有一个有默认参数
void func(int a, int b);int main() {int a 20;// int b 30;func(a);
}2、 占位参数
C中函数的形参列表里可以有占位参数用来做占位调用函数是必须填补这个位置
语法返回值类型 函数名(数据类型){}
#include iostream
using namespace std;// 占位参数可以有默认参数
// 返回值类型 函数名(数据类型){}
void func(int a, int) {cout this is func endl;
}void func(int a, int);int main() {func(10, 20);
}3、 函数重载
3.1 概述 作用函数名可以相同提高函数名的复用性
函数重载的条件
同一作用域下 函数名相同 函数参数类型不同 或者 个数不同 或者 顺序不同 注意函数的返回值不可以作为函数重载的条件
#include iostream
using namespace std;// 函数重载
// 在同一作用域下
// 函数名相同
// 函数参数类型不同或者个数不同或者顺序不同
void func(double a, int b) {cout 10 endl;
}
void func(string name) {cout name endl;
}
void func(int a, double b) {cout 1010 endl;
}int main() {func(hello);func(1.0, 20);func(20, 1.0);
}3.2 注意事项
引用作为重载条件 函数重载碰到函数默认参数
#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, int b 10) {cout func(int a默认参数)调用 endl;
}
void func2(int a) {cout func(int a)调用 endl;
}int main() {const int a 10;int b 20;func(a);func(b);func2(a, b);
}四、 类与对象
C面向对象的三大特征封装、继承、多态
C认为万事万物都皆为对象对象上有其属性和行为
1、 封装 1.1 封装的意义 封装是C面向对象的三大特性之一
封装的意义
将属性和行为作为一个整体表现生活中的事物
在设计类的时候属性和行为写在一起表现事物
语法class 类名 { 访问权限: 属性 / 行为 };
// 设置一个圆类求其周长
#include iostream
using namespace std;// 圆周率
const double PI 3.14;class Circle {// 访问权限公共权限
public: // 属性半径int m_r; // 行为获取圆的周长double calcculate() {return 2 * PI * m_r;}
};int main() {// 通过圆类来创建对象实例化对象Circle c1;// 给圆对象的属性赋值c1.m_r 10;cout 圆的周长为 c1.calcculate() endl;
}案例设计一个学生类属性有姓名和学号可以给姓名和学号赋值可以显示学生的姓名和学号
#include iostream
using namespace std;
#include string;class Student
{// 访问权限
public:// 属性string stu_name;int stu_id;// 行为void showInfo() {cout 姓名 stu_name 学号 stu_id endl;}// 给属性赋值void setInfo(string name, int id) {stu_name name;stu_id id;}
};int main() {Student stu; // 实例化stu.setInfo(张三, 1); stu.showInfo();
}类中的属性和行为我们同一称为成员
成员属性成员变量成员方法/成员函数
将属性和行为加以权限控制
类在设计时可以把属性和行为放在不同的权限下加以控制
访问权限有
public 公共权限 protected 保护权限 private 私有权限
#include iostream
using namespace std;
#include string;// public 成员类内可以访问类外也可以访问
// protected 成员类内可以访问类外不可以访问儿子也可以访问父亲保护的内容
// private 成员类内可以访问类内不可以访问儿子不可以访问父亲的私有内容class Person {// 公共权限
public:string name;// 保护权限
protected:string car;// 私有权限
private:int money;public:void func() {name zhansan;car tuolaji;money 20;}};
int main() {// 实例化具体对象Person p1;p1.name lishi;// p1.car benc; // 保护权限在类外不能访问// p1.money 30; // 私有权限内容类外也不能访问
}1.2 struct 和 class 的区别 在C中struct 和 class 唯一区别就在于 默认访问的权限不同
struct 默认权限为公共 class 默认权限为私有
#include iostream
using namespace std;
#include string;class C1 {int A; // 默认权限是私有
};
struct C2
{int A2; // 默认公有
};
int main() {// struct 默认公有C2 c2;c2.A2 100; // 可以访问// class 默认私有C1 c;// c.A 100; // 不能修改
}1.3 成员属性私有化 优点
将所有成员属性设为私有可以自己控制读写权限 对于写权限我们可以检测数据的有效性
#include iostream
using namespace std;
#include string;// 成员属性私有化
class Person {
public:// 设置姓名、获取姓名 提供权限void setName(string pname) {name pname;}string getName() {return name;}/* // 获取年龄int getAge() {age 18; // 初始化为18岁return age;} */// 可读可写年龄如果想修改年龄范围必须是0到18岁int getAge() {return age;}// 设置年龄void setAge(int num) {if (num 0 || num 18) {age 15;cout 您输入的年龄有问题 endl;return;}cout num endl;age num;}// 只写财产void setMoney(int num) {money num;}private: // 全部设置为私有// 姓名 可读可写string name;// 年龄 只读int age; // 财产 只写int money;
};int main() {Person p;p.setName(张三);cout 名字 p.getName() endl;p.setAge(150); // 如果输入的值不符合要求直接强制赋值cout 年龄 p.getAge() endl;
}1.4 案例 设计一个立方体类Cube求出立方体的面积和体积分别用全局函数和成员函数判断两个立方体是否相等
#include iostream
using namespace std;// 创建一个立法体的类
class Cube {// 设计属性
private:int c_l; // 长int c_h; // 高int c_w; // 宽// 行为 获取立方体的面积和体积
public:// 设置属性void setInfo(int l, int w, int h) {c_l l;c_w w;c_h h;}// 获取属性int getL() {return c_l;}int getH() {return c_h;}int getW() {return c_w;}// 获取面积int getS() {return c_l * c_h * 2 c_l * c_w * 2 c_h * c_w * 2;}// 获取体积int getV() {return c_l * c_h * c_w;}// 成员函数判断是否相等bool isSameByClass(Cube c) {if (getH() c.getH() getW() c.getW() getL() c.getL()) {return true;}return false;}};// 利用全局函数和成员函数判断两个立方体是否相等// 全局函数
bool isSame(Cube c1, Cube c2) { // 引用传递if (c1.getH() c2.getH() c1.getW() c2.getW() c1.getL() c2.getL()) {return true;}return false;
}int main() {// 第一个正方体Cube c1;c1.setInfo(1, 1, 1);int s1 c1.getS(); // 体积int v1 c1.getV(); // 面积// 第二个正方体Cube c2;c2.setInfo(1, 1, 2);int s2 c2.getS();int v2 c2.getV();if (isSame(c1, c2)) {cout 相同 endl;}else{cout 不相同 endl;}if (c1.isSameByClass(c2)) {cout 成员判断相同 endl;}else{cout 成员判断不相同 endl;}
}判断点在圆上的位置
#include iostream
using namespace std;// 判断点和圆的关系
// 点类
class Point {
private:int x;int y;
public:void setPoint(int x, int y) {x x;y y;}int getx() {return x;}int gety() {return y;}
};// 圆类
class Circle {
private:int r; // 半径Point circlePoint; // 代表圆心
public:void setR(int r) {r r;}int getR() {return r;}void setCenter(Point center) {circlePoint center;}Point getCenter() {return circlePoint;}
};// 判断点和圆的关系
void isInCircle(Circle c, Point p) {// 计算距离的平方if (pow(c.getCenter().getx() - p.getx(), 2) - pow(c.getCenter().gety() - p.gety(), 2) pow(c.getR(), 2)) {cout 点在圆上 endl;return;}cout 点不在圆上 endl;
}int main() {// 创建圆Circle c;Point ct;ct.setPoint(10, 0);c.setCenter(ct);c.setR(10);Point p;p.setPoint(10, 10);isInCircle(c, p);
}一个类可以实例化另一个类作为其属性
2、 对象的初始化和清理
生活中我们买的电子产品都基本会有出厂设置在某一天我们不用的时候删除一些自己信息数据保证安全 C中的面向对象来源于生活每个对象都会有初始化设置以及对象销毁前清理数据的设置 2.1 构造函数和解析函数 对象的初始化和清理也是两个非常重要的安全问题
一个对象或者变量没有初始状态对其使用后果是未知的 同样使用完一个对象或变量没有及时清理也会造成一定安全问题 C利用了构造函数和析构函数解决上述问题这两个函数将会被编译器自动调用完成对象初始化操作和清理工作
对象的初始化和清理工作是编译器强制要我们做的事情因此如果我们不提供构造函数和析构编译器会提供
编译器提供的构造函数和析构函数是空实现的
构造函数主要作用在于创建对象时为对象的成员属性赋值构造函数由编译器自动调用无须手动调用 析构函数主要作用在于对象销毁前系统自动调用执行一些清理工作 构造函数语法类名() {}
构造函数没有返回值也不写void 函数名字与类名相同 构造函数可以有参数也可以发生重载 程序在调用对象时候会自动调用构造无须手动调用而且只会调用一次 析构函数语法~类名() {}
析构函数没有返回值也不写void 函数名与类名相同在名称前面加上符号~ 析构函数不可以有参数因此不可以发生重载 程序在对象销毁前会自动调用析构无须手动调用而且只会调用一次
#include iostream
using namespace std;class Person {
public:// 构造函数Person() {cout Person 构造函数的调用 endl;}// 析构函数~Person() {cout Person 析构函数的调用 endl;}};
// 构造和析构都是必须有的实现如果我们自己不提供编译器会提供一个空实现的构造和析构
void test() {Person p; // 栈上的数据test执行完毕之后会自动释放这个对象
}
int main() {// 对象的初始化和清理//test();Person p;system(pause);return 0;
}2.2 构造函数的分类及调用
两种分类方式
按参数分类有参构造和无参构造 按类型分类普通构造和拷贝构造 三种调用方式
括号法 显示法 隐式转换法
#include iostream
using namespace std;// 构造函数的分类和调用
class Person {
public:int age;// 构造函数// 无参构造Person() {cout Person 构造函数的调用 endl;age 1;}// 有参构造Person(int a) {age a;cout Person 构造函数的调用有参 a endl;}// 拷贝构造函数Person(const Person p) {// 将传入的人身上的所有属性拷贝到我身上age p.age;cout Person 构造函数的调用拷贝 age endl;}
};
void test() {// 调用// 括号法Person p; // 默认构造函数的调用Person p02(10); // 调用有参构造函数Person p03(p02); // 拷贝构造函数的调用 //cout p2的年龄为: p2.age endl;//cout p3的年龄为: p3.age endl;// 显示法Person p1;Person p2 Person(10); // 有参构造Person p3 Person(p2); // 拷贝构造//Person(10); // 匿名对象特点当前行执行结束后系统会立即回收掉匿名对象// 隐式转换法Person p4 10; // 相当于 Person p4 Person(10);Person p5 p4;
}
int main() {test();
}注意事项
调用默认构造函数的时候不要加()
因为下面这行代码编译器会认为是一个函数的声明
Person p1();
不要利用拷贝构造函数初始化匿名对象
因为编译器会认为Person p4(p3) Person p3;认为其为对象的声明
Person p4(p3);
2.3 拷贝函数的调用时机
C中拷贝函数调用时机通常由三种情况
使用一个已经创建完毕的对象来初始化一个新对象 值传递的方式给函数参数传值 以值方式返回局部对象
#include iostream
using namespace std;// 拷贝构造函数的调用时机class Person {
public:Person() {cout 默认函数 endl;}Person(int age) {age age;cout 有参函数 endl;}Person(const Person p) {age p.age;cout 拷贝函数 endl;}~Person() {cout 类被释放 endl;}int age;
};
void test01() {
// 使用一个已经创建完毕的对象来初始化一个新对象Person p1(20);Person p2(p1);cout p2.age endl;
}
void doWork( Person p ) {cout doWork endl;
}
void test02() {
// 值传递的方式给函数参数传值Person p;doWork(p);
}
Person doWork02() {Person p1;return p1;
}
void test03() {
// 以值方式返回局部对象Person p doWork02();
}
int main() {//test01();//test02();test03();system(pause);return 0;
}2.4 构造函数调用规则
默认情况下C编译器至少给一个类添加3个函数
默认构造函数无参函数体为空 默认析构函数无参函数体为空 默认拷贝函数对属性进行值拷贝 构造函数调用规则
如果用户定义有参构造函数C下不会提供默认无参构造但是会提供默认拷贝构造 如果用户定义拷贝构造函数C不会再提供其他构造函数
#include iostream
using namespace std;// 构造函数的调用规则// 1. 创建一个类C编译器会给每个类添加至少三个函数析构函数默认函数拷贝函数// 如果用户定义有参构造函数C下不会提供默认无参构造但是会提供默认拷贝构造
class Person {
public://Person() {// cout 默认函数调用 endl;//}~Person() {cout 析构函数调用 endl;}int age;Person(int age) {age age;cout age endl;}//Person(const Person p) {// age p.age;// cout 拷贝构造函数 endl;// }
};
//void test1() {
// Person p;
// p.age 18;
// Person p2(p);
// cout p2.age 拷贝 endl; // 自动调用拷贝函数
//}
void test2() {Person p(10);Person p2(p);cout p2.age 拷贝 endl;
}
int main() {//test1();test2();
}2.5 深拷贝与浅拷贝
深拷贝简单的赋值拷贝操作
浅拷贝在堆区重新申请空间进行拷贝操作
#include iostream
using namespace std;// 深拷贝、浅拷贝
class Person {
public:int age;int* height;Person() {cout Person的默认构造函数调用 endl;}Person(int age_, int height_) {cout Person的有参构造函数调用 endl;age age_;height new int(height_); // 将身高的数据创建到堆区}Person(const Person p) {cout Person的拷贝构造函数调用 endl;age p.age;// height p.height; // 编译器默认实现的就是这行代码——浅拷贝// 深拷贝操作height new int(*p.height);}~Person() {// 析构代码将堆区开辟的数据做释放操作if (height ! NULL) {delete height;height NULL; // 避免野指针的出现}cout Person的析构函数调用 endl;}
};
void test() {Person p1(18, 160);cout p1的年龄为 p1.age p1的身高为 *p1.height endl;Person p2(p1); // 如果利用编译器提供的拷贝构造函数会做浅拷贝操作浅拷贝带来的问题是堆区的内存会重复释放// 这个问题要用深拷贝来解决cout p2的年龄为 p2.age p2的身高为 *p2.height endl;
}
int main() {test();system(pause);return 0;
}2.6 初始化列表
作用
C提供了初始化列表语法用来初始化属性
语法构造函数():属性1(值1)属性2(值2)…{}
#include iostream
using namespace std;// 初始化列表
class Person {
public:// 传统初始化操作利用传参构造函数进行操作// 初始化列表初始化属性// Person() :a(10), b(20), c(30) {// }// 更灵活的初始化Person(int a, int b, int c) :a(a), b(b), c(c) {}int a;int b;int c;
};
void test() {Person p(20, 30, 50);cout 创建P endl;cout p.a p.b p.c endl;
}
int main() {test();system(pause);return 0;
}2.7 类对象作为类成员
C类中的成员可以是另一个类的对象我们称该成员为对象成员
class A {}
class B {A a; // B类中有对象A作为成员A为对象成员
}那么当创建B对象时A与B的构造和析构顺序是谁先谁后呢
#include iostream
using namespace std;// 类对象作为类成员
class Phone {
public:string c_Pname;Phone(string pName) {c_Pname pName;cout phone构造函数 endl;}~Phone() {cout Phone析构函数 endl;}
};
class Person {
public:string c_Name;Phone c_Phone;Person(string name, string pName): c_Name(name), c_Phone(pName) {cout person构造函数 endl;}~Person() {cout person析构函数 endl;}
};
void test() {Person p(张三, 水果13ProMax);cout c_Name: p.c_Name c_Phone: p.c_Phone.c_Pname endl;
}
int main() {test();system(pause);return 0;
}构造的顺序是先调用对象成员的构造在调用本类的构造
析构的顺序相反
2.8 静态成员 静态成员就是在成员变量和成员函数前加上关键字static称为静态成员
静态成员分为
静态成员变量 所有对象共享同一份数据 在编译阶段分配内存 类内声明类外初始化 静态成员函数 所有对象共享同一个函数 静态成员函数只能访问静态成员变量
#include iostream
using namespace std;// 静态成员函数
class Person {
public:// 静态成员函数static void func() {c_A 100;// c_B 100; // 静态成员函数不可以访问非静态成员变量cout static void func c_A endl;}static int c_A; // 静态成员变量类内申明int c_B; // 非静态成员变量
};
int Person::c_A 10; // 类外的初始化
void test() {// 访问Person p;p.func(); // 通过对象访问Person::func(); // 通过类名访问 :: 为域操符
}
int main() {test();system(pause);return 0;
}静态函数也有访问权限
3、 C 对象模型和 this 指针 3.1 成员变量和成员函数分开存储 在 C 中类内成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象
#include iostream
using namespace std;// 成员变量和成员函数分开存储
class Person {int c_A; // 非静态成员变量属于类的对象static int c_B; // 静态成员变量不属于类对象上面void fn() {}; // 非静态成员函数不属于类对象上static void fn() {}; // 静态成员函数 不属于类的对象
};void test01() {Person p;/*空对象占用内存空间为1C 编译器会给每个空对象也分配一个字节空间是为了区分空对象占内存的位置每个空对象也应该有一个独一无二的内存地址*/cout sizeof(p) endl;
}int main() {test01();system(pause);return 0;
}3.2 this 指针 每个非静态成员函数只会诞生一份函数实例也就是说多个同类型的对象会共用一块代码
C 通过提供特殊的对象指针this 指针来判断这一块代码是否被对象调用。
this 指针指向被调用的成员函数所属的对象
this 指针是隐含每一个非静态成员函数内的一种指针
this 指针不需要定义直接使用即可
用途
当形参和成员变量同名时可用 this 指针来区分 在类的非静态成员函数中返回对象本身可使用 return *this
#include iostream
using namespace std;class Person {
public:Person(int age) {this-c_age age; // this 指针指向被调用的成员函数所属的对象}int c_age;Person addAge(Person p) { // Person 返回值的引用如果返回一个值会创建新的对象this-c_age p.c_age;return *this; // this 指向调用函数的对象的指针故 *this 指向的就是对象的本体 }
};void test01() {Person p1(18);cout p1的年龄为 p1.c_age endl;
}
void test02() {Person p1(18);Person p2(19);// 链式编程思想p2.addAge(p1).addAge(p1);cout p2的年龄为 p2.c_age endl;
}int main() {test01();test02();system(pause);return 0;
}3.3 空指针访问成员函数 C 中空指针也是可以调用成员函数的但是也要注意有没有用的 this 指针
如果用到 this 指针需要加以判断保证代码的健壮性
#include iostream
using namespace std;class Person {
public:void showClassName() {cout this is Person class endl;}void showPersonAge() {// 报错的原因是因为传入的指针为 NULLif (this NULL) {return;}cout age c_Age endl;}int c_Age;
};void test01() {Person* p NULL;p-showClassName();//p-showPersonAge();
}
int main() {test01();system(pause);return 0;
} 3.4 const 修饰成员函数 常函数
成员函数后加 const 后我们称这个函数为常函数 常函数内不可以修改成员属性 成员属性声明时加关键字 mutable 后在常函数中依然可以修改 常对象
声明函数前加 const 称该对象为常对象 常对象只能调用常函数
// 常函数
class Person {
public:// this 指针的本质是指针常量指针的指向是不可以修改的void showPerson() const { // - 相当于 const Person * const this; 原本是 Person * const this; // m_A 100; // 会报错this-m_B 100;}int m_A;mutable int m_B; // 特殊变量即使在常函数中也可以修改这个值加关键字 mutable
};// 常对象
void test() {const Person p; // 其为常对象// p.m_A 100; // 不允许修改p.m_B 100; // m_B 是特殊值在常对象下也可以修改// 常对象只能调用常函数其不能调用普通成员函数因为普通成员函数可以修改属性
}4、 友元
在程序里有些私有属性也想让类外特殊的一些函数或者类访问就需要用到友元的技术
友元的目的就是让一个函数或者类访问另一个类中私有成员
友元的关键字为friend
友元的三种实现
全局函数做友元 类做友元 成员函数做友元 4.1 全局函数
// 全局函数做友元
class Building {// goodGay 全局函数是 Building 的好朋友可以访问 Building 中私有成员friend void goodGay(Building* building);public:Building() {m_Bedroom 卧室;m_Sittingroom 客厅;}public:string m_Sittingroom; private:string m_Bedroom;
};// 全局函数
void goodGay(Building* building) {cout 好盆友访问 building-m_Sittingroom endl;cout 好盆友访问 building-m_Bedroom endl;
}
void test01() {Building building;goodGay(building);4.2 类
// 类做友元
class Building {// 友元friend class GoodGay;public:Building();public:string m_Sittingroom; private:string m_Bedroom;
};
// 类外写成员函数
Building::Building() {m_Bedroom 卧室;m_Sittingroom 客厅;
}
// 类
class GoodGay {
public:GoodGay();void visitHouse();Building* building;
};
// 类外写成员函数
GoodGay::GoodGay() {// 创建建筑物对象building new Building;
}
void GoodGay::visitHouse() {cout 好朋友正在访问 building-m_Sittingroom endl;cout 好朋友正在访问 building-m_Bedroom endl;
}void test() {GoodGay gg;gg.visitHouse();
}4.3 成员函数
class Building; // 类声明
class GoodGay {
public:GoodGay();void visit(); // 让函数可以访问 Building 中私有成员void visit1(); // 让函数不可以访问私有成员Building* building;
};
class Building {friend void GoodGay::visit(); // 使用域操符
public:Building();string m_Sitttingroom;
private:string m_Bedroom;
};
// 类外实现成员函数
GoodGay::GoodGay() {building new Building;
}
void GoodGay::visit() {cout 正在访问 building-m_Sitttingroom endl;cout 正在访问 building-m_Bedroom endl;
}
void GoodGay::visit1() {cout 正在访问 building-m_Sitttingroom endl;// cout 正在访问 building-m_Bedroom endl; // 报错
}
Building::Building() {m_Sitttingroom 客厅;m_Bedroom 卧室;
}void test() {GoodGay gg;gg.visit();cout --------- endl;gg.visit1();
}5、 运算符重载 运算符重载概念对已有的运算符重新进行定义赋予其另一个功能以适应不同的数据类型
5.1 加号运算符 作用实现两个自定义数据类型相加的运算
// 加号运算符重载
class Person {
public:// 成员函数重载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;
};
// 全局函数重载
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 test() {Person p1;p1.m_A 10;p1.m_B 10;Person p2;p2.m_A 10;p2.m_B 10;// 成员函数的本质调用// Person p3 p1.operator(p2);// 全局函数的本质调用// Person p3 operator(p1, p2);Person p3 p1 p2;cout p3.m_A p3.m_A \tp3.m_B p3.m_B endl;
}运算符重载也可以发生函数重载
Person operator(Person p1, int num) {Person temp;temp.m_A p1.m_A num;temp.m_B p1.m_B num;return temp;
} // 数字相加对于内置的数据类型的表达式的运算符是不可以改变的 5.2 左移运算符 作用可以输出自定义的数据类型
// 左移运算符重载
class Person
{
public:int m_A;int m_B;/* 利用成员函数重载p.operator(cout); - 简化函数 p cout;不会利用成员函数重载运算符因为无法实现 cout 在左侧void operator (ostream cout){cout a: m_A b: m_B endl;} */
};
void test()
{Person p;p.m_A 10;p.m_B 10;cout p endl;
}
// 只能利用全局函数重载ostream其属于标准输出流
ostream operator (ostream cout, Person p) // 本质operator (cout, p); - 简化 cout p;
{cout a: p.m_A b: p.m_B;return cout; // 链式编程要有返回值
}总结重载左移运算符配合友元可以实现输出自定义数据类型
5.3 递增运算符 作用通过重载递增运算符实现自己的整型数据
// 重载自增运算符
// 自定义整型
class MyInt
{friend ostream operator(ostream cout, MyInt i); // 友元访问私有元素
public:MyInt(){m_num 0;}// 重载前置自增运算符MyInt operator(){m_num; // 自增操作return *this;}// 重载后置自增运算符MyInt operator(int) // int 代表占位参数用于区分前置递增和后置递增注意不要使用引用{// 先记录当时结果MyInt temp *this;// 后自增m_num;// 最后返回结果return temp;}
private:int m_num;
};
// 重载左移运算符
ostream operator(ostream cout, MyInt i)
{cout i.m_num;return cout;
}
void test()
{MyInt i;cout i -- i endl;cout endl;cout i -- i endl;
}5.4 赋值运算符 C 编译器至少给一个类添加四个函数
默认构造函数无参函数体为空 默认析构函数无参函数体为空 默认拷贝函数对属性进行值拷贝 赋值运算符 operator对属性进行值拷贝 如果中有属性指向堆区做赋值操作时也会出现深浅拷贝问题
class Person
{
public:Person(int age){m_Age new int(age); // 将数据开辟到堆区}// 重载赋值运算符Person operator(Person p){// 编译器提供浅拷贝 this-m_Age p.m_Age;// 应该判断是否有属性在堆区如果有先释放干净然后再深拷贝if (this-m_Age) // 相当于 if (this-m_Age ! NULL){delete this-m_Age; // 删除数据m_Age NULL;}m_Age new int(*p.m_Age);// 返回对象本身return *this;}int* m_Age;~Person(){if (m_Age) // 当m_Age不为空时{delete m_Age;m_Age NULL;}}
};
void test()
{Person p1(10);Person p2(20);p2 p1; // 赋值操作默认为浅拷贝cout p1的年龄为 *p1.m_Age endl;cout p2的年龄为 *p2.m_Age endl;
}5.5 关系运算符 作用重载关系运算符可以让两个自定义对象进行对比操作
// 重载关系运算符
class Person
{
public:Person(string name, int age);// 重载 号其余类似bool operator(Person p); string m_Name;int m_Age;
};
Person::Person(string name, int age)
{this-m_Name name;this-m_Age age;
}
bool Person::operator(Person p)
{if (this-m_Name p.m_Name this-m_Age p.m_Age){return true;}else{return false;}
}
void test()
{Person p1(Tom, 18);Person p2(Tom, 18);Person p3(李华, 18);if (p1 p2){cout p1 p2 endl;}else{cout p1 ! p2 endl;}
}6、 继承
继承是面向对象三大特性之一
有些类与类之间存在特殊关系
如动物 - 猫 - 加菲猫
我们发现定义这些类时下级别的成员除了拥有上一级的共性还有自己的属性
这时候我们可以考虑利用继承的技术减少重复代码
6.1 基础语法 语法class 子类 : 继承方式 父类
例如很多网站中都有公共的头部公共的底部甚至公共的左侧列表栏
/*
class Java
{
public:Java() // 使用构造函数输出内容{cout Java 页面 endl;header();buttom();left();content();cout endl;}void header(){cout 首页、公开课、登录、注册... endl;}void buttom(){cout 帮助中心、交流合作、站内地图... endl;}void left(){cout Java / Python / C ... endl;}void content(){cout 关于 Java 的课程 endl;}
};
class Python // 许多内容和 Java 的内容一样
{
public:Python() {cout Python 页面 endl;header();buttom();left();content();cout endl;}void header(){cout 首页、公开课、登录、注册... endl;}void buttom(){cout 帮助中心、交流合作、站内地图... endl;}void left(){cout Java / Python / C ... endl;}void content(){cout 关于 Python 的课程 endl;}
};
// 里面有很多函数重复
*/
// 使用继承思想
class PublicPage // 定义公共页面
{
public:void header(){cout 首页、公开课、登录、注册... endl;}void buttom(){cout 帮助中心、交流合作、站内地图... endl;}void left(){cout Java / Python / C ... endl;}
};
class Java : public PublicPage
{
public:Java(){cout Java 页面 endl;header();buttom();left();content();cout endl;}void content(){cout 关于 Java 的课程 endl;}};
class Python : public PublicPage
{
public:Python(){cout Python 页面 endl;header();buttom();left();content();cout endl;}void content(){cout 关于 Python 的课程 endl;}};
void test()
{Java java;Python python;
}子类也称为派生类
父类也称为基类
6.2 继承方式
继承不能访问私有权限的内容
继承方式有三种
公共继承
基类中所有 public 成员在派生类中为 public 属性 基类中所有 protected 成员在派生类中为 protected 属性 基类中所有 private 成员在派生类中不能使用 保护继承
基类中的所有 public 成员在派生类中为 protected 属性 基类中的所有 protected 成员在派生类中为 protected 属性 基类中的所有 private 成员在派生类中不能使用。 私有继承
基类中的所有 public 成员在派生类中均为 private 属性 基类中的所有 protected 成员在派生类中均为 private 属性 基类中的所有 private 成员在派生类中不能使用
6.3 对象模型 从父类继承过来的成员哪些属于子类对象中
// 继承中的对象模型
class Base
{
public:int m_A;
private:int m_B;
protected:int m_C;
};
class Son : public Base
{
public:int m_D;
};
void test()
{cout size of son: sizeof(Son) endl;// 在父类中所有非静态成员属性都会被子类继承下去// 父类中私有成员属性是被编译器给隐藏了因此是访问不到的但是继承下去了
}利用开发人员命令提示工具查看对象模型
跳转盘符 跳转文件路径cd 具体路径 查看命令cl /d1 reportSingleClassLayout类名 文件名 6.4 构造和析构顺序 子类继承父类后当创建子类对象也会调用父类的构造函数
class Base
{
public:Base(){cout Base的构造函数 endl;}~Base(){cout Base的析构函数 endl;}
};
class Son : public Base
{
public:Son(){cout Son的构造函数 endl;}~Son(){cout Son的析构函数 endl;}};
void test()
{Son son; // 套娃白发人送黑发人
}6.5 同名成员处理 当子类与父类出现同名成员如何通过子类对象访问到子类或父类中同名的数据呢
访问子类同名成员直接访问 访问父类同名成员需要加作用域
class Base
{
public:Base(){m_A 100;}int m_A;void fn() {cout Base endl;}void fn(int a){cout a endl;}};
class Son : public Base
{
public:Son(){m_A 101;}int m_A;void fn(){cout Son endl;}
};
void test()
{Son s;cout s.m_A s.m_A endl; // 直接访问cout Base.m_A s.Base::m_A endl; // 添加作用域s.fn();s.Base::fn();s.Base::fn(100);
}同名函数调用方式类似添加作用域
如果子类中出现和父类同名的成员函数子类的同名成员函数会隐藏掉父类中所有同名成员函数
如果想访问到父类中被隐藏的同名成员函数需要加作用域
6.6 同名静态成员处理 静态成员和非静态成员出现同名处理方式一致
访问子类同名成员直接访问即可 访问父类同名成员需要加作用域
class Base
{
public:static int m_A; // 类内声明static void fn(){cout Base endl;}
};
int Base::m_A 100; // 类外要初始化
class Son : public Base
{
public:static int m_A;static void fn(){cout Son endl;}
};
int Son::m_A 101;void test()
{cout 通过类名访问数据 endl;Son s;cout s.m_A s.m_A endl;cout Base.m_A s.Base::m_A endl;cout 通过类名访问数据 endl;cout Son.m_A Son::m_A endl;cout Base.m_A Son::Base::m_A endl; // 第一对冒号代表通过类名的方式访问作用域第二对冒号代表访问父类作用域下
}函数调用方式类似
6.7 多继承语法 C 允许一个类继承多个类
语法class 子类 : 继承方式 父类1, 继承方式 父类2···
多继承可能会引发父类中有同名成员出现需要加作用域区分
C 实际开发不建议使用多继承
6.8 菱形继承 问题
当 B 继承了 A 的数据C 同样继承了 A 的数据当 D 使用数据时就会产生二义性 D 继承了两份数据其实我们清楚只要继承一份数据就可以了
class A
{
public:int m_Age;
};
/*
利用虚继承解决菱形继承的问题
继承之前加上关键字 virtual 变为虚继承
A 类称为虚基类
*/
class B : virtual public A{};
class C : virtual public A{};
class D : public B, public C{};
void test()
{D d;d.B::m_Age 18;d.C::m_Age 28;// 当出现菱形继承时两个父类拥有相同的数据需要加以区分cout B.m_Age d.B::m_Age endl;cout C.m_Age d.C::m_Age endl;// 这份数据只要有一份数据就可以了菱形继承导致有两份数据资源浪费
}利用虚继承解决菱形继承的问题 继承之前加上关键字 virtual 变为虚继承 A 类称为虚基类
7、 多态
7.1 基本概念 多态是 C 面向对象三大特性之一
多态分为两类
静态多态[函数重载](#3、 函数重载) 和 [运算符重载](#5、 运算符重载)属于静态多态复用函数名 动态多态派生类 和 虚函数实现运行时多态 静态多态 和 动态多态的区别
静态多态的函数地址早绑定 - 编译阶段函数地址 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
class A
{
public:virtual void speak() // 虚函数可以实现地址晚绑定{cout A is speaking endl;}};
class B : public A
{
public:void speak(){cout B is speaking endl;}
};
// 执行说话函数
void doSpeak(A a) // A a b;
{a.speak(); // 如果没加virtual则地址早绑定在编译阶段确定函数地址// 如果要执行 b.speak(); 需要在运行阶段进行绑定地址晚绑定
}
void test()
{B b;doSpeak(b);
}重写函数函数返回值类型函数名参数列表完全相同
动态多态满足条件
有继承关系 子类重写父类的虚函数子类重写函数可以不为虚函数 动态多态使用
父类指针或者引用执行子类对象 多态优点
代码组织结构清晰 可读性强 利于前期和后期的扩展以及维护
7.2 案例-计算器类
// 如果有扩展功能要实现开闭原则对扩展进行开放对修改进行关闭
class AbstarctCalc // 抽象类
{
public:virtual int getResult() // 虚函数{return 0;}int m_A;int m_B;
};
class Add : public AbstarctCalc // 加法
{
public:int getResult(){return m_A m_B;}
};
class Sub : public AbstarctCalc
{
public:int getResult(){return m_A - m_B;}
};
void test()
{// 多态使用条件// 父类指针或引用指向子类对象AbstarctCalc* abc new Add;abc-m_A 10;abc-m_B 20;cout abc-m_A abc-m_B abc-getResult() endl; // 执行子类对象delete abc; // 销毁数据abc new Sub; abc-m_A 10;abc-m_B 20;cout abc-m_A - abc-m_B abc-getResult() endl;
}7.3 纯虚函数和抽象类 在多态中通常父类中虚函数的实现是毫无意义的主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法virtual 返回值类型 函数名 (参数列表) 0
当类中有了纯虚函数这个类也称为抽象类
抽象类的特点
无法实现实例化对象 子类必须重写抽象类中的纯虚函数否则也属于抽象类
class Base
{
public:virtual void func() 0;
};
class Son : public Base
{
public:void func(){cout Son endl;}
};
void test(Base b) // 引用父类指针
{b.func();
}
void test()
{Son s;test(s);
}7.4 虚析构和纯虚析构 多态使用时如果子类中有属性开辟到堆区那么父类指针在释放时无法调用到子类的析构代码
解决方式将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构的共性
可以解决父类指针释放子类对象 都需要有具体的函数实现 虚析构和纯虚析构的区别
如果是纯虚析构该类属于抽象类无法实例化对象 虚析构语法virtual ~类名(){}
纯虚析构语法类内部virtual ~类名() 0; | 类外类名::~类名() {}
// 虚析构和纯虚析构
class A
{
public:A(){cout A() endl;}virtual void speak() 0;virtual ~A(){cout ~A() endl;}// 纯虚析构 virtual ~A() 0;
};
// A::~A(){} // 纯虚析构需要有函数实现
class C : public A
{
public:C(string name){cout C() endl;m_name new string(name); // 将数据开辟到堆区}void speak(){cout *m_name is speaking endl;}string* m_name; // 没有加 virtual 时没有调用此析构函数~C(){if (m_name){cout ~C() endl;delete m_name;m_name NULL;}}
};
void test()
{A* a new C(Tom);a-speak();// 父类指针在析构时候不会调用子类中析构函数导致子类如果有堆区的属性会出现资源浪费delete a;
}虚析构函数或纯虚析构函数使用来解决通过父类指针释放子类对象问题
如果子类中没有数据开辟到堆区可以不写为虚析构或纯虚析构
拥有纯虚析构函数的类也属于抽象类
五、 文件操作 程序运行时产生的数据都属于临时数据程序一旦运行结束都会被释放
通过文件可以将数据持久化存储
C中对文件操作需要包括头文件
文本文件文件以文本的 ASCII 码形式存储在计算机中 二进制文件文件以文本的二进制形式存储在计算机中用户一般不能直接读懂 操作文件分为三大类
ofstream写操作 ifstream读操作 fstream读写操作
文件打开方式