c++学习笔记

1、初识c++

1.1、常量

作用:用于记录程序中不可修改的变量。

2、核心编程

2.1 内存分区模型

c++ 程序执行时,将内存大方向划分为4个区域。

代码区:存放函数体的二进制代码,由操作系统进行管理。

全局区:存放全局变量及静态变量及常量

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

堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。

栈区

不要返回局部变量的地址

数据由编译器管理开辟和释放。

int *fun(int b)  //形参数据也会在栈区
{
	int a = 10; //局部变量 存在栈区 栈区的数据在函数执行完后自动释放。
	return &a;	//返回局部变量的地址。
}

堆区

在c++ 主要利用new 开辟数据。

int * fun()
{
	int *p = new  int(10); // 利用new 关键字把10 开辟在堆上,
	return p;
}

int main()
{
  system("pause");
  return 0; 
}

手动释放:delete

 int * fun()
 {
   int * p = new int (10);
   retturn p;
 }

void test()
{
  int *p = fun();
  cout << *p << endl;
  delete p;  // 释放 内存
}

//在堆上开辟数组
void test01()
{
  int * arr = new int [10]; //10代表数组有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; // 释放的时候 要加上 []
}
2.1.1 程序运行前

c++中程序运行前 分为全局区和代码区。

代码区的特点是共享和只读。

全局区中存放全局变量、静态变量、常量

常量区中存放 const修饰的全局变量 和字符串常量。

2.2 引用
2.2.1 引用的基本用法

作用: 给变量起别名

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

int a = 10;
int &b = a;

cout << a << endl;
cout << b << endl;
2.2.2 引用注意事项
  • 引用必须初始化
  • 引用初始化后,不可以改变
int a = 10;
int &b = a;
int c = 20;  //赋值操作 不是更改引用

cout << a << endl; //20
cout << b << endl; //20
cout << c << endl; //20
2.2.3 引用做函数参数
  • 引用传递 ,形参也会修饰实参。
void swap(int &a , int &b)
{
	int temp = a;
	a = b;
	b = temp;
}

int main()
{
  int a = 10 , b = 20;
  swap(a , b);
  
  cout << a ;//20
  cout << b ;//10
}
2.2.4 引用做函数返回值

作用: 引用是可以作为函数返回值存在的。

  • 不要返回局部变量引用
  • 用法: 函数调用作为左指。
 //发现是引用 转换为 int * const ref = &a;
void fun(int &ref)
{
  ref = 100; //ref 是引用 转换为 *ref = 100
}

int main ()
{
  int a = 10;
  int & ref = a ;
  
  ref = 20 ;
  
}
2.2.5 常量引用
  • 用来修饰形参 , 防止误操作。
const int & ref = 10;

void showValue(const int &val) //修饰形参 , 防止误操作
{
  cout << "val = " << val << endl;
}

2.3 函数高级
2.3.1、 函数提高
//如果我们传入数据,就用自己的数据,如果没有,那么就用默认值。
int fun(int a , int b = 10 , int c = 30)
{
	return a + b + c;
}
// 如果某个位置有了默认参数,那么从这个数字开始。都必须有默认值。 
// 声明有默认参数,函数实现就不能有默认参数。 ---声明和实现只能有一个默认参数。

2.3.2 函数重载
  • 函数名可以相同,提高复用性

重载满足条件:

1、同一个作用域下。

2、函数名相同,参数类型不同或者个数不同。

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

函数重载的注意事项:

1、引用作为重载的条件

void fun(int &a) 
{
    cout << "int &a :" <<endl; 
}

void fun(const int &a) //编译器做了优化 , 创建一个临时数据,然后让a 指向那个临时空间。
{
    cout << "const int &a :" <<endl;
}

int main ()
{
    int a = 10;
    fun(a);  //执行第一个方法。
  	fun(10); //执行第二个方法。
}

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

void fun(int a , int b = 10)
{
    cout << "fun(int a , int b = 10): "<<endl;
}

void fun(int a)
{
    cout << " fun(int a )" <<endl;
}

int main ()
{
    fun(10); //函数重载碰到默认参数,出现二义性,报错,尽量避免这种情况
}
2.4 类和对象

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

1、封装
class Circle
{
//访问权限
public:
    int m_r;
    
    double calcle()
    {
        return PI * m_r * 2;
    }
};

int main ()
{
    Circle c1;
    
    c1.m_r = 10;
    
    cout << "周长 :" << c1.calcle() << endl;
    
}
1.1、访问权限
  • 公共权限:public 类外可以访问
  • 保护权限:protected 类外不可以访问。 儿子可以访问父亲中保护内容
  • 私有权限:private 类外不可以访问。 儿子不可以访问父亲中保护内容

类. 默认权限 是 private

Struct 默认权限是 public

1.2、成员属性设置私有

可以自己控制 读写权限

对于写 可以检测数据的有效性

class Per
{
private:
    string name;
    int age;
    
public:
    void setName(string m_name)
    {
        name = m_name;
    }
    
    void show()
    {
        cout << "name : " << name << endl;
    }
};
2、对象初始化和清理
2.1、构造函数和析构函数
  • 构造函数:创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
  • 析构函数:主要作用于对象销毁前自动调用,执行一些清理工作。

构造函数语法: 类名(){}

​ 1、没有返回值

​ 2、函数名与类名相同

​ 3、构造函数可以有参数,可以发生重载

​ 4、创建对象的时候,构造函数会自动调用,而且只调用一次

析构函数语法:~类名(){}

​ 1、没有返回值

​ 2、对象销毁前调用

class Pr
{
public:
    Pr()
    {
        cout << "构造函数被调用 "<<endl;
    }
    ~Pr()
    {
        cout << "析构函数" << endl;
    }
};
void test()
{
    Pr p;   //在栈上的数据,执行完毕后,会释放对象
}
int main ()
{
    
    Pr p;
    
    system("pause");
    return 0; 
     
}
2.2、构造函数的分类及调用

参数分类 无惨构造(默认构造) 有参构造

类型分类 普通构造 拷贝构造

拷贝构造函数调用时机

c++ 调用拷贝函数通常有三种情况

  • 使用一个已经创建完毕的对象来初始化一个新对象
class Person
{
public:
    Person()
    {
        cout << "默认构造函数 ..."<<endl;
    }
    Person(int a)
    {
        age = a;
        cout << "有参构造函数 ..."<<endl;
    }
    Person(const Person &p)
    {
        age = p.age;
        cout << "拷贝构造函数 ..."<<endl;
    }
    int age;
};
void test()
{
    //
    Person p1(10);
    Person p2(p1);
}
  • 值传递的方式给函数参数传值
void doWork(Person p)
{
    
}
void test()
{
    Person p;
    doWork(p);
}
  • 以值方式返回局部
2.3、构造函数调用规则

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

1、默认构造函数

2、默认析构函数

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

构造函数调用规则:

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

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

2.4、深拷贝与浅拷贝

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

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

浅拷贝带来的问题: 堆区的内容重复释放。

浅拷贝 复制的知识地址 实际上指向的还是同一块地址,c++ 执行模拟栈

class Man
{
public:
    Man()
    {
        cout << "man 默认构造函数 " << endl;
    }
    Man(int age , int height)
    {
        m_age = age;
        m_height = new int(height);
        cout << "Man 有参构造函数" << endl;
    }
    //自己实现 拷贝构造函数,解决浅拷贝带来的问题
    Man( const Man &p)
    {
        cout << " 拷贝构造函数 " << endl;
        m_age = p.m_age;
        // m_height = p.height 编译器默认实现的代码。 浅拷贝操作
        
        //深拷贝
        m_height = new int(*p.m_height);
    }
    ~Man()
    {
        //析构代码,将堆区开辟数据做释放操作。
        if(m_height != NULL)
        {
            delete  m_height;
            m_height = NULL;
        }
        cout << "Man 析构函数" << endl;
    }
    
    int m_age;
    int * m_height;
};
void test()
{
    Man m(18 , 180);
    cout << "m1 的年龄为:"<< m.m_age << " 身高为:" << *m.m_height  <<endl;
    Man m2(m);
    cout << "m1 的年龄为:"<< m.m_age << " 身高为:" << *m.m_height <<endl;
}
2.5、初始化列表

作用: c++提供了初始化列表

class An
{
public:
  	// 初始化列表方式初始化
    An(int a , int b , int c) :m_A(a) , m_B(b) , m_C(c){}
    void Print()
    {
        cout << "a = " << m_A << "  b = " << m_B << " c = " << m_C <<endl;
    }
private:
    int m_A;
    int m_B;
    int m_C;
};
int main ()
{
    An a(1 , 2, 3);
    a.Print();
    
}
2.6、类对象作为类成员

c++ 类中成员可以是另一个类的对象,我们称该成员为对象成员

class A {}

class B
{
  A a;
}
class Phone
{
public:
    Phone(string name){
        m_name = name;
        cout << "phone 构造函数调用" << endl;
    }
    ~Phone()
    {
        cout << "phone 析构函数调用" << endl;
    }
    string m_name;
};
class Pe
{
public:
    Pe(string name , string phone): m_name(name) , m_phone(phone){
        cout << "pe 构造函数调用" << endl;
    }
    ~Pe()
    {
        cout << "pe 析构函数调用" << endl;
    }
    string m_name;
    Phone m_phone;
};
void test01()
{
    Pe pe("张三" ,"apple");
    cout << pe.m_name << "  " << pe.m_phone.m_name<< endl;
}

创建 B 对象时 ,A 与 B 的构造和析构的顺序是:

输出:
phone 构造函数调用
pe 构造函数调用
张三  apple
pe 析构函数调用
phone 析构函数调用
2.7、静态成员

静态成员就是在成员变量和成员函数前加上static, 称为静态成员。

  • 静态成员变量
    • 所有对象共享同一份数据。
    • 在编译阶段分配内存。
class Person1
{
public:
    static int m_a;
    
    //静态成员变量也是有访问权限。private 可进行访问。
};
//初始化
int Person1::m_a = 100;
void test02()
{
    //静态成员变量 不属于某个对象上,所有对象都共享同一份数据
    //1、通过对象访问
    Person1 p1;
    cout << p1.m_a <<endl;
    
    //2、通过类名进行访问
    cout << Person1::m_a << endl;
    
} 
  • 静态成员函数
    • 所有对象共享同一个函数
    • 静态成员函数只能访问静态成员变量
class Person1
{
public:
    static void func()
    {
        //m_b = 200  静态成员函数 不可以访问 非静态成员变量 ,无法区分到底是哪个对象的
        cout << "static void fun 调用" << endl;
    }
  	int m_b;
    static int m_a;
};

void test02()
{
    //1、
    Person1 p1;
    p1.func();
    
    //2、
    Person1::func();
} 
3、 C++对象模型和this 指针

this 指向被调用的成员函数所属的对象。

this 用途:

  • 当形参和成员变量同名时,可用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可用 return *this
class Person1
{
public:
    Person1(int age)
    {
        this->age = age;
    }
    
    Person1& addPersonAge(Person1 p)
    {
        this -> age += p.age;
      	//返回对象本身 
        return *this;
    }
    
    int age ;
};
void test01()
{
    //1、解决名称冲突
    Person1 p1(18);
    cout << "p1 年龄 为" << p1.age << endl;
}
void test02()
{
    //返回对象本身用 *this
    Person1 p1(10);
    Person1 p2(10);
    
    p2.addPersonAge(10).addPersonAge(10).addPersonAge(10);
    cout << p2.age << endl;
} 
3.1、空指针调用成员函数
class Person
{
public:
    void showClassName()
    {
        cout << "this is person clas " <<endl;
    }
    void showPersonAge()
    {
        //指针为空,因为传入的指针为空
        if(this == NULL)
        {
            return;
        }
        cout << "age = " << m_age << endl;
    }
    int m_age;
};

void test01()
{
    Person *p = NULL;
    
    p->showClassName();
    
    p->showPersonAge();
}
3.2、const 修饰成员函数

常函数

  • 成员函数后加 const 后我们称为这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable , 在常函数中依旧可以修改

常对象:

  • 声明对象前加const 称该对象为常对象
  • 常对象只能调用常函数

常对象里面的成员属性需要初始化。

class Person
{
public:
    // this指针的本质 是指针常量 指针的指向是不可以修改的
    // const Person * const this;
    // 在成员函数后面加const ,修饰的是this 指向,让指针指向的值也不可以修改。
    void showPerson() const
    {
        this -> m_b = 100;
        //this -> m_a = 100;
        //this = NULL; // this 指针不可以修改指针的指向
        cout << this->m_b  << endl;
    }
    
    void func()
    {
        
    }
    
    int m_a = 10;
    mutable int m_b = 10; //特殊变量 即使在常函数中,也可以修改这个值 加关键字 mutable
};

void test01()
{
    Person p;
    p.showPerson();
}

void test02()
{
    const Person p;  //在对象前加const 变为常对象
    p.m_b = 1200;
    cout << p.m_b << endl;
    
    //常对象只能调用常函数
    p.showPerson();
    //p.func()  报错 不可以调用普通函数
}
4、友元

有些私有属性,也想让类外特殊的一些函数或者类进行访问,就需要用到友元技术。

友元的目的是让一个函数或者类 访问另一个类的私有成员

关键字为 : feiend

友元的三种实现:

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元
4.1、全局函数做友元
class Building
{
    //友元 可访问类里私有元素 全局函数做友元 
    friend void goodGay( Building *building);
public:
    Building()
    {
        m_SittingRoom = "客厅";
        m_BedRoom = "卧室";
    }
    
public:
    string m_SittingRoom;
    
private:
    string m_BedRoom;
};

void goodGay( Building *building)
{
    cout << building->m_SittingRoom << endl;
    cout << building->m_BedRoom << endl;
}
void test03()
{
    Building build;
    goodGay(&build);
}
4.2、类做友元
class Building;
class GoodGay
{
public:
    GoodGay();
    void visit();
    
private:
    Building *building;
};

class Building
{
    //告诉编译器 goodGay类是Building类的好朋友,可以访问到Building类的私有内容
    friend class GoodGay;
    
public:
    Building();
    
public:
    string m_SittingRoom;
private:
    string m_BedRoom;
};

Building::Building()
{
    m_SittingRoom = "客厅";
    m_BedRoom = "卧室";
}

GoodGay::GoodGay()
{
    building = new Building;
}

void GoodGay::visit()
{
    cout << "好基友正在访问:" << building->m_SittingRoom <<endl;
    
    cout << "好基友正在访问:" << building->m_BedRoom <<endl;
}

void test03()
{
    GoodGay gg;
    gg.visit();
}
4.3、成员函数做友元
class Building;
class GoodGay
{
public:
    GoodGay();
    void visit();
    
private:
    Building * building;
    
};

class Building
{
    friend void  GoodGay::visit();
public:
    Building();
    
public:
    string m_sittingRoom ;
private:
    string m_bedRoom;
};

Building::Building()
{
    this->m_sittingRoom = "客厅";
    this->m_bedRoom = "卧室";
}

GoodGay::GoodGay()
{
    building = new Building;
}

void GoodGay::visit()
{
    cout << building->m_sittingRoom << endl;
    cout << building->m_bedRoom << endl;
}

void test03()
{
    GoodGay gg;
    gg.visit();
}
5、运算符重载

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

5.1、加号运算符重载

作用:实现两个自定义数据类型相加的运算。

class Method
{
public:
    //成员函数重载运算符
//    Method operator+ (Method &p)
//    {
//        Method 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;
};

Method operator+(Method &m1 , Method &m2)
{
    Method temp ;
    temp.m_a = m1.m_a + m2.m_a;
    temp.m_b = m2.m_b + m2.m_b;
    return temp;
}

void test04()
{
    Method m1;
    m1.m_b = 10;
    m1.m_a = 10;
    Method m2;
    m2.m_b = 10;
    m2.m_a = 10;
    //成员函数重载调用
    //Person p3 = p1.operate+(p2);  本质上
    Method m3 = m1 + m2;
    cout << " a =  " << m3.m_a << endl << " b =  " << m3.m_b << endl;
}
5.2、左移运算符
6、继承

继承是面向三大对象的特性

6.1、基本语法
//语法 class 子类 : 继承方式 父类

class Animal
{
public:
    void eat()
    {
        cout << " 我 会吃" <<endl;
    }
};

class Dog:public Animal
{
public:
    void go()
    {
        cout << "狗会跑" <<endl;
    }
};
6.2、继承方式

主要继承方式

  • 公共继承
  • 保护继承
  • 私有继承
class Base
{
public:
    int m_a;
protected:
    int m_b;
    //父类的私有元素 是不会被继承的元素所访问
private:
    int m_c;
};

//public 方式
class Son1 : public Base
{
public:
    void func()
    {
        cout << m_a <<endl;  // 父类公共成员,子类还是公共权限
        cout << m_b << endl; // 父类保护成员,到子类中变为保护权限
    }
};

class Son2 : protected Base
{
public:
    void func()
    {
        cout << m_a <<endl;  // 父类公共成员,到子类中变为保护权限
        cout << m_b << endl; // 父类保护成员,到子类中变为保护权限
    }
};

class Son3 : private Base
{
public:
    void func()
    {
        cout << m_a <<endl;  // 父类公共成员,到子类中变为私有权限
        cout << m_b << endl; // 父类保护成员,到子类中变为私有权限
    }
};
6.3、 继承中对象模型
class Base
{
public:
    int m_a;
protected:
    int m_b;
    //父类的私有元素 是不会被继承的元素所访问
private:
    int m_c;
};
class Son4:public Base
{
public:
    int m_d;
};

void test05()
{
    //16
    //父类所有非静态成员属性都会被子类继承下去
    // 父类私有成员属性 是被编译器隐藏了 因此是访问不到 但是确实被继承了
    Son4 s1;
    cout << sizeof(s1) << endl;
}
6.4 、继承中构造和析构顺序

继承中构造和析构顺序如下:

先构造父类,再构造子类,析构到顺序与构造的顺序相反。

class Base1
{
public:
    Base1()
    {
        cout << "base 构造函数" << endl;
    }
    ~Base1()
    {
        cout << "base 析构函数" << endl;
    }
};

class Son5 : public Base1
{
public:
    Son5()
    {
        cout << "son 构造函数" << endl;
    }
    ~Son5()
    {
        cout << "son 析构函数" << endl;
    }
};

void test1()
{
    Son5 s;
}
//base 构造函数
//son 构造函数
//son 析构函数
//base 析构函数
6.5、继承同名成员处理方式
  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域
class Base2
{
public:
    Base2()
    {
        m_A = 100;
    }
    void func()
    {
        cout << "base " << endl;
    }
    int m_A = 100;
};

class Son6:public Base2
{
public:
    Son6()
    {
        m_A = 200;
    }
    void func()
    {
        cout << " son " << endl;
    }
    int m_A;
};
//同名属性处理方式
void test2()
{
    Son6 s;
    cout << "son  m_a = " << s.m_A << endl;
    //如果通过 子类对象 访问到父类中同名成员 , 需要加作用域
    cout << "base m_a = " << s.Base2::m_A << endl;
}
void test3()
{
    Son6 s;
    s.func();
    s.Base2::func();
}

总结

子类对象可以直接访问到子类中同名成员

子类对象加作用域可以访问到父类同名成员

当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类同名函数

6.6、继承同名静态成员处理方式

静态成员和非静态成员出现同名,处理方式一致

  • 访问子类同名成员 直接访问
  • 访问父类同名成员 需要加作用域
6.7、多继承语法

c++允许一个类继承多个类

语法:class 子类 :继承方式 父类1 , 继承方式 父类2

多继承可能引发父类中有同名成员出现,需要加作用域区分

c++ 实际开发中不建议用多继承

6.8、 菱形继承

菱形继承概念:

​ 两个派生类继承同一个基类

​ 又有某个类同时继承这两个派生类

​ 这种称为 菱形继承 或者钻石继承

class Animal1
{
public:
    int m_Age;
};

//利用虚继承 解决菱形继承的问题
//继承之前 加上关键字 virtual 变为虚继承
class Sheep:virtual public Animal1{};

class Tuo:virtual public Animal1{};

class SheepTuo:public Sheep , public Tuo
{
    
};

void test4()
{
    SheepTuo st;
    st.Sheep::m_Age = 18;
    st.Tuo::m_Age = 28;
    //当菱形继承,两个父类拥有相同数据,需要加以作用域区分
    cout << st.Sheep::m_Age << endl;
    cout << st.Tuo::m_Age << endl;
}
7、多态

多态分为两类

  • 静态多态:函数重载 和 运算符重载属于静态多态 ,
  • 动态多态:派生类和虚函数实现运行时多态

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
class Animal2
{
public:
    //c11  c++动态多态仅支持直接父类、
    //父类作为一个节点 使得各个不同的子类都能传入同一个函数题中并且调用子类对应的重载函数
    virtual void speak()
    {
        cout << "动物在说话" << endl;
    }
};

class Cat : public Animal2
{
public:
    // 重写 函数返回值类型
    void speak()
    {
        cout << "小猫在说话" << endl;
    }
};

//动态多态满足条件
//1、有继承关系
//2、子类重写父类的虚函数

//多态使用条件
//父类引用指向子类对象
void speak(Animal2 &animal)
{
    animal.speak();
}

void test5()
{
    Cat c;
    speak(c);
}
7.1、多态案例 - 计算器
class Calculator
{
public:
    
    virtual int getResult()
    {
        return 0;
    }
    
    int m_Num1;
    int m_NUm2;
};

class AddCalculator : public Calculator
{
public:
    int getResult()
    {
        return m_Num1 + m_NUm2;
    }
};

class MulCalculator : public Calculator
{
public:
    int getResult()
    {
        return m_Num1 - m_NUm2;
    }
};

void test6()
{
    Calculator * cal = new AddCalculator;
    cal->m_NUm2 = 100;
    cal->m_Num1 = 100;
    
    cout << cal->m_Num1 << " + " << cal->m_NUm2 << " = " << cal->getResult() <<endl;
    delete cal;
    
    cal = new MulCalculator;
    cal->m_NUm2 = 100;
    cal->m_Num1 = 100;
    
    cout << cal->m_Num1 << " - " << cal->m_NUm2 << " = " << cal->getResult() <<endl;
    delete cal;
}
7.2、纯虚函数和抽象类

纯虚函数语法 : virtual 返回值类型 函数名 (参数列表)= 0;

当类中有了纯虚函数 这个类称为抽象类

抽象类特点

  • 无法实例化对象
  • 子类必须重写抽象类的纯虚函数,否则也属于抽象类
class Bas
{
public:	
		//纯虚函数
  	// 类中只要有一个纯虚函数 就称为 抽象类
  	//抽象类无法实例化对象
  	//子类必须重写父类的纯虚函数 否则也属于抽象类
    virtual void fun() = 0;  //即便是void 也只能是 = 0
};

class Son7:public Bas
{
public:
  void fun()
    {
      cout << " son7 ----" <<endl;
  }
};

void test7()
{
    Bas * b = new Son7;
    
    b->fun();
}
7.3、虚析构和纯虚析构

多态使用时,如果子类有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构函数

解决方式: 将父类的析构函数改为 虚析构 或者 纯虚析构

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要具体的函数实现

虚析构和纯虚析构区别:

  • 如果是纯虚析构 , 该类属于抽象类 无法实例化对象

虚析构语法:
virtual ~类名(){}

纯虚析构语法:

virtaul ~类名() = 0;

该函数需要实现,可以用类::方法的方式来声明

class Big
{
public:
    Big()
    {
        cout << "big 构造函数" <<endl;
    }
     virtual void speak()
    {
        cout << " big speak " << endl;
    }
    virtual ~Big()
    {
        cout << " big 析构函数" << endl;
    }
};

class Small :public Big
{
public:
    Small(string name)
    {
        cout << "small 构造函数" << endl;
        m_name = new string(name);
    }
    
    void speak()
    {
        cout << "small speak" <<endl;
    }
    
    ~Small()
    {
        cout << " Cat 析构函数 调用" << endl;
        if(this -> m_name != NULL){
            delete m_name;
            m_name = NULL;
        }
    }
    
    string *m_name;
};

void test8()
{
    Big *b = new Small("tom");
    b->speak();
    
    //通过父类指针去释放,会导致子类对象可能处理不干净,造成内存泄露
    //给基类添加一个虚析构函数
    //虚析构函数就是用来解决父类指针 释放子类对象
    
    delete b;
}

总结:

​ 1、虚析构或纯虚析构就是用来解决通过父类指针释放子类对象

​ 2、如果子类中没有堆区数据,可以不写为虚析构或者纯虚析构

​ 3、拥有纯虚析构函数的类也属于抽象类

3、文件操作

通过文件可以将数据持久化

c++中对文件操作需要包含 头文件

文件类型分为两种:

1、文本文件 - 文件以文本的ASCII码 形式存储在计算机中

2、二进制文件 - 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂他们

操作文件的三大类

1、ofstream : 写操作

2、ifstream :读操作

3、fstream : 读写操作

5.1、文本文件
5.1.1、写文件

​ 写文件步骤如下:

​ 1、包含头文件

​ #include

​ 2、创建流对象

​ of stream ofs;

​ 3、打开文件

​ ofs.open(“文件路径” , 打开方式);

​ 4、写数据

​ ofs << “学入的数据”;

​ 5、关闭文件

​ ofs.close() ;

文件打开方式:

ios::in 为读文件而打开文件

ios::out 为写文件而打开文件

ios::binary 二进制

5.1.2、读文件

4、 C++ 提高编程

4.1模版
1.1 函数模版
  • 泛型编程。主要利用技术就是模板
  • c++提供两种模版机制 : 函数模版 和 类模版

语法:

template <typename >
函数声明 或 定义

解释:

Template – 声明创建模版

1.2 函数模版
1、函数模版语法
//函数模版
template <typename  T>  // typename 可以替换成class 声明一个模版。告诉编译器后面的T 不要报错,T时一个通用的数据类型
void mySwap(T &a , T &b)
{
    T temp = a ;
    a = b;
    b = temp;
}

int main()
{
    int a = 10 , b = 20;
    double a1 = 1.1 , b1 = 2.2;
    
    //自动类型推导
    mySwap(a, b);
    
    //显示指定类型
    mySwap(a1, b1);
    
    cout << "a1 =  " << a1 << "  b1 =  " << b1 << endl;
    
    cout << "a = " << a << "  b = " << b << endl;
    
}
2、注意事项

​ 使用模版时必须确定出通用数据类型T, 并且能够推一致的类型

3、函数模版案例
//函数模版
template <typename  T>  // 声明一个模版。告诉编译器后面的T 不要报错,T时一个通用的数据类型
void mySwap(T &a , T &b)
{
    T temp = a ;
    a = b;
    b = temp;
}

//实现通用 对数组 进行排序的函数
// 从大到大

//排序算法
template <class T>
void mySort(T arr[] , int len)
{
    for(int i = 0 ; i < len - 1; i++ ){
        int min = i;
        for(int j = i + 1 ; j < len ; j++){
            if( arr[min] > arr[j]){
                min = j;
            }
        }
        if(min != i){
            mySwap(arr[i], arr[min]);
        }
    }
}
4、普通函数和函数模版区别:

​ 1、普通函数调用 可以发生隐式类型转换

​ 2、函数模版 用自动类型推导,不可以发生隐式类型转换

​ 3、函数模版 用显示指定类型,可以发生隐式类型转换

5、普通函数 和 函数模版的调用规则

​ 1、如果函数模版和普通函数都可以实现,优先调用普通函数

​ 2、可以通过空模版参数列表来强调函数模版

void myPrint(int a , int b)
{
    cout << "普通函数" << endl;
}

template <class T>
void myPrint(T a , T b)
{
    cout << "调用的模版" << endl;
}

int main()
{
    int a = 10, b = 20;
    
    //myPrint(a, b);
    
    //强制调用函数模版
    myPrint<>(a, b);
}

​ 3、函数模版可以发生重载

​ 4、如果函数模版可以产生更好的匹配,优先调用函数模版

6、局限性

模版不是万能的,有些特定数据类型,需要用具体化方式做特殊处理

class Person
{
public:
    Person(string name , int age){
        this->m_Age = age ;
        this->m_Name = name;
    }
    string m_Name;
    int m_Age;
};

template <class T>
bool myComp(T &a , T &b){
    return a == b;
}

template<> bool myComp(Person &p1 , Person &p2)
{
    bool flag;
    if(p1.m_Age == p2.m_Age && p1.m_Name == p2.m_Name)
    {
        return true;
    }else{
        return false;
    }
}

void test()
{
    Person p("a" , 10);
    Person p1("a" , 11);
    
    cout << myComp(p, p1) << endl;
}

1.3 类模版
1、类模版语法:
//类模版
template <class nameType , class ageType>
class MyPerson
{
public:
    MyPerson(nameType name , ageType age)
    {
        this->name = name;
        this->age = age;
    }
    
    void show()
    {
        cout << this->name << "  " << this->age << endl;
    }
    
    nameType name;
    ageType age;
};

void test1()
{
    MyPerson m1("张三" , 189);
    m1.show(); 
}

2、类模版对象做函数参数

一共三种传入方式:

1、指定传入类型 - 直接显示对象的数据类型

2、参数模版化 - 将对象中的参数变为模版进行传递

3、整个类模版化 - 讲这个对象类型模版化进行传递

//1、指定传入类型
void printPerson( MyPerson<string, int> &m)
{
    m.show();
}

//2、参数模版化
template<class T1 , class T2>
void printPerson2(MyPerson<T1, T2> &p)
{
    p.show();
}
//3、整个类模版化
template<class T>
void printPerson3(T &t)
{
    t.show();
}

void test1()
{
    MyPerson<string, int> m("aa" , 10);
    printPerson3(m);
}
3、类模版与继承
template <class T>
class Base
{
    T m;
};

//class Son : public Base 错误, 必须知道Base 数据类型
class Son : public Base<int >
{
    
};

//如果想灵活指定父类的T类型, 子类也需要变类模版
template<class T1 , class T2>
class Son1 : public Base<T1>
{
    T2 obj;
};
4、类模版成员函数类外实现

5、STL

1、初识stl
1.1、基本概念
  • STL。: 标准模版库
  • STL. 从广义上分 : 容器 算法 迭代器
  • 容器和算法可以通过迭代器进行无缝连接
  • s t l 几乎所有代码都采用了模版类或者模版函数
1.2、stl 六大组件

容器、算法、迭代器、仿函数 、 适配器、空间适配器

1、容器: 哥中数据结构。如vector 、 list. 、de que 、set 、 map 等用来存放数据

2、算法: 各种常用算法,如sort 。

3、迭代器: 扮演了容器和算法之间的胶合剂

4、仿函数:行为类似函数

5、适配器 :一种用来修饰容器或者仿函数或迭代器接口的东西

6、空间配置器;负责空间的配置与管理

1.3、 简单遍历
void test()
{
    //创建yige vector 容器 ,数组
    vector<int> v;
    v.push_back(10);
    v.push_back(20);
    v.push_back(30);
    
    for(vector<int>::iterator it = v.begin() ; it != v.end() ; it++)
    {
        cout << *it << " ";
        
    }
    cout << endl;
}

void test1()
{
    vector<vector<int>> v;
    
    vector<int> v1;
    vector<int> v2;
    vector<int> v3;
    vector<int> v4;
    
    for(int i = 0 ; i < 4 ; i ++)
    {
        v1.push_back(i + 1);
        v2.push_back(i + 2);
        v3.push_back(i + 2);
        v4.push_back(i + 2);
    }
    
    v.push_back(v1);
    v.push_back(v2);
    v.push_back(v3);
    v.push_back(v4);
    
    for(vector<vector<int>>::iterator it = v.begin() ; it != v.end() ; it++)
    {
        for(vector<int>::iterator vit = (*it).begin() ; vit != (*it).end() ; vit++)
        {
            cout << *vit << " ";
        }
        cout << endl;
    }
    
}
2、容器
1、string 容器
2.1、string 基本概念
  • string 是 C++ 风格的字符串 而 string 本质上是一个类

string 和 char* 区别:

  • Char* 是一个指针
  • string 是一个类 类内部封装了char* 管理这个类 , 是一个char * 型 的容器

特点: string 类封装 了很多成员方法

比如 : find copy delete replace insert

string 管理cha r * 所分配的内存 不用担心复制和越界等,由类内部 进行负责

2.2、string 构造函数
  • string() //创建一个空字符串

    String(const char *s);

  • string(const string & str) ; 使用string 对象初始化另一个string 对象

  • string(int n , char c) 使用n 个字符串c 初始化

void test2()
{
    string s1;
    
    const char * str = "hello";
    string s2(str);
    
    string s3(s2);
    
    string s4(10 , 'a');
}
2.3、string赋值操作
//1 、operator
//2、 assign

void test2()
{
    //string & operator = const char * str;
    string str1 = "hello ";
    
    string str2 = str1;
    cout << "str2 = " << str2 << endl;
    
    string str3;
    str3.assign("hello" , 5);
    cout << "str3 = " << str3 << endl;
}
2.4、string 拼接
  • +=
  • append
2.5、查找和替换
  • 查找 ; 查找字符串是否存在 find
  • 替换 : 在指定位置替换字符串 replace
void test4()
{
    string str = "abcdefgh";
    //find
    int pos = str.find("dq");
    
    cout << str.find("dq") << " " << pos <<  endl; //输出出现的位置
    //rfind  和 find 区别
    // rfind 从右往左。find  从左往右
    
    
    //替换
    //从1 号位置起 三个字符  替换成“1111”
    string str1 = str.replace( 1 , 3, "1111");
    cout << str1 << endl;
    
}

2.6、字符串比较
void test5()
{
    string str1 = "hello";
    string str2 = "hell";
    
    if(str1.compare(str2) == 0)
    {
        cout << "相等" <<endl;
    }else if(str1.compare(str2) > 0)
    {
        cout << "str1 大于 str2" << endl;
    }else{
        cout << "str1 小于 str2" << endl;
    }
}
2.7、插入和删除
void test6()
{
    string str = "hello";
    
    str.insert(1, "aaa");
    
    cout << str << endl;
    
    str.erase( 1 , 3);
    
    cout << str << endl;
}	
2、vector 容器

单端数组 , 可动态扩展

动态扩展:

  • 寻找更大的内存空间,将原数据拷贝,把之前的内存释放。
2.1、构造函数。
void printVector(vector<int> &v)
{
    for(vector<int>::iterator it = v.begin() ; it != v.end() ; it ++)
    {
        cout << *it << " ";
    }
    cout << endl;
}

void test7()
{
    vector<int > v1;
    
    for(int i = 0 ; i < 10 ; i++)
    {
        v1.push_back(i);
    }
    printVector(v1);
}
3、deque容器
  • 双端数组,可以对头端进行插入删除操作。

deque 和 vector 区别:

  • Vector 对于头部的插入删除效率低,数据量越大,效率越低
  • de que相对而言,对头部的插入删除速度比vector快
  • Vector 访问元素的速度会比deque快,这和两者内部实现有关。
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值