C++

74 阅读21分钟

程序内存模块

内存区四区

  • 代码区:存放二进制代码,由操作系统进行管理

  • 全局区:存放全局变量和静态变量、常量

  • 栈区:由编译器自动分配释放,存放函数的参数值、局部变量等

  • 堆区:由程序员分配和释放,若程序员没有释放,则程序结束时操作系统回收

意义:

不同的区域存放数据,赋予不同的生命周期,给我们更大的灵活编程

1.1 程序运行前

在程序编译后,生成了 exe 可执行文件,未执行该程序前分为两个区域:

代码区:

存放 CPU 执行的机器指令

代码区是共享的(一份代码可被重复执行)、只读的(防止程序意外的修改指令)

全局区:

全局变量、静态变量

常量区:字符串常量、其他常量

数据在程序结束后由操作系统释放

#include <iostream>  
#include <cstdint> // 为了使用 uintptr_t  
using namespace std;  
//g-lobal  c-const  l-local
// 全局变量  
int g_a = 10;  
// 全局常量  
const int c_g_a = 10;  
  
int main() {  
    cout << "全局变量地址 = " << reinterpret_cast<uintptr_t>(&g_a) << endl;  
    // 局部变量  
    int a = 10;  
    cout << "局部变量地址 = " << reinterpret_cast<uintptr_t>(&a) << endl;  
    // 静态变量  
    static int s_a = 10;  
    cout << "静态变量地址 = " << reinterpret_cast<uintptr_t>(&s_a) << endl;  
    // 字符串常量("hello world")  
    const char* str = "hello world";  
    cout << "字符串常量地址 = " << reinterpret_cast<uintptr_t>(str) << endl;  
    // const 修饰的变量  
    // 1、const 修饰全局变量  
    cout << "全局常量地址 = " << reinterpret_cast<uintptr_t>(&c_g_a) << endl;  
    // 2、const 修饰局部变量  
    const int c_l_a = 10;  
    cout << "局部常量地址 = " << reinterpret_cast<uintptr_t>(&c_l_a) << endl;  
    return 0;  
}
总结
非全局区全局区
局部变量、局部常量全局变量、静态变量、字符串常量、全局常量

1.2 程序运行后

栈区:

不要返回局部变量的地址

int* func()
{
    int a = 10;//数据存放于栈区,函数执行完自动释放
    return &a;
}

堆区:

new 开辟内存

int* func()
{
    int *p = new int(10);//保存在堆区,由程序员释放
    return p;
}

1.3new 操作符

int* func()
{
    //new 返回该数据类型的指针
    //int *p = new int(10) == int *p = new int; *p = 10;
    //创建整型变量
    int *p = new int(10);
    return p;
}
void test()
{
    int *p = func();
    //释放堆区数据,整型
    delete p;
    //创建数组
    int *arr = new int[10];
    //释放数组
    delete[] arr;
}

引用

2.1 引用的基本使用

**作用:**给变量起别名

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

int a = 10;
int &b = a;

2.2 引用的注意事项

引用必须初始化

int& b;    //错误

引用一旦初始化就不可以更改

如果已经执行了  int& b = a;   不可以执行&b = c;

2.3 引用做函数参数

**作用:**函数传参时,可以利用引用的技术让形参修饰实参

**优点:**可以简化指针修改实参

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

2.4 引用做函数的返回值

不要返回局部变量的引用

**用法:**函数的调用可以作为左值

int& func()
{
    int a = 10;   //数据在栈区
    return a;
}
int& funb()
{
    static int a = 10;  //数据在全局区
    return a;
}
int main()
{
    int& c = func();
    int& b = funb();
    funb() = 1000;  //funb()返回 a 的引用,即 a = 1000,同时 b 是 a 的引用,即 b = 1000
}

2.5 引用的本质

**本质:**在 c++内部实现是一个指针常量

int& b = a;   //int* const b = &a;
b = 20;  //*b = 20;

2.6 常量引用

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

//引用必须引一块合法的内存空间
int& b = 10;  //错误,10 是常量
const int& b = 10;  //正确,即 int temp = 10; int &b = temp;
//但是不能修改值
b = 20;  //错误
void print(const int& a)
{
    //a = 100;  //错误,const 修饰之后不可改值
    cout<<"a = "<<a<<endl;
}
int main()
{
    int a = 10;
    print(a);
}

函数

3.1 函数默认参数

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

注意:

1.如果从某个位置开始有了默认参数,那么这个位置往后,都必须有默认值

//从 b 开始往后都必须有默认值
void fun(int a, int b = 10, int c = 10, int d = 10)

2.如果函数声明有默认参数,函数实现就不能有默认参数(声明和实现只能一个有默认值)

int fun(int a, int b = 10);
int fun(int a, int b)
{
     a += b;
}

3.2 函数重载

**作用:**函数名相同,提高复用性

条件:

  • 同一个作用域下(全局/局部)

  • 函数名相同

  • 函数参数类型不同或个数不同或顺序不同

函数的返回值不可以作为函数重载的条件

注意:

1、引用作为重载条件

void func(int &a)  //1
void func(const int &a)  //2
int main()
{
    int a;
    func(a);  //1
    func(10);  //2
}

2、函数重载碰到函数默认参数

void func(int a, int b = 10)
void func(int a)
int main()
{
    int a;
    func(a);  //错误,出现二义性,尽量避免
}

类和对象 c++面向对象三大特征:封装、继承、多态

**eg:**人可以作为对象,属性:姓名、年龄、身高...,行为:走、跑、跳...,具有相同性质的对象,我们可以抽象为类,如:人属于人类、车属于车类

4.1 封装

4.1.1 封装的意义

  • 将属性和行为作为一个整体

  • 将属性和行为加以权限控制

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

const double pi = 3.14;
class circle
{
    //类中的属性和行为统一称为成员
    //属性可以称作成员属性、成员变量
    //行为可以称为成员函数、成员方法
    //访问权限
public:
    //圆的属性
    int r;
    //行为(获取圆的周长)
    double cal()
    {
        return 2*pi*r;
    }
};
int main()
{
     //创建圆类对象(实例化)
     circle c1;
     //给属性赋值
     c1.r = 10;
     cout<<"圆的周长:"<<c1.cal();
}
  • 访问权限:

1、public(公共):成员 类内和类外可以访问

2、protected(保护):成员 类内可以访问,类外不可以

3、private(私有):成员 类内可以访问,类外不可以,在继承中,儿子不可以访问父亲的内容

class person
{
private:
      int password;
public:
      string name;
protected:
      string car;
public:
      void func()
      {
            name = "zhang";
            car = "icar";
            password = 123;
      }


};
int main()
{
      person p;
      p.name = "li";
      //保护和私有权限,类外都不可以访问
      p.car = "bcar";   //错误
      p.password = 234; //错误
}

4.1.2struct 和 class 的区别

默认访问权限不同:

struct:默认权限为公共

class:默认权限为私有

class hhh1
{
      int k;
};
struct hhh2
{
      int k;
};
int main()
{
     hhh1 a;
     a.k = 1;     //错误
     struct hhh2 b;
     b.k = 1;
}

4.1.3 成员属性设置为私有

优点:

1、将所有成员属性设置为私有,可以自己控制读写权限

class person
{
public:
      void setname(string n_name)
      {
            name = n_name;
      }
      string getname()
      {
            return name;
      }
private:
      string name;
      int age;
      string hight;
};
int main()
{
     person p;
     p.setname("zhang");
     cout<<p.getname()<<endl;
}

2、对于写权限,可以检测数据的有效性

#include<iostream>
using namespace std;
#include<string>
class person
{
public:
    void setage(int n)
    {
        //设置年龄范围
        if (n < 0 || n>150)
        {
            cout << "赋值失败" << endl;
            return;
        }
        age = n;
    }
    int getage()
    {
        return age;
    }
private:
    string name;
    int age;
    string hight;
};
int main()
{
    person p;
    p.setage(140);
    cout << p.getage() << endl;
}

案例一:设计立方体

1、求出立方体的体积和面积

2、分别用全局函数和成员函数判断两个立方体是否相等

#include<iostream>
using namespace std;
class cube
{
public:
    void set(double cube_l, double cube_w, double cube_h)
    {
        if(cube_l<=0 || cube_w<=0 || cube_h<=0)
        {
            cout<<"赋值失败"<<endl;
            return ;
        }
        l = cube_l;
        w = cube_w;
        h = cube_h;
    }
    double get_l()
    {
        return l;
    }
    double get_w()
    {
        return w;
    }
    double get_h()
    {
        return h;
    }
    double get_s()
    {
        return h*l*2+h*w*2+w*l*2;
    }
    double get_v()
    {
        return h*l*w;
    }
    bool isSame(cube &c)
    {
        if(l == c.get_l() && w == c.get_w() && h == c.get_h())
            return true;   
        else return false;
    }
private:
    double l;   //长
    double w;   //宽
    double h;   //高
};
bool isSame(cube& c1, cube& c2)
{
    if(c1.get_l() == c2.get_l() && c1.get_w() == c2.get_w() && c1.get_h() == c2.get_h())
    return true;
    else return false;
}
int main()
{
    cube cube_1;
    double l, w, h;
    cin>>l>>w>>h;
    cube_1.set(l, w, h);
    cout<<"s = "<<cube_1.get_s()<<endl;
    cout<<"v = "<<cube_1.get_v()<<endl;
    cube cube_2;
    cin>>l>>w>>h;
    cout<<"isSame = "<<isSame(cube_1, cube_2)<<endl;
    cout<<"isSame_cube = "<<cube_1.isSame(cube_2)<<endl;
}

案例二:点和圆的关系

#include<iostream>
#include<math.h>
using namespace std;
class Point
{
public:
    void setPoint(int x, int y)
    {
        point_x = x;
        point_y = y;
    }
    int get_x()
    {
        return point_x;
    }
    int get_y()
    {
        return point_y;
    }
private:
    int point_x, point_y;
};
class circle
{
public:
    void setCenter(int r, Point center)
    {
        circle_r = r;
        circle_center = center;
    }
    int get_r()
    {
        return  circle_r;
    }
    Point get_center()
    {
        return circle_center;
    }
private:
    int circle_r;
    Point circle_center;  //类可以作为成员变量
};
void isIncircle(circle &c, Point &p)
{
    int a, b;
    a = pow(c.get_center().get_x() - p.get_x(), 2);
    b = pow(c.get_center().get_y() - p.get_y(), 2);
    if(sqrt(a+b) == c.get_r())
        cout<<"在圆上 1"<<endl;
    else if(sqrt(a+b) < c.get_r())
        cout<<"在圆内 2"<<endl;
    else cout<<"在圆外 3"<<endl;
}
int main()
{
    circle cir;
    Point cir_center;
    int x, y, r;
    cin>>x>>y>>r;
    cir_center.setPoint(x, y);
    cir.setCenter(r, cir_center);
    Point dot;
    cin>>x>>y;
    dot.setPoint(x, y);
    isIncircle(cir, dot);
}   

分文件编写

Circle.h/头文件

写定义,如:函数定义 void f ( ) ;

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


class Point
{
public:
    void setPoint(int x, int y);
    int get_x();
    int get_y();
private:
    int point_x, point_y;
};
class circle
{
public:
    void setCenter(int r, Point center);
    int get_r();
    Point get_center();
private:
    int circle_r;
    Point circle_center;
};
void isIncircle(circle &c, Point &p);
Circle.cpp/源文件

写实现,如:函数实现 void f ( ) { }

#include "Circle.h"
#include<iostream>
#include<math.h>
//如果不加 Point::该函数为全局函数,但该函数为成员函数,所有需要写 Point::作用域
void Point::setPoint(int x, int y)
{
    point_x = x;    
    point_y = y;
}
int Point::get_x()
{
    return point_x;
}
int Point::get_y()
{
    return point_y;
}
void circle::setCenter(int r, Point center)
{
    circle_r = r;
    circle_center = center;
}
int circle::get_r()
{
    return  circle_r;
}
Point circle::get_center()
{
    return circle_center;
}
void isIncircle(circle &c, Point &p)
{
    int a, b;
    a = pow(c.get_center().get_x() - p.get_x(), 2);
    b = pow(c.get_center().get_y() - p.get_y(), 2);
    if(sqrt(a+b) == c.get_r())
        cout<<"在圆上 1"<<endl;
    else if(sqrt(a+b) < c.get_r())
        cout<<"在圆内 2"<<endl;
    else cout<<"在圆外 3"<<endl;
}
main.cpp
#include<iostream>
#include "Circle.cpp"
#include "Circle.h"
using namespace std;
int main()
{
    circle cir;
    Point cir_center;
    int x, y, r;
    cin>>x>>y>>r;
    cir_center.setPoint(x, y);
    cir.setCenter(r, cir_center);
    Point dot;
    cin>>x>>y;
    dot.setPoint(x, y);
    isIncircle(cir, dot);
}  

4.2 对象的初始化和清理

4.2.1 构造函数和析构函数

两个函数会被编译器自动调用,完成对象初始化和清理工作。如果我们不提供这两个函数,编译器会提供,编译器提供的构造函数和析构函数是空实现

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

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

构造函数:类名(){ }

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;
    }
};
void test()
{
    Person p;  //在栈上,test 执行完之后,释放对象
}
int main()
{
    Person p;  //执行后,只会执行构造而不执行析构,main 结束后执行析构
    test();
    return 0;
}

4.2.2 构造函数的分类及调用

分类:

按参数:有参构造和无参构造

按类型:普通构造和拷贝构造

调用:

括号法、显示法、隐式转换法

class Person
{
public:
    //无参构造(默认构造),普通构造
    Person(){}
    //有参构造,普通构造
    Person(int a)
    {
        age = a;
    }
    //拷贝构造函数
    Person(const Person& p)
    {
        //将 p 对象所有的属性,拷贝到我身上
        age = p.age;
    }
    ~Person(){}
    int a;
};
void test()
{
    Person p1;
    //括号法
    Person p2(10);
    Person p3(p2);
    //注意:如果是调用默认构造,则不应该加()否则编译器会认为是一个函数声明
    Person p1();  //错误
    //显示法
    Person p2 = Person(10);
    Person p3 = Person(Person p2);
    //注意:等号右侧为匿名对象
    Person p(10);  //特点:当前行执行结束后,系统会立即回收匿名对象
    //注意:不要利用拷贝构造函数初始化匿名对象
    Person(p3);  //错误,编译器认为 Person(p3) == Person p3,相当于重定义
    //隐式转换法
    Person p2 = 10;  //相当与 Person p4 = Person(10);
    Person p3 = p2;
}

4.2.3 拷贝构造函数调用时机

  • 使用一个已经创建完毕的对象来初始化一个新对象
void test1()
{
    Person p1(20);
    Person p2(p1);
}
  • 值传递的方式给函数参数传值
void fun(Person k)//相当于 Person k = p;此时会执行拷贝构造函数.但仅仅是值传递,若 k 值改变 p 不会改变
{
    k.age = 10;
}
void test2()
{
    Person p;
    work(p);
}
  • 以值方式返回局部对象
Person work2()
{
    Person p1;
    cout<<(int*)&p1<<endl;
    return p1;
}
void test 3()
{
    Person p = work2();
    cout<<(int*)&p<<endl;
}
// 两个地址打印出来的地址不同,说明不是同一个p

4.2.4构造函数调用规则

默认情况下,c++编译器至少给一个类添加3个函数

1、默认构造函数(无参,函数体为空)

2、默认析构函数(无参,函数体为空)

3、默认拷贝构造函数,对属性进行值拷贝

  • 如果用户定义有参构造函数,c++不在提供默认无参构造,但会提供默认拷贝构造

  • 如果用户定义拷贝构造函数,c++不会再提供其他构造函数

#include <iostream>
using namespace std;
class Person
{
public:
    Person()
    {
        cout<<"默认构造函数"<<endl;
    }
//    Person(const Person &p)
//    {
//        cout<<"拷贝构造函数"<<endl;
//        age = p.age;
//    }
    ~Person()
    {   
        cout<<"析构函数"<<endl;
    }
    int age;
};
int main()
{
    Person p;
    p.age = 18;
    Person p2(p);
    cout<<p2.age<<endl; 
}

4.2.5深拷贝与浅拷贝🌟

浅拷贝:简单的赋值拷贝操作

深拷贝:在堆区重新申请空间,进行拷贝操作

#include <iostream>
using namespace std;
class Person
{
public:
    Person(int age_, int height_)
    {
        cout << "有参构造函数" << endl;
        age = age_;
        height = new int(height_);
    }
    ~Person()
    {
    	//将堆区开辟的数据做释放操作
		if(height != NULL)
		{
			delete height;
			height = NULL;
		} 
        cout << "析构函数" << endl;
    }
    int age;
    int *height;
};
void test()
{
	Person p1(18, 180);
    cout << "p1的年龄: " << p1.age << " p1的身高: " << p1.height << endl;
    cout << "p1的年龄: " << p1.age << " p1的身高: " << *p1.height << endl;
    Person p2(p1);
    cout << "p2的年龄: " << p2.age << " p2的身高: " << p2.height << endl;
    cout << "p2的年龄: " << p2.age << " p2的身高: " << *p2.height << endl;
}
int main()
{
	test();
}

此时编译器会报错,原因: Person p2(p1),如果利用编译器提供的拷贝构造函数,会做浅拷贝操作,浅拷贝只是简单的值拷贝操作,此时height是一个指针,所以p2的height就被赋值为与p1相同的一块内存地址,在函数结束时,p2会优先进行析构函数释放空间,在进行p1的析构函数,带来的问题就是堆区的内存重复释放。此时浅拷贝的问题需要利用深拷贝来解决

image.png

#include <iostream>
using namespace std;
class Person
{
public:
    Person(int age_, int height_)
    {
        cout << "有参构造函数" << endl;
        age = age_;
        height = new int(height_);
    }
    Person(const Person &p)
    {
        cout << "拷贝构造函数" << endl;
        age = p.age;
        // 深拷贝解决浅拷贝的问题
        height = new int(*p.height);
    }
    ~Person()
    {
        // 将堆区开辟的数据做释放操作
        if (height != NULL)
        {
            delete height;
            height = NULL;
        }
        cout << "析构函数" << endl;
    }
    int age;
    int *height;
};
void test()
{
    Person p1(18, 180);
    cout << "p1的年龄: " << p1.age << " p1的身高: " << p1.height << endl;
    cout << "p1的年龄: " << p1.age << " p1的身高: " << *p1.height << endl;
    Person p2(p1);
    cout << "p2的年龄: " << p2.age << " p2的身高: " << p2.height << endl;
    cout << "p2的年龄: " << p2.age << " p2的身高: " << *p2.height << endl;
}
int main()
{
    test();
}

**总结:**如果属性有在堆区开辟的,一定要自己提供拷贝构造函数

4.2.6初始化列表

初始化属性,效率更高

#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;
};
int main()
{
    Person p1;
    cout << p1.a << " " << p1.b << " " << p1.c << endl;
    Person p2(10, 20, 30);
    cout << p2.a << " " << p2.b << " " << p2.c << endl;
}

4.2.7静态成员

1、静态成员变量

  • 所以对象共享同一份数据

  • 编译阶段就开始分配内存

  • 类内声明,类外初始化操作

class Person
{
public:
    static int a;
};
int Person::a = 100;  //必须在类外初始化,可以不用赋值,默认为0
void test()
{
    Person p;
    cout << p.a << endl;
}

两种访问方式:通过对象进行访问和通过类名进行访问

class Person
{
public:
    static int a;
};
int Person::a;
void test()
{
    Person p;
    // 通过对象进行访问
    cout << p.a << endl;
    // 通过类名进行访问
    cout << Person::a << endl;
}

**注意:**静态成员变量也是有访问权限的,私有权限在类外访问不到

class Person
{
private:
    static int b;
};
int Person::b;
void test()
{
    Person p;
    cout << p.b << endl;    
    cout << Person::b << endl;
    //两种方式都是错误的
}

2、静态成员函数

  • 所有对象共享同一个函数

  • 静态成员函数只能访问静态成员变量

class Person
{
public:
    static void fun()
    {
        cout << "fun" << endl;
    }
};
void test()
{
    Person p;
    // 通过对象访问
    p.fun();
    // 通过类名访问
    Person::fun();
}

静态成员函数只能访问静态成员变量

class Person
{
public:
    static void fun()
    {
        a = 100;
        b = 100;  // 错误
        //定义p1、p2、p3……
        //如果在静态函数中调用非静态成员变量,编译器不知道是那个对象的非静态成员变量
        cout << "fun" << endl;
    }
    static int a;
    int b;
};
int Person::a;
void test()
{
    Person p;
    // 通过对象访问
    p.fun();
    // 通过类名访问
    Person::fun();
}

**注意:**静态成员函数也是有访问权限的

class Person
{
private:
    static void func(){}
};
void test()
{
    Person::func();  //报错,类外访问不到
}

4.3 c++对象模型和this指针

4.3.1 成员变量和成员函数分开存储

在c++中,类的成员变量和成员函数分开存储,空对象的内存空间为1

class Person
{
};
void test()
{
    Person p;
    // 空对象占用内存空间为:1
    //c++编译器会给每个空对象分配一个字节空间,为了区别空对象占用内存的位置
    //每个空对象有一个独一无二的内存地址
    cout << sizeof(p) << endl;  // 1
}

只有非静态变量才属于类的对象上

class Person
{
    int a;                 // 非静态成员变量 属于类的对象上
    static int b;          // 静态成员函数 不属于类的对象上
    void func() {}         // 非静态成员变量 不属于类的对象上
    static void func2() {} // 静态成员函数 不属于类的对象上
};
int Person::b = 10;
void test()
{
    Person p;
    cout << sizeof(p) << endl; // 4
}

4.3.2 this指针

  • 每一个非静态成员函数只会诞生一份函数实例,那么多个同类型的对象会共用一块代码,c++通过提供特殊的对象指针解决上述问题,this指针。this指针指向被调用的成员函数所属的对象

  • this指针是隐含在每一个非静态成员函数内的一种指针,不需要定义,直接使用

用途:

  • 当形参和成员变量同名时,可用this指针来区分
class Person
{
public:
    Person(int age)
    {
        this->age = age;
    }
    int age;
};
void test()
{
    Person p1(20);
    cout << p1.age;  // 20
}
  • 在类的非静态成员函数中返回对象本身,可使用return *this
class Person
{
public:
    Person(int age)
    {
        this->age = age;
    }
    Person &sum(Person &p)
    {
        this->age += p.age;
        // this指向p2的指针,则*this指向的就是p2这个对象的本体
        return *this;
    }
    int age;
};
void test()
{
    Person p1(20);
    Person p2(10);
    // 链式编程思想,如:cout << p2.age << endl;
    p2.sum(p1).sum(p1).sum(p2);
    // 相当于返回p2之后,进行p2.sum(p1),再返回p2
    cout << p1.age; // 20
    cout << p2.age; // 100
}

**思考:**Person &sum(Person &p),返回引用,相当于返回p2的本体,如果不加引用:Person sum(Person &p),则返回的是值,此时输出的p2.age为多少? p2.age = 30,因为返回的是值,是本体利用拷贝构造函数创建的新的数据对象,返回的是p2',此时p2.sum(p1)=p2',然后进行p2'.sum(p1),调用结束后返回p2'',每次调用的Person对象都不是同一个,如果用值的方式返回,会创建一个新对象,但是用引用返回,会返回本体

4.3.3空指针访问成员函数

c++中空指针也可以调用成员函数,但需要注意有没有用到this指针

如果用到this指针,需要加以判断保证代码的健壮性

class Person
{
public:
    void showClassName()
    {
        cout << "Person" << endl;
    }
    void showPersonAge()
    {
        // 报错原因:传入指针为空
        cout << age << endl;
    }
    int age;
};
void test()
{
    Person *p1 = NULL;
    p1->showClassName();
    p1->showPersonAge(); // 错误
}

**修改:**加一个判定语句,提高代码健壮性

class Person
{
public:
    void showClassName()
    {
        cout << "Person" << endl;
    }
    void showPersonAge()
    {
        // 提高代码健壮性
        if (this == NULL)
        {
            return;
        }
        cout << age << endl;
    }
    int age;
};
void test()
{
    Person *p1 = NULL;
    p1->showClassName();
    p1->showPersonAge();
}

4.3.4const修饰成员函数

常函数:

  • 成员函数加const称常函数

  • 常函数内不可以修改成员属性

  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改

class Person
{
public:
    // this指针的本质是指针常量,指针常量的特点:指针指向是不可以修改
    // Person *const this;此时指针指向的值可以修改,指针指向不可以修改
    /*在函数后加const,相当于const Person *const this;
      此时指针指向的值和指针指向都不可以修改*/
    //常函数
    void showPerson() const
    {
        // a = 100;    //报错,不允许修改
        // 因为在函数体内部都有一个隐藏的this指针,a = 100 == this->a = 100
        // this = NULL; //报错,因为this指针此时指向p,是不允许修改的
        /* 但是this指针的值是可以修改的,如果不添加const,
        "this->a = 100;"就是正确的*/
        b = 100;
    }
    int a;
    mutable int b;  //特殊变量,在常函数中也可以修改值
};
void test()
{
    Person p;
    p.showPerson(); // 调用之后this指针便指向了p,所以不允许this指向空
}

常对象:

  • 声明对象加const称该对象为常对象

  • 常对象只能调用常函数

class Person
{
public:
    Person()
    {
        cout << "构造函数" << endl;
    }
    void showPerson() const {}
    void fun()
    {
        a = 100; // 正确
    }
    int a;
    mutable int b;
};
void test()
{
    const Person p; // 常对象
    // 特点:不允许修改指针指向的值
    // p.a = 100; // 报错
    p.b = 100; // 正确,在常对象下也可以修改
    // 常对象只能调用常函数
    p.showPerson(); // 正确
    // p.fun();    //错误
    // 常对象不可以调用普通成员函数,因为普通成员函数可以修改属性
}

4.4友元friend

让一个函数或者类访问另一个类中的私有成员,相当于只有你的好朋友才可以进你的房间

4.4.1 全局函数做友元

class Building
{
    //添加友元函数
    friend void test(Building &build);


public:
    void Setting()
    {
        SittingRoom = "客厅";
        BedRoom = "卧室";
    }


public:
    string SittingRoom;


private:
    string BedRoom;
};
void test(Building &build)
{
    cout << build.SittingRoom << endl;
    cout << build.BedRoom << endl;
}
int main()
{
    Building build;
    build.Setting();
    test(build);
    return 0;
}

4.4.2 类做友元

class Building;
class friend_test
{
public:
    friend_test();
    ~friend_test()
    {
        delete building;
    }
    void visit();
    Building *building;
};
class Building
{
    // 声明
    friend class friend_test;


public:
    Building();


public:
    string sittingRoom;


private:
    string bedRoom;
};
// 类外写成员函数
Building::Building()
{
    sittingRoom = "客厅";
    bedRoom = "卧室";
}
//若是在friend_test类的构造函数中分配地址
//此时building类在之后,编译器并不知道要分配多少内存,所以会报错
//而写在building类之后就不会报错
friend_test::friend_test()
{
    building = new Building;
}
void friend_test::visit()
{
    cout << building->sittingRoom << endl;
    cout << building->bedRoom << endl;
}
int main()
{
    friend_test f;
    f.visit();
}

4.4.3 成员函数做友元

class Building;
class friend_test
{
public:
    friend_test();
    ~friend_test();
    void visit();
private:
    Building *building;
};
class Building
{
    friend void friend_test::visit();
public:
    Building()
    {
        settingRoom = "客厅";
        bedRoom = "卧室";
    }
public:
    string settingRoom;
private:
    string bedRoom;
};
friend_test::friend_test()
{
    building = new Building;
}
friend_test::~friend_test()
{
    delete building;
}
void friend_test::visit()
{
    cout << building->settingRoom << endl;
    cout << building->bedRoom << endl;
}
int main()
{
    friend_test f;
    f.visit();
    return 0;
}

4.5 运算符重载

对已有的运算符重新定义,赋予另一种功能,以适应不对数据类型

4.5.1 加号运算符重载

image.png 只能写一个,要么成员函数重载要么全局函数重载

class Person
{
public:
    // 通过成员函数重载+号
    // Person operator+(Person &p)
    // {
    //     Person temp;
    //     temp.a = this->a + p.a;
    //     temp.b = this->b + p.b;
    //     return temp;
    // }
    void print()
    {
        cout << a << endl
             << b << endl;
    }


public:
    int a;
    int b;
};
// 通过全局函数重载
Person operator+(Person &p1, Person &p2)
{
    Person temp;
    temp.a = p1.a + p2.a;
    temp.b = p1.b + p2.b;
    return temp;
}
void test()
{
    Person p1;
    p1.a = 10;
    p1.b = 10;
    Person p2;
    p2.a = 10;
    p2.b = 10;
    Person p3;
    p3 = p1 + p2;
    //成员函数调用的本质:Person p3 = p1.operator+(p2);
    //全局函数调用的本质:Person p3 = operator(p1, p2); 
    p3.print();
}
int main()
{
    test();
    return 0;
}

运算符重载可以函数重载

Person operator+(Person &p1, Person &p2)
Person operator+(Person &p1, int c)
void test()
{
  operator+(p1, 10)
}

4.5.2 左移运算符重载

class Person
{
public:
    int a;
    int b;
};
// 利用全局函数重载左移运算符
//1
// void operator<<(ostream &cout, Person &p)
// {
//     cout << p.a << endl
//          << p.b << endl;
// }
//2
ostream &operator<<(ostream &cout, Person &p)
{
    cout << p.a << endl
         << p.b << endl;
    return cout;
}
int main()
{
    Person p;
    p.a = 10;
    p.b = 10;
    cout << p;
    cout << p << endl;
}

第一个没有返回值,不能在cout << p后面追加endl,第二个返回输出流对象,可以追加endl,链式输出 ostream &cout其中cout只是形参,是一个别名,可以是其他名字,如:

ostream &operator<<(ostream &out, Person &p)
{
    out << p.a << endl
         << p.b << endl;
    return out;
}

但是引用时不能更改名字cout<<p<<endl;

通过友元函数实现重载左移运算符函数输出私有成员

class Person
{
    friend ostream &operator<<(ostream &out, Person &p);


public:
    Person(int c_) : c(c_) {}
    int a;
    int b;


private:
    int c;
};
ostream &operator<<(ostream &out, Person &p)
{
    out << p.a << endl
        << p.b << endl
        << p.c << endl;
    return out;
}
int main()
{
    Person p(20);
    p.a = 10;
    p.b = 10;
    cout << p << "hello" << endl;
}

4.5.3 递增运算符重载

4.6继承