C++编程:进阶阶段

目录

1 程序的内存模型

1.1 内存四区---代码区

1.2 内存四区---全局区

1.3 内存四区---栈区

1.4 内存四区---堆区

2 C++中的引用

2.1 引用的基本语法

2.2 引用与函数

2.3 引用的本质

3 函数进阶

3.1 函数的默认参数

3.2 函数的占位参数(用法未知)

3.3 函数重载

4 类和对象

4.1 封装

4.1.1 类的定义及实例化对象

4.1.2 访问权限

4.1.3 struct 与 class

4.1.4 案例1:立方体类

4.1.5 案例2:点和圆(分文件)

4.2 对象特征

4.2.1 构造函数和析构函数

4.2.2 构造函数的分类

4.2.3 拷贝函数调用时机

4.2.4 构造函数调用规则

4.2.5 深拷贝与浅拷贝

4.2.6 初始化列表

4.2.7 类对象作为类成员

4.2.8 静态成员

4.2.9 成员变量和成员函数的存储

4.2.10 this指针

4.2.11 空指针访问成员函数

4.2.12 const修饰成员函数

4.3 友元

4.3.1 全局函数做友元

4.3.2 友元类

4.3.3 成员函数做友元

4.4 C++运算符重载

4.4.1 加号运算符重载

4.4.2 左移运算符重载

4.4.3 递增运算符重载

4.4.4 赋值运算符重载

4.4.5 关系运算符重载

4.4.6 函数调用运算符重载

4.5 继承

4.5.1 基本语法

4.5.2 继承方式

4.5.3 继承中的对象模型

4.5.4 继承中构造和析构的顺序

4.5.5 同名成员处理

4.5.6 多继承语法

4.6 多态

4.6.1 多态的基本概念

4.6.2 多态的原理

4.6.3 多态案例1:计算器类

4.6.4 纯虚函数和抽象类

4.6.5 多态案例2:制作饮品

4.6.6 虚析构和纯虚析构

4.6.7 多态案例3:电脑组装

5 C++文件操作


本阶段主要针对C++面向对象编程技术做详细讲解,探讨C++中的核心和精髓。

1 程序的内存模型

C++在执行代码时,将内存分为4个区域,不同区域存放的数据,赋予不同的生命周期。

  • 代码区:存放函数体的二进制代码,由操作系统管理;
  • 全局区:存放全局变量、静态变量和常量;
  • 栈区:由编译器自动分配释放,存放函数的参数值、局部变量等;
  • 堆区:由程序员分配和释放,若程序员不释放,则程序结束时由操作系统回收。

1.1 内存四区---代码区

        代码区是程序运行前分的,在程序编译后运行前,分为代码区和全局区。代码区存放CPU执行的机器指令。

特点:

  • 共享:对于频繁被执行的程序,只需要在内存中有一份代码即可。
  • 只读:防止程序域外修改其中的指令。

1.2 内存四区---全局区

        存放全局变量、静态变量和常量。该区域的数据在程序结束后由操作系统释放。

代码如下:

#include <iostream>
using namespace std;

//定义全局变量
int g_a=10;
int g_b=10;
const int c_g_a=0;
const int c_g_b=0;

int main()
{
    //全局区
    //存放全局变量
    //定义普通局部变量
    int a=10;
    int b=10;
    cout<<"局部变量a的地址为:"<<(long long)&a<<endl;
    cout<<"局部变量b的地址为:"<<(long long)&b<<endl;

    cout<<"全局变量g_a的地址为:"<<(long long)&g_a<<endl;
    cout<<"全局变量g_b的地址为:"<<(long long)&g_b<<endl;

    //定义静态变量
    static int s_a=0;
    static int s_b=0;
    cout<<"静态变量s_a的地址为:"<<(long long)&s_a<<endl;
    cout<<"静态变量s_b的地址为:"<<(long long)&s_b<<endl;

    //定义常量
    //字符串常量
    cout<<"字符串常量的地址为:"<<(long long)&"hello C++"<<endl;

    //const修饰的全局变量
    cout<<"全局常量c_g_a的地址为:"<<(long long)&c_g_a<<endl;
    cout<<"全局常量c_g_b的地址为:"<<(long long)&c_g_b<<endl;

    //const修饰的局部变量
    const int c_l_a=0;
    const int c_l_b=0;
    cout<<"局部常量c_l_a的地址为:"<<(long long)&c_l_a<<endl;
    cout<<"局部常量c_l_b的地址为:"<<(long long)&c_l_b<<endl;

    return 0;
}

输出如下:全局变量的内存地址相近,与局部变量的地址相差明显远。

1.3 内存四区---栈区

        由编译器自动分配释放,存放函数的参数值、局部变量等,不要返回局部变量的地址(函数执行完之后会自动释放),栈区开辟的数据由编译器自动释放。

代码如下:

#include <iostream>
using namespace std;

int* func()
{
    int a=0;//局部变量
    return &a;//返回局部变量的地址 报错(函数返回类型加一个 * 返回局部变量的地址就不会报错)
}

int main()
{
    //接收func函数的返回值
    int * p=func();
    cout <<*p<<endl;//报错
    return 0;
}

错误示例:

1.4 内存四区---堆区

        由程序员分配和释放,若程序员不释放,则程序结束时由操作系统回收。在C++中用new在堆区开辟内存。

代码如下:

#include <iostream>
using namespace std;

int* func()
{
    //利用new将数据开辟到堆区
    int * p=new int(10);//用指针接收内存地址编号
    return p;
}

void func1()
{
    //在堆区开辟数组
    int * arr=new int[10];//用指针接收内存地址编号
    for(int i=0;i<10;i++)
    {
        arr[i]=i+100;
    }
    for(int i=0;i<10;i++)
    {
        cout<<arr[i]<<endl;
    }

    //释放数组
    delete[] arr;
}

int main()
{
    //接收func函数的返回值,接收的是在堆中开辟的地址,而不是局部变量在栈中的地址
    int * p=func();
    cout <<*p<<endl;
    cout <<*p<<endl;

    //手动释放内存
    delete p;
    cout<<*p<<endl;//报错 内存已被释放,再访问不会输出正确的值

    func1();
    return 0;
}

输出如下:

2 C++中的引用

引用的作用:给变量取别名。(别名和原名操作同一个内存地址)

语法:数据类型 &别名=原名;

2.1 引用的基本语法

语法:数据类型 &别名=原名;

PS:引用必须初始化,初始化后不可改变。

代码如下:

#include <iostream>
using namespace std;

int main()
{
    //引用
    int a=9;
    //创建引用
    int &b=a;
    //int &c;//报错,必须初始化
    cout<<"a="<<a<<endl;
    cout<<"b="<<b<<endl;
    
    b=100;

    cout<<"a="<<a<<endl;
    cout<<"b="<<b<<endl;

    int c=20;
    //引用被初始化后不可更改,下面的语句实际上是赋值语句
    b=c;

    cout<<"a="<<a<<endl;
    cout<<"b="<<b<<endl;
    cout<<"c="<<c<<endl;
    
    return 0;
}

输出如下:

错误示例:

2.2 引用与函数

函数传参时,可以利用引用的技术让形参修饰实参,可以简化指针修改实参。

引用传递和地址传递效果相同。

如果函数的返回值是引用,则函数的调用可以作为左值。

代码如下:

#include <iostream>
using namespace std;


//交换函数
//值传递
void func1(int a,int b)
{
    int temp=a;
    a=b;
    b=temp;
}

//地址传递
void func2(int * a,int * b)
{
    int temp=*a;
    *a=*b;
    *b=temp;
}

//引用传递
void func3(int &a,int &b)
{
    int temp=a;
    a=b;
    b=temp;
}

int main()
{
    //引用做函数参数
    int a=9;
    int b=0;

    cout<<"参数传递前"<<endl;
    cout<<"a="<<a<<endl;
    cout<<"b="<<b<<endl;

    //值传递,形参不会修饰实参
    func1(a,b);
    cout<<"值传递后"<<endl;
    cout<<"a="<<a<<endl;
    cout<<"b="<<b<<endl;

    //地址传递,形参会修饰(改变)实参
    func2(&a,&b);
    cout<<"地址传递后"<<endl;
    cout<<"a="<<a<<endl;
    cout<<"b="<<b<<endl;

    //引用传递,形参也会修饰实参
    func3(a,b);
    cout<<"引用传递后"<<endl;
    cout<<"a="<<a<<endl;
    cout<<"b="<<b<<endl;
    return 0;
}

输出如下:

代码如下:

#include <iostream>
using namespace std;

// int& func1()
// {
//     int a=9;//局部变量,存放在栈区
//     return a;
// }

int& func2()
{
    static int a=9;//静态变量,存放在全局区
    return a;
}

int main()
{
    //引用做函数返回值,不要返回局部变量的引用
    //函数的调用可以作为左值
    int a=9;
    int b=0;

    // int& ref=func1();
    // cout<<"ref="<<ref<<endl;

    int& ref2=func2();
    cout<<"ref2="<<ref2<<endl;

    //如果函数的返回值是引用,函数的调用可以作为左值
    func2()=1000;
    cout<<"ref2="<<ref2<<endl;
    return 0;
}

输出如下:

错误示例:返回局部变量的引用

2.3 引用的本质

引用的本质是一个指针常量(指针指向不可更改,指向的数据可以改变)

下面两句代码的效果是一样的。

int& ref=a; 

int * const ref=&a;

2.4 常量引用

作用:用于修饰形参,防止误操作

代码如下:

#include <iostream>
using namespace std;

void show(const int& val)
{
    //val=99;//报错 常量不可修改
    cout<<"val="<<val<<endl;
}

int main()
{
    //常量引用:修饰形参
    int a=9;
    //int& ref=9;//报错  必须引用一块合法的内存空间
    //const int& ref=9;//不再报错,编译器会操作为int temp=9;  const int& ref=temp;
    show(a);
    return 0;
}

错误示例:int& ref=9;

错误示例:修改常量

3 函数进阶

3.1 函数的默认参数

C++中,函数的形参是可以有默认值的。

语法:返回值类型 函数名(参数=默认值){}

PS:如果某个形参有了默认值,则该形参及其后面的形参都必须有默认值。

        如果函数声明有默认参数,则函数实现不能有默认参数。

代码如下:

#include <iostream>
using namespace std;

//如果函数声明有默认参数,则函数实现不能有默认参数。
//int func(int a,int b=20,int c=30);

// 函数默认参数
int func(int a,int b=20,int c=30)
{
    return a+b+c;
}

int main()
{
    //cout<<func(10,20,30)<<endl;
    cout<<func(10)<<endl;
    //如果自己传入了数据,则函数用传入的数据运行
    cout<<func(10,30)<<endl;

    return 0;
}

输出如下:

错误示例:函数声明有默认参数,函数实现也有默认参数。

3.2 函数的占位参数(用法未知)

C++中函数的形参列表里可以有占位参数,用来占位,调用函数时必须填补该位置。

语法:返回值类型 函数名(数据类型){}

PS:占位参数也可以有默认值,如int func(int a,int =10)

代码如下:

#include <iostream>
using namespace std;


// 第二个int为占位参数
int func(int a,int)
{
    cout<<"this is func"<<endl;
}

int main()
{
    func(10,10);

    return 0;
}

错误示例:调用时不传占位参数.

3.3 函数重载

作用:函数名可以相同,提高复用性。

函数重载满足条件:

  • 同一作用域下(目前写的都是全局函数,都在全局作用域下)
  • 函数名称相同
  • 函数参数类型不同 或 个数不同 或顺序不同

PS:函数的返回值不可作为函数重载的条件。

代码如下:

#include <iostream>
using namespace std;

// 函数重载
void func()
{
    cout<<"this is func"<<endl;
}
//函数名相同
void func(int a)
{
    cout<<"this is func(int a)"<<endl;
}
//参数类型不同
void func(double a)
{
    cout<<"this is func(double a)"<<endl;
}
//参数个数不同
void func(int a,double b)
{
    cout<<"this is func(int a,double b)"<<endl;
}
//参数顺序不同
void func(double a,int b)
{
    cout<<"this is func(double a,int b)"<<endl;
}


int main()
{
    func();
    func(10);
    func(3.14);
    func(10,3.14);
    func(3.14,10);

    return 0;
}

输出如下:

PS:引用作为重载条件

        函数重载碰到默认参数。

代码如下:

#include <iostream>
using namespace std;

// 函数重载:引用作为重载条件
void func(int &a )
{
    cout<<"this is func(int& a )"<<endl;
}

void func(const int &a )
{
    cout<<"this is func(const int &a )"<<endl;
}

//函数重载时定义了默认参数,会存在二义性
void func2(int a,int b=10)
{
    cout<<"this is func2(int a,int b=10)"<<endl;
}

void func2(int a )
{
    cout<<"this is func2(int a )"<<endl;
}

int main()
{
    int a=10;
    func(a);
    func(10);

    //func2(10);//报错 存在两个相同的重载函数
    func2(10,20);

    return 0;
}

输出如下:

错误示例:函数重载时定义默认参数。

4 类和对象

C++面向对象的三大特性:封装、继承、多态

具有相同性质的对象,抽象为类

4.1 封装

封装的意义:将属性和行为作为一个整体,表现生活中的事物,并将属性和行为加以权限控制。

4.1.1 类的定义及实例化对象

语法:class 类名{访问权限:属性/行为};

类中的属性和行为统称为成员,属性也可以称为成员属性或成员变量,行为也可以称为成员函数或成员方法。

代码如下:

#include <iostream>
using namespace std;

//设计一个圆类,求圆的周长
const double PI=3.14;

//class代表设计一个类,类后面紧跟的是类名称
class Circle
{
    //访问权限
public://公共权限

    //属性
    //半径
    int r;

    //行为:获取圆的周长
    double calculateZC()
    {
        return 2*PI*r;
    }

};

int main()
{
    //实例化:通过圆类,创建具体的圆(对象)
    Circle c1;

    //给圆对象的属性进行赋值
    c1.r=10;
    cout<<"圆的周长为:"<<c1.calculateZC()<<endl;
    return 0;
}

输出如下:

4.1.2 访问权限

访问权限分为三种:

  • 公共权限:类内外都可以访问成员
  • 保护权限:类内可以访问成员,类外不能访问
  • 私有权限:类内可以访问成员,类外不能访问

代码如下:

#include <iostream>
using namespace std;

class Person
{
    //公共权限
public:
    string name;

    //保护权限
protected:
    string car;
    //私有权限
private:
    int passwd;
public:
    void func()
    {
        name="ddd";
        car="bench";
        passwd=4444;
    }
};
int main()
{
    //实例化一个对象
    Person p1;
    //给圆对象的属性进行赋值
    p1.name="dddd";
    //p1.car="paosij";
    //p1.passwd=1234;
    
    return 0;
}

错误示例:修改保护权限和私钥权限的属性。

成员属性设置为私有,可以自己控制读写权限,对于写权限,可以检测数据的有效性。

代码如下:

#include <iostream>
using namespace std;

class Person
{
public:
    //设置姓名
    void setName(string name)
    {
        m_name=name;
    }

    //获取姓名
    string getName()
    {
        return m_name;
    }

    //获取年龄
    int getAge()
    {
        return m_age;
    }

    //设置偶像
    void setIdol(string idol)
    {
        m_idol=idol;
    }

private:
    string m_name;//姓名 可读可写

    int m_age=18;//年龄 只读

    string m_idol;//偶像 只写


};
int main()
{
    //实例化一个对象
    Person p;
    //设置姓名
    p.setName("ddd");
    //获取姓名
    cout<<"姓名:"<<p.getName()<<endl;

    //设置年龄,不存在该函数
    //p.setAge("ddd");//报错 不存在该成员setAge
    //p.m_age=23;//报错 无法访问
    //获取年龄
    cout<<"年龄:"<<p.getAge()<<endl;

    //设置偶像
    p.setIdol("huge");
    //获取偶像,没有获取函数
    //cout<<"偶像:"<<p.getIdol()<<endl;//报错 不存在该成员getIdol
    //cout<<"偶像:"<<p.m_idol()<<endl;//报错 无法访问

    return 0;
}

错误示例:

4.1.3 struct 与 class

  • struct默认权限为公共
  • class默认权限为私有

错误示例:访问class中默认权限下的成员。

4.1.4 案例1:立方体类

题目:设计立方体类,求出立方体的面积和体积,分别用全局函数和成员函数判断两个立方体是否相等

代码如下:

#include <iostream>
using namespace std;

class Cube
{
private:
    //属性:长宽高
    int l;
    int w;
    int h;

public:
    //设置\获取属性
    void setLong(int ll)
    {
        l=ll;
    }

    int getLong()
    {
        return l;
    }

    void setWidth(int ww)
    {
        w=ww;
    }

    int getWidth()
    {
        return w;
    }

    void setHeight(int hh)
    {
        h=hh;
    }

    int getHeight()
    {
        return h;
    }
    //方法:计算面积、体积、判断两个立方体是否相等
    int getSquare()
    {
        return 2*(l*h+h*w+h*w);
    }

    int getVolume()
    {
        return l*w*h;
    }

    //利用成员函数判断两个立方体是否相等
    void isEqual(Cube &c)
    {
        if(l==c.getLong() && w==c.getWidth() && h==c.getHeight())
        {
            cout<<"两个立方体相同"<<endl;
        }
        else
        {
            cout<<"两个立方体不同"<<endl;
        }
    }
};

//利用全局函数判断两个立方体是否相等
void isSame(Cube &c1,Cube &c2)
{
    if(c1.getLong()==c2.getLong() && c1.getWidth()==c2.getWidth() && c1.getHeight()==c2.getHeight())
    {
        cout<<"两个立方体相同"<<endl;
    }
    else
    {
        cout<<"两个立方体不同"<<endl;
    }
}

int main()
{
    //创建对象
    Cube c1;
    c1.setLong(1);
    c1.setWidth(2);
    c1.setHeight(3);

    cout<<"c1的面积为:"<<c1.getSquare()<<endl;
    cout<<"c1的体积为:"<<c1.getVolume()<<endl;

    Cube c2;
    c2.setLong(1);
    c2.setWidth(2);
    c2.setHeight(3);

    //判断两个立方体是否相等
    isSame(c1,c2);
    c1.isEqual(c2);

    c2.setLong(2);
    isSame(c1,c2);
    c1.isEqual(c2);

    return 0;
}

输出如下:

4.1.5 案例2:点和圆(分文件)

题目:设计一个圆形类和一个点类,计算点和圆的关系。

代码如下:

#include <iostream>
using namespace std;

//点类
class Point
{
private:
    int p_x;
    int p_y;

public:
    //设置/获取X,Y
    void setX(int x)
    {
        p_x=x;
    }
    int getX()
    {
        return p_x;
    }

    void setY(int y)
    {
        p_y=y;
    }
    int getY()
    {
        return p_y;
    }
};

//圆类
class Circle
{
private:    
    int c_r;//半径
    Point center;//圆心

public:
    //设置/获取半径圆心
    void setR(int r)
    {
        c_r=r;
    }
    int getR()
    {
        return c_r;
    }

    void setCer(Point cer)
    {
        center=cer;
    }

    Point getCer()
    {
        return center;
    }
};

//判断点和圆的关系
void isInCircle(Circle &c,Point &p)
{
    int dist=(c.getCer().getX()-p.getX()) * (c.getCer().getX()-p.getX())+
    (c.getCer().getY()-p.getY()) * (c.getCer().getY()-p.getY());
    int rdist=c.getR() * c.getR();

    if(dist==rdist)
    {
        cout<<"点在圆上"<<endl;
    }
    else if(dist>rdist)
    {
        cout<<"点在圆外"<<endl;
    }
    else
    {
        cout<<"点在圆内"<<endl;
    }
}

int main()
{
    //创建点对象和圆对象
    Point p;
    Circle c;
    Point center;

    //设置点的坐标
    p.setX(0);
    p.setY(0);

    //设置园的半径及圆心的坐标
    c.setR(10);
    center.setX(10);
    center.setY(0);
    c.setCer(center);

    //判断点与圆的关系
    isInCircle(c,p);
    return 0;
}

输出如下:

分文件编写:

point.h

#pragma once//防止头文件重复包含
#include <iostream>
using namespace std;

//点类
class Point
{
private:
    int p_x;
    int p_y;

public:
    //设置/获取X,Y
    void setX(int x);

    int getX();

    void setY(int y);

    int getY();

};

point.cpp

#include "point.h"

//点类

//设置/获取X,Y
void Point::setX(int x)//Point::的作用是声明该函数是Point作用域下的成员函数
{
    p_x=x;
}
int Point::getX()
{
    return p_x;
}

void Point::setY(int y)
{
    p_y=y;
}
int Point::getY()
{
    return p_y;
}

circle.h

#pragma once
#include <iostream>
using namespace std;
#include "point.h"

//圆类
class Circle
{
private:    
    int c_r;//半径
    Point center;//圆心

public:
    //设置/获取半径圆心
    void setR(int r);

    int getR();

    void setCer(Point cer);

    Point getCer();

};

circle.cpp

#include "circle.h"

//圆类


//设置/获取半径圆心
void Circle::setR(int r)
{
    c_r=r;
}
int Circle::getR()
{
    return c_r;
}

void Circle::setCer(Point cer)
{
    center=cer;
}

Point Circle::getCer()
{
    return center;
}

main.cpp

#include <iostream>
using namespace std;
#include "point.h"
#include "circle.h"


//判断点和圆的关系
void isInCircle(Circle &c,Point &p)
{
    int dist=(c.getCer().getX()-p.getX()) * (c.getCer().getX()-p.getX())+
    (c.getCer().getY()-p.getY()) * (c.getCer().getY()-p.getY());
    int rdist=c.getR() * c.getR();

    if(dist==rdist)
    {
        cout<<"点在圆上"<<endl;
    }
    else if(dist>rdist)
    {
        cout<<"点在圆外"<<endl;
    }
    else
    {
        cout<<"点在圆内"<<endl;
    }
}

int main()
{
    //创建点对象和圆对象
    Point p;
    Circle c;
    Point center;

    //设置点的坐标
    p.setX(0);
    p.setY(0);

    //设置园的半径及圆心的坐标
    c.setR(10);
    center.setX(10);
    center.setY(0);
    c.setCer(center);

    //判断点与圆的关系
    isInCircle(c,p);
    return 0;
}

4.2 对象特征

对象的初始化和清理:C++中,每个对象都有初始设置和对象销毁前的清理数据的设置。

4.2.1 构造函数和析构函数

C++中利用构造函数和析构函数对对象进行初始化和清理。这两个函数会被编译器自动调用,完成对象的初始化和清理。如果程序员不提供构造和析构,编译器会提供构造函数和析构函数,但是是空的。

构造函数:创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。

语法:类名(){}

1.构造函数,没有返回值也不写void;

2.函数名与类名相同;

3.构造函数可以有参数,因此可以发生重载;

4.程序在调用对象时会自动调用构造,无须手动调用,且只调用一次。

析构函数:对象销毁前系统自动调用,执行一些清理工作。

语法:~类名(){}

1.析构函数,没有返回值也不写void;

2.函数名与类名相同,在函数名前加上符号~;

3.析构函数不可以有函数,因此不可以发生重载;

4.程序在对象销毁前会自动调用析构,无须手动调用,且只调用一次。

代码如下:

#include <iostream>
using namespace std;

class Person
{
public:
    //构造函数 初始化对象
    Person()
    {
        cout<<"Person构造函数的调用"<<endl;
    }

    //析构函数 销毁/清理对象
    ~Person()
    {
        cout<<"Person析构函数的调用"<<endl;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值