C++基础学习

构造函数/拷贝构造函数/析构函数

构造函数

构造函数的概念

注意点:
  1. 在类对象进入其作用域时调用构造函数。

  2. 构造函数没有返回值,因此也不需要在定义构造函数时声明类型。

  3. 构造函数不需用户调用,也不能被用户调用。一般不提倡在构造函数体中加入与初始化无关的内容,以保持程序的清晰。

  4. 如果用户自己没有定义构造函数,则C++系统会自动生成一个构造函数,只是这个构造函数的函数体是空的,也没有参数,不执行初始化操作

  5. 没有给出形参的构造函数,称为默认构造函数。一个类只能有一个默认构造函数。

  6. 尽管在一个类中可以包含多个构造函数,但是对于每一个对象来说,建立对象时只执行其中一个构造函数,并非每个构造函数都被执行。

  7. 应该在声明构造函数时指定默认值,而不能只在定义构造函数时指定默认值。

  8. 在声明构造函数时,形参名可以省略。

  9. 如果构造函数的全部参数都指定了默认值,则在定义对象时可以给一个或几个实参,也可以不给出实参。

  10. 在一个类中定义了全部是默认参数的构造函数后,不能再定义重载构造函数。

##### 在C++中,如果你在一个类中定义了一个构造函数,它所有的参数都有默认值,这意味着你可以通过不提供任何实参来调用这个构造函数。这种情况下,如果再定义一个与之重载的构造函数,可能会导致编译器无法确定应该调用哪个构造函数,因为即使没有提供实参,编译器也可以认为是在调用那个带有默认参数的构造函数。因此,这样做通常会导致编译错误或产生未预期的行为。

```c++
class MyClass {
public:
    // 这是一个全有默认参数的构造函数
    MyClass(int a = 1, int b = 2, int c = 3) {
        // ...
    }

    // 这是一个试图重载的构造函数
    // 但是这会导致编译错误,因为上面的构造函数可以接受0个、1个、2个或3个参数
    // 由于默认参数的存在,这个重载是不必要的,也是不允许的
    MyClass(int a, int b) {
        // ...
    }
};
```

//1.创建对象的时候,构造函数会自动调用,而且只调用一次
//2.析构函数: 析构函数没有参数,析构函数可以手工调用

#include <iostream>
using namespace std;
class Person
{
public:
    Person()
    {
        cout << "Person构造函数的调用!" << endl;
    }
    ~Person()
    {
        cout << "Person析构函数的调用!" << endl;
    }
};

void test01()
{
    Person p;
}
int main()
{
    test01();
    return 0;
}

构造函数的分类及调用

//1.构造函数的分类及调用

// --无参构造(默认构造)和有参构造

// --按照类型: 普通构造函数和拷贝构造函数

// 构造函数.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
//1.构造函数的分类及调用
//  --无参构造(默认构造)和有参构造
//  --按照类型: 普通构造函数和拷贝构造函数
//  

#include <iostream>
using namespace std;
class Person
{
public:
    Person()
    {
        cout << "Person无参构造函数的调用!" << endl;
    }
    Person(int a)
    {
        age = a;
        cout << "Person有参构造函数的调用!" << endl;
    }

    //拷贝构造函数
    Person(const Person &p)
    {

        age = p.age;
        cout << "Person拷贝构造函数的调用!" << endl;
    }
    ~Person()
    {
        cout << "Person析构函数的调用!" << endl;
    }

private:
    int age;
};
//调用
void test01()
{
    //1.括号法
    //Person p1;//默认构造函数的调用方法
    //Person p2(10);
    //Person p3(p2);
    //注意事项
    // --调用默认构造函数的时候,不要加()
    // --不要利用拷贝构造函数初始化匿名对象 编译器会认为 Person(p3)==Person p3

    //2.显示法
    //Person p1;
    //Person p2 = Person(10);//有参构造
    //Person p3 = p2;

    //Person(10);//匿名对象::  特点:当前执行结束后,系统立即回收掉匿名对象
    //cout << "aaaa" << endl;
    /*运行结果
    * Person有参构造函数的调用!
      Person析构函数的调用!
      aaaa
    */

    //3.隐式转换法
    Person p4 = 10;
    Person p5 = p4;
}
int main()
{
    test01();
    return 0;
}

自动调用拷贝构造函数的情况:

//自动调用拷贝构造函数的情况:
//1.用类的一个对象去初始化另一个对象
//2.值传递的方式给函数参数传值
//3.函数的返回值是类的对象

#include <iostream>
using namespace std;
class Person
{
public:
    Person()
    {
        cout << "Person无参构造函数的调用!" << endl;
    }
    Person(int a)
    {
        age = a;
        cout << "Person有参构造函数的调用!" << endl;
    }

    //拷贝构造函数
    Person(const Person& p)
    {

        age = p.age;
        cout << "Person拷贝构造函数的调用!" << endl;
    }
    ~Person()
    {
        cout << "Person析构函数的调用!" << endl;
    }


    int age;
};
//调用
void test01()
{
    //1.用类的一个对象去初始化另一个对象
    Person p1(20);
    Person p2(p1);
    cout << "p2的年龄为:" << p2.age << endl;

}

//2.值传递的方式给函数参数传值
void doWork(Person p)
{

}
void test02()
{
    
    Person p1;
    doWork(p1);
}

//3.函数的返回值是类的对象
Person func()
{
    Person p1;
    return p1;
}
void test03()
{
    Person p3 = func();
}
int main()
{
    //test01();
    //test02();
    test03();
    
    return 0;
}


构造函数的调用规则

//一般情况下,c++会默认创建默认的构造函数、析构函数、拷贝构造函数
//
//构造函数的调用规则:
//如果用户自定义了了 有参构造函数 那么c++不提供默认构造函数
//如果用户自定义了 拷贝构造函数 那么c++不提供其他构造函数
#include <iostream>
using namespace std;
class Person
{
public:
    Person()
    {
        cout << "Person无参构造函数的调用!" << endl;
    }
    Person(int a)
    {
        age = a;
        cout << "Person有参构造函数的调用!" << endl;
    }
    //拷贝构造函数
    Person(const Person& p)
    {

        age = p.age;
        cout << "Person拷贝构造函数的调用!" << endl;
    }
    ~Person()
    {
        cout << "Person析构函数的调用!" << endl;
    }
    int age;
};

void test01()
{
    Person p;
    p.age = 18;
    Person p2(p);
    cout<<"p2的年龄为:"<<p2.age<<endl;

}

//如果用户自定义了了 有参构造函数 那么c++不提供默认构造函数
void test02()
{
    Person p;
}

int main()
{
    test01();
    
    return 0;
}

深拷贝与浅拷贝问题

//深拷贝与浅拷贝问题
//浅拷贝:简单的赋值拷贝操作  带来的问题:堆区的内存重复释放
//深拷贝:在堆区重新申请内存,进行拷贝操作  ——>解决堆区重复释放问题
#include <iostream>
using namespace std;
class Person
{
public:
    Person()
    {
        cout << "Person默认构造函数的调用!" << endl;
    }
    Person(int a,int Hight)
    {
        age = a;
        height = new int(Hight); 
        cout << "Person有参构造函数的调用!" << endl;
    }
    //拷贝构造函数
    Person(const Person& p)
    {

        age = p.age;
        //height = p.height;编译器默认实现是这行代码

        height = new int(*p.height);//深拷贝
        cout << "Person拷贝构造函数的调用!" << endl;
    }
    ~Person()
    {
        //析构函数:将堆区开辟的内存释放
        if(height!=NULL)
        {
            delete height;
            height = NULL;
        }
        cout << "Person析构函数的调用!" << endl;
    }
    int age;
    int *height;
};

void test01()
{
    Person p1(20,180);
    cout<<"p1的年龄为:"<<p1.age<<" p1的身高为"<<*p1.height<<"cm"<<endl;
    Person p2(p1);
    cout<<"p2的年龄为:"<<p2.age<<" p2的身高为"<<*p2.height<<"cm"<<endl;
}
int main()
{
    test01();
    
    return 0;
}

析构函数注意事项

  1. 析构函数不返回任何值,没有函数参数。因此它不能被重载。

  2. 一个类可以有多个构造函数,但只能有一个析构函数

    以下四种情况,程序会执行析构函数

    1. 如果在一个函数中定义了一个对象(它是自动局部对象),当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数。
    2. static局部对象在函数调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用static局部对象的析构函数
    3. 如果定义了一个全局对象,则在程序的流程离开其作用域时(如main函数结束或调用exit函数) 时,调用该全局对象的析构函数。
    4. 如果用new运算符动态地建立了一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数。
    5. 析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用。

对象特性

初始化列表

语法:构造函数():属性1(值),属性2(值)…{

}


#include <iostream>
using namespace std;
class Person
{
public:
    int a,b,c;
    Person()
    {
        cout << "Person无参构造函数的调用!" << endl;
    }
    Person(int A,int B,int C):a(A),b(B),c(C)
    {
        cout << "Person有参构造函数的调用!" << endl;
    }
    ~Person()
    {
        cout << "Person析构函数的调用!" << endl;
    } 
};

void test01()
{
    Person p(11,22,33);
    cout<<p.a<<" "<<p.b<<" "<<p.c<<endl;
}

int main()
{
    test01();
    return 0;
}

类包含类对象成员

/* Phone的构造函数调用!
Person的构造函数调用!
张三的手机名为:小米手机
Person的析构函数调用!
Phone的析构函数调用!
 */
//构造函数:先调用类对象成员
//析构函数:后析构类对象成员
#include <iostream>
#include <cstring>
using namespace std;
class Phone
{
    public:
    Phone(string a):phoneName(a)
    {
        cout<<"Phone的构造函数调用!"<<endl;
    }

    ~Phone()
    {
        cout<<"Phone的析构函数调用!"<<endl;
    }
    string phoneName;
};

class Person
{
    public:   
    Person(string a,string b):Name(a),PName(b)
    {
        cout<<"Person的构造函数调用!"<<endl;
    }

    ~Person()
    {
        cout<<"Person的析构函数调用!"<<endl;
    }
    string Name;
    Phone PName;
};

void test01()
{
    Person p1("张三","小米手机");
    cout<<p1.Name<<"的手机名为:"<<p1.PName.phoneName<<endl;
}
signed main()
{
    test01();
    return 0;
}

静态成员

//静态成员变量:
//1.在编译阶段分配内存
//2.类内声明,类外初始化
//3.所有对象共享同一份数据
#include <iostream>
using namespace std;
class Person
{
public:
    static int a;
};
int Person::a = 10;//类外初始化
void test01()
{
    Person p1;
    p1.a = 200;
    cout<<p1.a<<endl;
    Person p2;
    p2.a = 2000;
    cout<<Person::a<<endl;//两种访问方式
}
int main()
{
    test01();
    return 0;
}

静态成员函数

//静态成员函数
//所有对象共享同一个函数
//静态成员函数只能访问静态成员变量
#include <iostream>
using namespace std;
class Person
{
public:

    static void func()
    {
        cout<<"func()的函数调用"<<endl;
        a = 1000;
    }
    static int a;
};
int Person::a = 10;//类外初始化
void test01()
{
   Person p1;
   p1.func();
   cout<<Person::a<<endl;
}
int main()
{
    test01();
    return 0;
}

友元

全局函数做友元

#include <iostream>
using namespace std;
class A
{
    friend void func(A &m);
    public:
    A():a(10),b(20)
    {
        
    }
    int a;
    private:
    int b;
};
void func(A &m)
{
   cout<<m.a<<endl;
   cout<<m.b;
}
void test()
{
    A p1;
    func(p1);
}
int main()
{
    test();
    return 0;
}

常见基础知识点

指针常量和常量指针()

如果const位于星号*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;

如果const位于星号*的右侧,const就是修饰指针本身,即指针本身是常量。

通俗理解:
左定值,右定向,const修饰不变量

常量指针

当为常量指针时,不可以通过修改所指向的变量的值,但是指针可以指向别的变量。

int a = 5;
const int *p =&a;
*p = 20;   //error  不可以通过修改所指向的变量的值

int b =20;
p = &b; //right  指针可以指向别的变量

指针常量

当为指针常量时,指针常量的值不可以修改,就是不能指向别的变量,但是可以通过指针修改它所指向的变量的值。

int a = 5;
int *const p = &a;//左定值
*p = 20;     //right 可以修改所指向变量的值

int b = 10;
p = &b;      //error 不可以指向别的变量

强制修改const限定的值

#include <iostream>
using namespace std;
int main()
{
	const int a = 10;
	int* b = (int*)&a;
	*b = 20;
	cout << *b;
	return 0;
}
#include <iostream>
using namespace std;
int main()
{
	const int a = 10;
	int* b = const_cast<int*>(&a);
	*b = 20;
	cout << *b << endl;
	return 0;
}

这段代码尝试通过指针间接修改一个const变量的值。虽然这种做法违反了C++中const关键字的语义,并且会导致未定义行为,但我们可以从内存和编译器模型的角度讨论其背后的原理。

  1. 声明与初始化:
    • 首先,const int a = 10;声明了一个常量a,并初始化为10。这意味着按照语言规则,a的值不应该在后续被修改。
  2. 指针赋值与类型转换:
    • 接着,int* b = (int *)(&a);这一行创建了一个整型指针b,并通过类型强制转换(int *)获得了a的地址。这里,我们告诉编译器忽略aconst属性,允许我们用一个非const指针指向它。这是一个危险的操作,因为它破坏了类型系统的安全性。
  3. 修改指针指向的值:
    • 然后,*b = 20;尝试通过指针b修改其指向地址的内容,即尝试修改a的值为20。由于指针b失去了const信息,从指针操作的角度看,这行代码似乎可以执行对内存的写入操作。
  4. 未定义行为:
    • 尽管上述步骤在某些环境下看似能“工作”,但实际上这是未定义行为。C++标准并没有规定在这种情况下程序应该如何反应。可能的结果包括但不限于:
      • 修改成功:在一些简单的环境或特定编译器配置下,内存中的值确实被修改了,因此*b现在指向的是20。
      • 修改未反映:编译器可能已经将a的值内联到了所有使用它的位置,导致修改内存的实际效果无法体现,打印*b时可能看到预期的20,但直接打印a可能还是10。
      • 程序崩溃:在更严格的安全检查或特定平台下,尝试修改只读内存可能会导致访问违规,从而引发程序崩溃。
      • 其他不可预测行为:包括但不限于数据损坏、优化导致的意料之外的结果等。
  5. 输出:
    • 最后,cout << *b << endl;输出了指针b所指向的值,如果修改生效,这里会输出20,但这并不意味着a本身的值在语言逻辑层面被成功修改了。

总结来说,虽然通过指针强制类型转换和解引用来修改const变量的值在某些情况下看似可行,但这是一种危险且不符合语言规范的做法,可能导致各种不可预料的问题。正确的编程实践是遵循const的语义,确保程序的健壮性和可维护性。

const 和 mutable

mutable就是为了突破成员函数 const的限制,可以在const函数里面来修改被mutable修饰的成员变量。

class A
{
public:
	A()
	{
		this->number = 0;
	}
private:
	mutable int number;//可改变的
public:
	int getNum()const//常量函数
	{
		this->number++;
		return this->number;
	}
};
int main()
{
	A a;
	cout << a.getNum() << endl;
	cout << a.getNum() << endl;

	return 0;
}

继承

image-20240503110157564

继承中构造和析构的顺序

先构造父类,再构造子类

先析构子类,后析构父类

#include <iostream>
using namespace std;

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 test01()
{
    Son s1;
}

int main()
{
    test01();
    return 0;
}

同名成员的调用

#include <iostream>
using namespace std;

class base
{
public:
    int m_a;

    static int m_b;

    void func()
    {
        cout << "base的func()的调用" << endl;
    }
};
int base::m_b = 0;

class Son : public base
{
public:
    int m_a;

    static int m_b;

    void func()
    {
        cout << "Son的func()的调用" << endl;
    }
};
int Son::m_b = 10;
void test01()
{
    Son s1;
    s1.func();
    s1.base::func();
    cout << "Son的m_b: " << s1.m_b << endl;
    cout << "base的m_b: " << s1.base::m_b << endl;
}

int main()
{
    test01();
    return 0;
}

image-20240503182301224

虚基类:菱形继承

#include <iostream>
using namespace std;
class N
{
    public:
    int num;
};

class A:virtual public N{};
class B:virtual public N{};

class C:public A,public B{};

void test01()
{
    C c;
    c.A::num = 100;
    c.B::num = 200;
    cout<<"c中A作用域下的num=="<<c.A::num<<endl;
    cout<<"c中B作用域下的num=="<<c.B::num<<endl;
    cout<<"c中的num=="<<c.num<<endl;
}

int main()
{
    test01();
    return 0;
}

image-20240503191559133

//唯一性,解决二义性

C++运算符重载

加号+运算重载

#include <iostream>
using namespace std;
class A
{
public:
    int m_a;
    int m_b;
    //类成员函数实现加号重载
    A operator+(A p)
    {
        A temp;
        temp.m_a = this->m_a+p.m_a;
        temp.m_b = this->m_b+p.m_b;
        return temp;
    }
};
void test01()
{
    A p1;
    p1.m_a = 20;
    p1.m_b = 30;
    A p2;
    p2.m_a = p2.m_b = 40;
    A p3;
    p3 = p1+p2;
    cout<<"p3的m_a = "<<p3.m_a<<endl;
    cout<<"p3的m_b = "<<p3.m_b<<endl;
}
int main()
{
    test01();
    return 0;
}
#include <iostream>
using namespace std;
class A
{
public:
    int m_a;
    int m_b;
    //类成员函数实现加号重载
    /* A operator+(A p)
    {
        A temp;
        temp.m_a = this->m_a+p.m_a;
        temp.m_b = this->m_b+p.m_b;
        return temp;
    } */
};

//全局函数重载加号运算符
A operator+(A a1,A a2)
{
    A temp;
    temp.m_a = a1.m_a+a2.m_a;
    temp.m_b = a1.m_b+a2.m_b;
    return temp;

}
void test01()
{
    A p1;
    p1.m_a = 20;
    p1.m_b = 30;
    A p2;
    p2.m_a = p2.m_b = 40;
    A p3;
    p3 = p1+p2;
    cout<<"p3的m_a = "<<p3.m_a<<endl;
    cout<<"p3的m_b = "<<p3.m_b<<endl;
}
int main()
{
    test01();

    return 0;
}

左移运算符<<重载

#include <iostream>
using namespace std;
class A
{
    //友元访问隐私成员
    friend ostream& operator<<(ostream &cout,A p);
public:
    A(int a,int b):m_a(a),m_b(b)
    {

    }

private:
    int m_a;
    int m_b;
};
//只能全局函数重载左移运算符
ostream& operator<<(ostream &cout,A p)//链式编程为了和endl匹配
{
    cout<<"p的m_a = "<<p.m_a<<"  p的m_b = "<<p.m_b;
    return cout;
}
void test01()
{
    A p1(20,40);
    cout<<p1<<"\n"<<"小马哥太帅啦!"<<endl;
}

int main()
{
    test01();
    return 0;
}

递增运算符重载

#include <iostream>
using namespace std;

class A
{
    friend ostream &operator <<(ostream& cout,A p);//这里不引用
public:
    A(int a):m_a(a){};

    //前置++
    A &operator++()//这里一定要引用
    {
        m_a++;
        return *this;
    }

    //后置++
    A operator++(int)//这里不引用***
    {
        //记录当时结果
        A temp = *this;
        m_a++;
        return temp;
    }


private:
    int m_a;

};

ostream &operator <<(ostream& cout,A p)
{
    cout<<p.m_a;
    return cout;
}
void test01()
{
    A p1(1);
    cout<<++p1<<endl;
    cout<<p1<<endl;
}
void test02()
{
    A p2(1);
    cout<<p2++<<endl;
    cout<<p2;
}
int main()
{
    test01();
    test02();
    return 0;
}

image-20240507105423210

赋值运算符重载

#include <iostream>
using namespace std;
class A
{
    public:
    A(int a)
    {
        m_a = new int(a);
    }

    ~A()
    {
       if(m_a!=NULL)
        {
            delete m_a;
            m_a = NULL;
        }
    }
    //重载赋值运算符
    //为了防堆区内存重复释放
    A &operator=(A &p)
    {
        if(m_a!=NULL)//析构函数的思路
        {
            delete m_a;
            m_a = NULL;
        }
        m_a = new int(*p.m_a);
        return *this;
    }
    int *m_a;

};

void test01()
{
    A p1(20),p2(30),p3(40);
    p1 = p2 = p3;
    cout<<*p1.m_a<<endl;
    cout<<*p2.m_a<<endl;
    cout<<*p3.m_a<<endl;

}
int main()
{
    test01();    
    return 0;
}

关系运算符重载

#include <iostream>
using namespace std;
#include <cstring>
class A
{
public:
    A(int a,string name):m_a(a),m_name(name){};
    int m_a;
    string m_name;

    bool operator==(A p)
    {
        if(this->m_a==p.m_a&&this->m_name==p.m_name){
            return true;
        }
        return false;

    }
};

void test01()
{
    A p1(18,"马洪建");
    A p2(18,"马洪建");
    if(p1==p2){
        cout<<"相同"<<endl;
    }
    else{
        cout<<"不同"<<endl;
    }

}
int main()
{
    test01();
    return 0;
}

多态

多态的基本使用

#include <iostream>
using namespace std;
class Animal
{
    public:
    //虚函数,在编译阶段就不调用了
    virtual void Speak()
    {
        cout<<"动物在说话"<<endl;
    }
};
class Cat:public Animal
{
    public:
    void Speak()
    {
        cout<<"小猫在说话"<<endl;
    }
};
class Dog:public Animal
{
    public:
    void Speak()
    {
        cout<<"小狗在说话"<<endl;
    }
};

void DoSpeak(Animal &animal)
{
    animal.Speak();
}
//多态满足的基本条件:
//1,有继承关系
//2,子类重写父类中的虚函数

//多态的使用
//父类指针或引用子类对象

void test01()
{
    Cat cat;
    DoSpeak(cat);
    Dog dog;
    DoSpeak(dog);
}

int main()
{
    test01();
    return 0;
}

纯虚函数和抽象类

#include <iostream>
using namespace std;

//纯虚函数和抽象类
class Base
{
public:
    virtual func() = 0;
    //纯虚函数
    //只要有一个纯虚函数,这个类就叫做抽象类
    //抽象类特点:
    //1,无法实例化对象
    //2,抽象类的子类一定要重写父类中的纯虚函数,不然也属于抽象类

};
class Son:public Base
{
public:
    virtual func(){
        cout<<"func()函数的调用"<<endl;
    }
};

void test01()
{
    Base *base = new Son;
    base->func();

}
int main()
{
    test01();

    return 0;
}

虚析构和纯虚析构

#include <iostream>
using namespace std;
#include <cstring>
class Animal
{
    public:
    Animal()
    {
        cout<<"Animal的构造函数调用!"<<endl;
    }
    //利用虚析构可以解决父类指针在析构的时候 不会调用子类中析构函数
    /* virtual ~Animal()
    {
        cout<<"Animal的虚析构函数调用!"<<endl;
    }
 */
    //纯虚析构
    //需要声明,也需要具体实现
    virtual ~Animal() = 0;

    //虚函数,在编译阶段就不调用了
    virtual void Speak()
    {
        cout<<"动物在说话"<<endl;
    }
};

Animal::~Animal()
{
    cout<<"Animal的纯虚析构函数调用!"<<endl;
}

class Cat:public Animal
{
    public:
    Cat(string name)
    {
        cout<<"Cat的构造函数调用"<<endl;
        m_name = new string(name);
    }
    ~Cat()
    {
        if(m_name!=NULL){
            cout<<"Cat的析构函数调用!"<<endl;
            delete m_name;
            m_name = NULL;
        }
    }
    void Speak()
    {
        cout<<*m_name<<"小猫在说话"<<endl;
    }
    string *m_name;
};
class Dog:public Animal
{
    public:
    void Speak()
    {
        cout<<"小狗在说话"<<endl;
    }
};

void DoSpeak(Animal &animal)
{
    animal.Speak();
}


void test01()
{
    Animal *animal = new Cat("汤姆猫");
    animal->Speak();
    delete animal;
    //父类指针在析构的时候 不会调用子类中析构函数,导致子类如果有堆区属性,会导致内存泄露
}

int main()
{
    test01();
    return 0;
}

文件操作

image-20240513092703392

image-20240513092735264

image-20240513092750319

image-20240513092803677

写文件

#include <iostream>
using namespace std;
#include <fstream>

void test01()
{
    //创建流对象
    ofstream ofs;

    //指定打开方式
    ofs.open("text.txt", ios::out);

    //写内容
    ofs << "小马哥" << endl;
    ofs << "男" << endl;
    ofs << "19岁" << endl;

    ofs.close();
}

int main()
{
    test01();
    std::cout << "Hello World!\n";
}

读文件

image-20240513092622834

#include <iostream>
using namespace std;
#include <fstream>
#include <string>

//文本文件

void test01()
{
    //1.包含头文件

    //2.创建流对象
    ifstream ifs;
    //3.打开文件 ,判断是否打开成功
    ifs.open(("E:\\学习\\编程\\vs2022\\c++\\黑马程序员\\文本操作 写文件\\文本操作 写文件\\text.txt"),ios::in);
    if (!ifs.is_open())
    {
        cout << "文件打开失败了" << endl;
        return;
    }
    //4.读数据

    //第一种
    char buf[1024] = { 0 };
    while (ifs >> buf)
    {
        cout << buf << endl;
    }

    //第二种
    char buf[1024] = { 0 };
    while (ifs.getline(buf, sizeof(buf)))
    {
        cout << buf << endl;
    }

    //第三种
    string s;
    while (getline(ifs, s))
    {
        cout << s << endl;
    }
    //第四种
    char c;
    while ((c = ifs.get()) != EOF)
    {
        cout << c;
    }
    //5.关闭
    ifs.close();
}
int main()
{
    test01();
}

模板

函数模板

函数模板的使用方法

#include <iostream>
using namespace std;
//两种方式使用函数模板
    /* 
    1.自动类型推导
     mySwap(a,b);
    2,显示指定类型
    mySwap<int>(a,b) */
//函数模板
template<typename T>
void mySwap(T &a,T &b)
{
    T temp = a;
    a = b;
    b = temp;
}


void test01()
{
    int a = 201;
    int b = 10;
    mySwap<int>(a,b);
    cout<<"a = "<<a<<endl;
    cout<<"b = "<<b<<endl;
}
int main()
{
    test01(); 
    return 0;
}

函数模板注意事项

//注意事项:
//1,函数模板必须推导出一致数据类型

//2,模板必须要给出T的数据类型,才可以使用
#include <iostream>
using namespace std;
//函数模板
template<typename T>
void mySwap(T &a,T &b)
{
    T temp = a;
    a = b;
    b = temp;
}

template<class T>
void func()
{
    cout<<"func()的调用!"<<endl;
}

void test01()
{
    int a = 201;
    int b = 10;
    //两种方式使用函数模板
    /* 
    1.自动类型推导
     mySwap(a,b);
    2,显示指定类型
    mySwap<int>(a,b) */
    mySwap<int>(a,b);
     
    //必须指定T的数据类型
    func<int>();//<int>
      
    cout<<"a = "<<a<<endl;
    cout<<"b = "<<b<<endl;
}
int main()
{
    test01();
    return 0;
}

函数模板案例-数组排序

#include <iostream>
using namespace std;
/* 实现通用对数组进行排序的函数
规则 从大到小
算法 选择
测试 char数组 int数组 */

//交换函数模板
template<class 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;i++)
    {
        int max = i;
        for(int j = i+1;j<len;j++)
        {
            if(arr[max]<arr[j])
            {
                max = j;
            }
        }
        if(max!=i)
        {
            mySwap(arr[max],arr[i]);
        }
    }
}

//打印数组模板
template<class T>
void printArr(T arr[],int len)
{
     for(int i = 0;i<len;i++)
     {
        cout<<arr[i]<<' ';
     }
     cout<<endl;
}
void test01()
{
    //测试char数组
    char chararr[] = "abrfyhuj";
    mySort(chararr,sizeof(chararr));
    printArr(chararr,sizeof(chararr));

}
void test02()
{
    //测试int数组
    int a[] = {1,2,5,6,3,1,45,444,56};
    mySort(a,sizeof(a)/sizeof(int));
    printArr(a,sizeof(a)/sizeof(int));
}

int main()
{
    test01();
    test02();
    return 0;
}

函数模板和普通函数的区别

#include <iostream>
using namespace std;


//函数模板与普通函数的区别
/* 
1,普通函数在调用时可以发生隐式类型转换
2,函数模板,用自动类型推导,不可以发生隐式类型转换
3,函数模板 用显示指定类型,可以发生隐式类型转换 */

void test01()
{
    int a = 201;
    int b = 10;
    //两种方式使用函数模板
    /* 
    1.自动类型推导
     mySwap(a,b);
    2,显示指定类型
    mySwap<int>(a,b) */
    mySwap<int>(a,b);
   
    cout<<"a = "<<a<<endl;
    cout<<"b = "<<b<<endl;
}
int main()
{
    test01();
    
    return 0;
}

函数模板的调用规则

#include <iostream>
using namespace std;

//普通函数和函数模板的调用规则
/* 
1,如果函数模板和普通函数都可以调用,优先调用普通函数
2,可以通过空模板参数列表,强制调用函数模板
3,函数模板可以发生函数重载
4,如果函数模板可以产生更好的匹配,优先调用函数模板 */

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

template<typename T>
void Myfunc(T a,T b)
{
    cout<<"函数模板调用"<<endl;
}
template<typename T>
void Myfunc(T a,T b,T c)
{
    cout<<"函数模板重载的调用"<<endl;
}

void test01()
{
    int a = 201;
    int b = 10;
    //1,如果函数模板和普通函数都可以调用,优先调用普通函数
    //2,可以通过空模板参数列表,强制调用函数模板
    //3,函数模板可以发生函数重载
    Myfunc(a,b);

    //空模板参数列表,强制调用函数模板
    Myfunc<>(a,b);
    //函数模板重载的调用
    Myfunc<>(a,b,100);
}

void test02()
{
    char a = 'a';
    char b = 'b';
    //4,如果函数模板可以产生更好的匹配,优先调用函数模板
    Myfunc(a,b);
}
int main()
{
    //test01();
    test02();    
    return 0;
}

模板的局限性

#include <iostream>
using namespace std;

//函数模板的局限性
//模板并不是万能的,有些特定的数据类型,需要具体化的方式做特殊实现
class Person
{
public:
    Person(string name,int age)
    {
        this->m_Name = name;
        this->m_Age = age;
    }
    string m_Name;
    int m_Age;
};



//对比两个数据是否相等的函数
template<class T>
bool myCmp(T &a,T &b)
{
    if(a==b)return true;
    else return false;
}

//利用具体化Person的版本代码实现Compare函数
template<>
bool myCmp(Person &a,Person &b)
{
    if(a.m_Name == b.m_Name && a.m_Age == b.m_Age)
    {
        return true;
    }
    else
    {
        return false;
    }
}

void test01()
{
    int a = 10;
    int b = 20;
    bool ret = myCmp(a,b);
    if(ret)
    {
        cout<<"a==b"<<endl;
    } 
    else
    {
        cout<<"a!=b"<<endl;
    }
}

//测试并不是万能的
void test02()
{
    Person p1("Tom",10);
    Person p2("Tom",10);
    bool ret = myCmp(p1,p2);
    if(ret)
    {
        cout<<"p1==p2"<<endl;
    } 
    else
    {
        cout<<"p1!=p2"<<endl;
    }
}
int main()
{
    test01();
    test02();
    return 0;
}

类模板

类模板的基本语法

#include <iostream>
using namespace std;
#include <string>
//类模板
template<class Nametype,class Agetype>
class Person
{
    public:

    Person(Nametype name,Agetype age):m_name(name),m_age(age){};
    Nametype m_name;
    Agetype m_age;

};

void test01()
{
    Person<string,int> p("Tom",20);//类模板的基础语法
    cout<<p.m_name<<endl;
    cout<<p.m_age<<endl;
}
int main()
{
    test01();
    return 0;
}

类模板和函数模板的区别

类模板不能发生自动类型推导,必须显示调用类模板
//类模板和函数模板的调用区别
//1.类模板没有自动类型推导,只能用显示调用
#include <iostream>
using namespace std;
template <class NameType,class AgeType>
class Person
{
    public:
    Person(NameType name,AgeType age):m_name(name),m_age(age){};
    void Show()
    {
        cout<<this->m_name<<' '<<this->m_age<<endl;
    }
    NameType m_name;
    AgeType m_age;
};

//类模板没有自动类型推导
void test01()
{
    //Person p1("小明",20);没有自动类型推导,有的编译器会优化
    Person <string,int>p1("小明",20);
    p1.Show();
}
int main()
{
    test01();

    return 0;
}
类模板可以有默认参数列表
//类模板和函数模板的调用区别
//2.类模板在参数列表中,可以有默认参数
#include <iostream>
using namespace std;
template <class NameType,class AgeType = int>//默认参数
class Person
{
    public:
    Person(NameType name,AgeType age):m_name(name),m_age(age){};
    void Show()
    {
        cout<<this->m_name<<' '<<this->m_age<<endl;
    }
    NameType m_name;
    AgeType m_age;
};

//类模板在参数列表中,可以有默认参数
void test01()
{
    
    Person <string>p1("小明",20);//这里可以不写int因为AgeType = int
    p1.Show();
}
int main()
{
    test01();

    return 0;
}

类模板中成员函数的调用时机

#include <iostream>
using namespace std;
//类模板中成员函数的调用时机
//类模板中的成员函数在调用的时候才去创建
class Person1
{
    public:
    ShowPerson1()
    {
        cout<<"Person1的函数调用"<<endl;
    }
};
class Person2
{
    public:
    void ShowPerson2()
    {
        cout<<"Person2的函数调用"<<endl;
    }
};


template <class T>//不调用只编译是不会报错的
class A
{
    public:
    T obj;
    
    //类模板中的成员函数
    void func1()
    {
        obj.ShowPerson1();
    }

    void func2()
    {
        obj.ShowPerson2();
    }
};

void test01()
{
    A<Person2>m;
    //m.func1();   调用1不可以,因为类型不匹配
    m.func2();
}
int main()
{
    test01();
    return 0;
}

类模板做函数的参数

//类模板对象做函数的参数
//1.指定传入类型--整体拿进来
//2.参数模板化
//3.整个类模板化
#include <iostream>
using namespace std;
#include <string>
template <class Nametype,class Agetype>
class Person
{
public:
    Person(Nametype name, Agetype age)
    {
        this->name = name;
        this->age = age;
    }
    void Showperson()
    {
        cout << "name: " << name << endl;
        cout << "age: " << age << endl;
    }
    Nametype name;
    Agetype age;
};

//1.指定传入类型--整体拿进来
void PrintPerson1(Person<string, int> &p)
{
    p.Showperson();
}
void test01()
{
    Person<string, int> p("Tom", 10);
    PrintPerson1(p);
}
//2.参数模板化
template <class T1,class T2>
void PrintPerson2(Person<T1,T2>&p)
{
    p.Showperson();
    cout<<"T1的数据类型是:"<<typeid(T1).name()<<endl;
    cout<<"T2的数据类型是:"<<typeid(T2).name()<<endl;
}

void test02()
{
    Person<string,int> p2("猪八戒",40);
    PrintPerson2(p2);
}

//3.整个类模板化
template<class T>
void PrintPerson3(T &p)
{
    p.Showperson();
    cout<<"T的数据类型是:"<<typeid(T).name()<<endl;
}
void test03()
{
    Person <string,int>p3("孙悟空",100);
    PrintPerson3(p3);
}
int main()
{
    test01();
    test02();
    test03();
    return 0;
}

类模板和继承

#include <iostream>
using namespace std;
#include <string>
//如果父类是类模板,子类要么指定出父类模板的数据类型,要么子类也是类模板
template<class T>
class base
{
public:
    T name;
};
//指定出父类模板的数据类型
class Son1:public base<string>{
    public:
};
void test01()
{
    Son1 c;
}

//类模板继承类模板,就可以用T2指定父类中T的类型
template <class T1,class T2>
class Son2:public base<T2>
{
    public:
    Son2()
    {
        cout<<"T1的数据类型是:"<<typeid(T1).name()<<endl;
        cout<<"T2的数据类型是:"<<typeid(T2).name()<<endl;

    }
    T1 obj;
};
void test02()
{
    Son2<string,int> a;
}
int main()
{
    test02();

    return 0;
}

类模板外定义各成员函数

源代码

#include <iostream>
using namespace std;
template <class numtype>
class Compare
{
public:
    Compare(numtype a, numtype b)
    {
        x = a;
        y = b;
    }

    numtype max()
    {
        return (x > y) ? x : y;
    }

    numtype min()
    {
        return (x < y) ? x : y;
    }

private:
    numtype x, y;
};


int main()
{
    Compare<int> cmp1(3, 7);
    cout << cmp1.max() << endl;
    cout << cmp1.min() << endl
         << endl;
    Compare<float> cmp2(45.7, 93.6);
    cout << cmp2.max() << endl;
    cout << cmp2.min() << endl
         << endl;
    Compare<char> cmp3('a','A');
    cout << cmp3.max() << endl;
    cout << cmp3.min() << endl;
    return 0;
}

在类外定义各成员函数

#include <iostream>
using namespace std;

template <class numtype>
class Compare
{
public:
    Compare(numtype a, numtype b);
    numtype max();
    numtype min();

private:
    numtype x, y;
};

// 在类模板外部声明成员函数
template <class numtype>
Compare<numtype>::Compare(numtype a, numtype b)
{
    x = a;
    y = b;
}

template <class numtype>
numtype Compare<numtype>::max()
{
    return (x > y) ? x : y;
}

template <class numtype>
numtype Compare<numtype>::min()
{
    return (x < y) ? x : y;
}

int main()
{
    Compare<int> cmp1(3, 7);
    cout << cmp1.max() << endl;
    cout << cmp1.min() << endl
         << endl;
    Compare<float> cmp2(45.7, 93.6);
    cout << cmp2.max() << endl;
    cout << cmp2.min() << endl
         << endl;
    Compare<char> cmp3('a', 'A');
    cout << cmp3.max() << endl;
    cout << cmp3.min() << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值