C++学习笔记

本文详细介绍了C++的基础知识,涵盖类和对象的概念,C++中的函数(如带默认参数、重载、模板、内联和友元),命名空间和输入输出流,以及构造函数、析构函数和拷贝构造函数。此外,还讲解了C++中的继承、派生、多态性,包括虚函数、虚析构函数和抽象类。最后,探讨了文件操作的基本步骤,如打开、读写和关闭文件。这些内容构成了C++面向对象编程的核心概念和技术。
摘要由CSDN通过智能技术生成

第一章 C++入门

一、从C到C++
C++语言是在C语言基础上为支持面向对象设计的一套程序语言,面向对象体现在“类”的机制。
何为类?
C语言结构体

struct stu
{
    int num;
    char sex;
    int math_score;
    int en_score;
    int c_score;
  
};
int main()
{
   struct stu A;
    return 0;
}

C++类:

class stu
{
    int num;
    char sex;
    int math_score;
    int en_score;
    int c_score;
  
    int total_score()
    {
      return math_score+en_score+c_score;
    };
  
};
int main()
{
   class stu A;
    return 0;
}

C++的class中多了函数定义,C++类的成员变量叫做属性,类的函数叫做方法,即类拥有属性和方法两部分。
二、命名空间
using namespace std;
这是C++新引入的一个机制,主要是为解决多个模块命名冲突的问题。将相同的名字放到不同空间里,来防止名字的冲突。
例如标准C++库提供的对象都存放在std这个标准名字空中,比如cin、cout、endl,所以我们会看到在C++程序中都会有using namespace std;
三、C++的输入输出
1、cout输出流的使用
cout << “Hello”;会在屏幕上显示字符串Hello
本质上是将字符串“Hello”插入到cout对象里,并以cout对象作为返回值返回。
2、cin输入流的使用
接受一个数据之前,都要先定义一个与之类型一致的变量用来存放这个数据,然后利用cin>>输入操作符来接受用户从键盘的输入。
C语言:

while((scanf("%d%d",&a,&b)) == 2){
...//循环输入
}

c++:

while(cin >> a >> b){
...
}

第三章 函数

一、带默认形参值得函数

#include<iostream>
using namespace std;
int add(int a=3,int b=5)
{
    return a+b;
}
int main()
{
    cout<<add(10,20)<<endl;//将10和20分别给a和b
    cout<<add(30)<<endl;//将30给a,b为默认的5
    cout<<add()<<endl;//使用a、b的默认值3和5
    return 0;
}

注意:
(1)由于参数传递的顺序是从右向左入栈,所以有默认值的参数必须放在形参列表的最右边!
(2)另外,当函数需要提前说明时,若形参存在默认参数,则声明部分可以制定默认值,后面的定义部分可以不再制定默认值。
二、C++函数重载
C++函数重载即两个或两个以上的函数,函数名相同,但形参类型或个数不同,编译器根据传入参数的类型和个数,自动选择合适的函数调用。
eg:

#include<iostream>
using namespace std;
int add(int a,int b)
{
    cout<<"(int ,int)\t";
    return a+b;
}
double add(double a,double b)
{
    cout<<"(doble ,double)\t";
    return a+b;
}
double add(double a,int b)
{
    cout<<"(double ,int)\t";
    return a+b;
}
double add(int a,double b)
{
    cout<<"(int ,double)\t";
    return a+b;
}
int main()
{
    cout<<add(2,3)<<endl;
    cout<<add(2.9,15.3)<<endl;
    cout<<add(10,9.9)<<endl;
    cout<<add(11.5,5)<<endl;
    return 0;
}

三、函数模板
C++提供函数模板这一机制,大大提高代码的可重用性。
函数模板,是可以创建一个通用的函数,可以支持多种形参。

用关键字template来定义,形式如下:

template<class 类型名1,class 类型名2…>
返回值 函数名(形参表列) 模板参数表
{
函数体
}
说明一下,这个一般形式中,第一行的template<class 类型名1,class 类型名2…>是一句声明语句 ,template是定义模板函数的关键字,尖括号里可以有多个类型,前面都要用class(或者typename来定义)。然后后面跟定义的函数模板,切记中间不可以加其他的语句,不然会报错!

eg

#include<iostream>
using namespace std;
template<class T1,class T2>
T1 add(T1 x,T2 y)
{
    cout<<sizeof(T1)<<","<<sizeof(T2)<<"\t";
    return x+y;
}
int main()
{
    cout<<add(10,20)<<endl;;
    cout<<add(3.14,5.98)<<endl;
    cout<<add('A',2)<<endl;
    return 0;
}

四、incline内联函数
内联函数的机制,在编译时,把函数代码插到函数调用处,从而免去函数调用的一系列过程。
eg:

#include<iostream>
using namespace std;
  
inline int Max(int a,int b)
{
    return a>b?a:b;
}
int main()
{
    cout<<Max(3,5)<<endl;
    cout<<Max(7,9)<<endl;
    return 0;
}

注意:
内联函数的定义要在调用之前出现,才可以让编译器在编译期间了解上下文,进行代码替换。

第四章 类和对象

一、类的定义
类是对象的抽象和概括,对象是类的具体和实例。
eg

class Student
{
public:
    int num;
    char name[100];
    int score;
    int print()
    {
        cout<<num<<" "<<name<<" "<<score;
        return 0;
    }
};

形式上类和C语言的结构体非常像,成员有变量也有函数,一般称之为成员变量和方法。还有一个控制成员访问权限的一个控制属性:public、protected、private。
private:表示私有,被它声明的成员仅能在被该类的成员访问,外界不能访问,是最封闭的一种权限。
protected:除了类自己的成员可以访问,它的子类也可以访问。
public:可以被该类的任何对象访问,是完全公开的数据。
☆☆☆注意:类后面有一个分号;不可以去掉。
二、对象的建立和使用
1、对象的创建
类是包含函数的结构体,是一种自定义数据类型,用它定义的变量,即是对象。所谓“对象是类的具体和实例”。
eg:

Student A;
A.num = 101;
strcpy(A.name,"dotcpp");//字符串赋值
A.score = 100;
A.print();

2、对象的指针

Student *p;
Student A;
p = &A;
p->print();

与普通变量一样,对象也是一片连续的内存空间,因此可以创建一个指向对象的指针,即对象指针,存储这个对象的地址。
在传参的时候也建议用指针来传递,,因为其传递的为地址,不会进行对象之间的副本赋值,从而减少内存的开销,提高效率。
3、对象的引用
引用,是C++中一种新的类型,对象引用就是给类对象取一个别名。本质上也是把这个类对象的地址赋给了这个引用类型,两者是指向一块内存空间的。
eg:

Student A;
Student &Aq=A;

需要注意的是:

  1. 与指针一样,两者必须是同类型才可以引用。
  2. 除非做函数的返回值或形参时,其余定义引用类型的同时就要初始化!
  3. 引用类型并非是新建立一个对象,因此也不会调用构造函数。
Student A;
Student &Aq=A;
Aq.print();

用引用类型时,本质还是存的地址,因此无论传参定义都不会太多内存开销,有指针的优势,同时使用起来和类对象本身使用一样,再做函数实参时,直接传入引用对象就可以,不用加地址符,因此看起来更直观、方便。这就是引用类型的优点。
4、C++中的构造函数
(1)构造函数
特殊函数,与类名同名,且没有返回值的一个函数,只要我们定义一个类的对象,系统会自动调用它,进行专门初始化对象用。
当没有定义构造函数时,系统会默认生成一个无参构造函数,
eg:

#include<iostream>
#include<Cstring>
using namespace std;
class Student
{
    private:
    int num;//学号
    char name[100];//名字
    int score;//成绩
    public:
    Student(int n,char *str,int s);
    int print();
    int Set(int n,char *str,int s);
};
Student::Student(int n,char *str,int s)
{
     num = n;
     strcpy(name,str);
     score = s;
cout<<"Constructor"<<endl;
}
int Student::print()
{
    cout<<num<<" "<<name<<" "<<score;
    return 0;
}
int Student::Set(int n,char *str,int s)
{
     num = n;
     strcpy(name,str);
     score = s;
}
int main()
{
    Student A(100,"dotcpp",11);
    A.print();
    return 0;
}

5、析构函数
(1)在对象销毁时也会自动调用一个函数,它也和类名同名,也没有返回值,名字前有一个个波浪线~,用来区分构造函数,它的作用主要是用做对象释放后的清理善后工作。它,就是析构函数。
(2)与构造函数相同的是,与类名相同,没有返回值,如果用户不定义,系统也会自动生成一个空的析构函数。而一旦用户定义,则对象在销毁时自动调用。
(3)与构造函数不同的是,虽然他俩都为公开类型。构造可以重载,有多个兄弟,而析构却不能重载,但它可以是虚函数,一个类只能有一个析构函数。
eg:

#include<iostream>
#include<Cstring>
using namespace std;
class Student
{
    private:
    int num;//学号
    char name[100];//名字
    int score;//成绩
    public:
    Student(int n,char *str,int s);
    ~Student();
    int print();
    int Set(int n,char *str,int s);
};
Student::Student(int n,char *str,int s)
{
     num = n;
     strcpy(name,str);
     score = s;
     cout<<num<<" "<<name<<" "<<score<<" ";
     cout<<"Constructor"<<endl;
}
Student::~Student()
{
    cout<<num<<" "<<name<<" "<<score<<" ";
    cout<<"destructor"<<endl;
}
int Student::print()
{
    cout<<num<<" "<<name<<" "<<score<<endl;
    return 0;
}
int Student::Set(int n,char *str,int s)
{
     num = n;
     strcpy(name,str);
     score = s;
}
int main()
{
    Student A(100,"dot",11);
    Student B(101,"cpp",12);
    return 0;
}

6、拷贝构造函数
在C++中,与类名同名,且形参是本类对象的引用类型的函数,叫做拷贝构造函数(Copy Constrctor),与构造函数一样,当我们不主动定义的时候,系统也会自动生成一个,进行两个对象成员之间对应的简单赋值,用来初始化一个对象

#include<iostream>
using namespace std;
#define PI 3.1415
class Circle
{
    private:
    double R;
    public:
    Circle(double R);
    Circle(Circle &A);
    double area();
    double girth();
};
Circle::Circle(double R)
{
    cout<<"Constructor"<<endl;
    this->R = R;
}
Circle::Circle(Circle &A)
{
    cout<<"Copy Constructor"<<endl;
    this->R = A.R;
}
double Circle::area()
{
    return PI*R*R;
}
double Circle::girth()
{
    return 2*PI*R;
}
int main()
{
  
    Circle A(5);
    Circle B(A);
    return 0;
}

第一次定义的A对象调用带参数的构造函数,第二个B对象由于是通过A对象来初始化,所以调用拷贝构造函数。
7、浅拷贝和深拷贝
之所以引入深拷贝,是因为如果成员变量中加一个指针成员,初始化中需要动态开辟内存,否则会出现安全隐患。
eg:

#include<iostream>
#include<cstring>
using namespace std;
#define PI 3.1415
class Circle
{
    private:
    double R;
    char *str;
    public:
    Circle(double R,char *str);
    Circle(Circle &A);
    ~Circle();
    double area();
    double girth();
};
  
Circle::~Circle()
{
    delete []str;
    cout<<"Call Destructor"<<endl;
}
Circle::Circle(Circle &A)//拷贝构造(深拷贝)
{
    cout<<"Copy Constructor"<<endl;
    this->R = A.R;
    this->str = new char[strlen(A.str)+1];
    strcpy(this->str,A.str);
}
Circle::Circle(double R,char *str)
{
    cout<<"Constructor"<<endl;
    this->R = R;
    this->str = new char[strlen(str)+1];
    strcpy(this->str,str);
}
  
double Circle::area()
{
    return PI*R*R;
}
double Circle::girth()
{
    return 2*PI*R;
}
int main()
{
  
    Circle A(5,"NO.1 Old class");
    Circle B(A);
    return 0;
}

上述代码中如果没有拷贝构造函数

Circle::Circle(Circle &A)//拷贝构造(深拷贝)
{
    cout<<"Copy Constructor"<<endl;
    this->R = A.R;
    this->str = new char[strlen(A.str)+1];
    strcpy(this->str,A.str);
}

因为默认的拷贝构造函数仅仅是进行数据赋值,并不能为指针开辟内存空间,相当于代码:

This->str = str;

那么本质上,也就是两个指针指向一块堆空间。已经违背了我们的初衷。那么在程序结束的时候,两个对象回收的时候,会调用自己的析构函数,释放这块内存空间,由于两个对象要调用两次,即delete两次,就会出现错误!
所以必须定义一个特定的拷贝构造函数,即不仅可以进行数据的拷贝,也可以为成员分配内存空间,实现真正的拷贝,也叫做深拷贝,这就是深拷贝构造函数。
在这里插入图片描述
三、C++中的this指针
this指针式一个类中一个很隐蔽的特殊指针。

int Clock::SetTime(int h,int m,int s)
{
   this->H = h;
   this->M = m;
   this->S = s;
}
//也可以写成:
int Clock::SetTime(int h,int m,int s)
{
   (*this).H = h;
   (*this).M= m;
   (*this).S= s;
}

实际上,当一个对象调用其成员函数的时候,即便程序中有多个该类的对象,但成员函数的代码也仅有一份,所以为了区分它们是哪个对象调用的成员函数,编译器也是转化成this->成员函数这种形式来使用的。
2、友元函数
类中的私有成员,只有被类中的成员函数访问,在类外是不能被访问的。体现了C++类设计的封装、隐蔽思想。
但有时在类外确实想访问这些私有成员就会很麻烦,所以采用友元函数解决。
友元的对象,它可以是全局的一般函数,也可以是其他类里的成员函数,这种叫做友元函数。不仅如此,友元还可以是一个类,这种叫做友元类。
eg:

#include<iostream>
#include<math.h>
using namespace std;
class Point
{
    private:
        double x;
        double y;
    public:
        Point(double a,double b)
        {
            x = a;
            y = b;
        }
        int GetPoint()
        {
            cout<<"("<<x<<","<<y<<")";
            return 0;
        }
        friend double Distance(Point &a,Point &b);
};
//求两点之间的距离
double Distance(Point &a,Point &b)
{
    double xx;
    double yy;
    xx = a.x-b.x;
    yy = a.y-b.y;
  
    return sqrt(xx*xx+yy*yy);
}
int main()
{
    Point A(2.0,3.0);
    Point B(1.0,2.0);
    double dis;
    dis = Distance(A,B);
    cout<<dis<<endl;
    return 0;
}

3、友元类的使用方法

#include<iostream>
#include<math.h>
using namespace std;
  
class Point
{
    private:
        double x;
        double y;
    public:
        Point(double a,double b)
        {
            x = a;
            y = b;
        }
        int GetPoint()
        {
            cout<<"("<<x<<","<<y<<")";
            return 0;
        }
        int distancetoLine()
        {
  
        }
    friend class Tool;
};
  
class Tool
{
public:
    double GetX(Point &A)
    {
        cout<<A.x<<endl;
        return A.x;
    }
    double GetY(Point &A)
    {
        cout<<A.y<<endl;
        return A.y;
    }
    double dis(Point &A)
    {
        cout<<sqrt(A.x*A.x+A.y*A.y)<<endl;
        return  sqrt(A.x*A.x+A.y*A.y);
    }
};
  
int main()
{
    Point A(2.0,3.0);
    Tool T;
    T.GetX(A);
    T.GetY(A);
    T.dis(A);
    return 0;
}

友元机制的优缺点总结如下:

优点:更方便快捷的访问类内的私有成员

缺点:打破了C++中的封装思想
四、C++中常数据的使用及初始化
1、常数据成员
格式:

数据类型 const 数据成员名;
或 const 数据类型 数据成员名;

另外有一个特殊情况,如果成员是static类型,即静态常数据成员,因为是静态属性,所以需要在类外进行初始化。
eg:

#include<iostream>
using namespace std;
class Clock
{
private:
    const int h;  //修饰h为常类型成员
    const int m;  //修饰m为常类型成员
    int const s;   //和上面两种用法都可以
    static const int x;
public:
    Clock(int a,int b,int c):h(a),m(b),s(c)
    {
        cout<<"Constrctor! Called"<<endl;
    }
    int ShowTime()
    {
        cout<<h<<":"<<m<<":"<<s<<endl;
        return 0;
    }
    int GetX()
    {
        cout<<x<<endl;
        return 0;
    }
};
const int Clock::x = 99;
int main()
{
    Clock A(12,10,30);
    A.ShowTime();
    A.GetX();
    return 0;
}

2、常对象
C++中可以把一个对象声明为const类型,即常对象。这样声明之后,这个对象在整个生命周期中就不可以再被更改,所以在定义的时候要由构造函数进行初始化。

定义格式如下:类型 const 对象名;
或 const 类型 对象名;

注意:常对象不可以访问类中的非常成员函数,只能访问常成员函数。
eg:

#include<iostream>
using namespace std;
class Clock
{
private:
    const int h;  //修饰h为常类型成员
    const int m;  //修饰m为常类型成员
    int const s;   //和上面两种用法都可以
    int x;
public:
    Clock(int a,int b,int c):h(a),m(b),s(c)
    {
        x=99;
        cout<<"Constrctor! Called"<<endl;
    }
    int ShowTime()
    {
        cout<<h<<":"<<m<<":"<<s<<endl;
        return 0;
    }
    int GetX() const
    {
        //x=99;
        cout<<x<<endl;
        return 0;
    }
};
  
int main()
{
    const Clock A(12,10,30);
    const Clock B(14,20,50);
    //A = B;
    //A.ShowTime();
    A.GetX();
    return 0;
}

3、常成员函数
类似的,一个类中的成员函数被const修饰后,就变成了常成员函数,常成员函数的定义如下:

返回类型 函数名(形参表列)const;

需要注意:

常成员函数的定义和声明部分都要包含const;

常成员函数只能调用常成员函数,而不能调用非常成员函数,访问但不可以更改非常成员变量。
eg:

#include<iostream>
using namespace std;
class Clock
{
private:
    const int h;  //修饰h为常类型成员
    const int m;  //修饰m为常类型成员
    int const s;   //和上面两种用法都可以
    int x;
public:
    Clock(int a,int b,int c):h(a),m(b),s(c)
    {
        x=99;
        cout<<"Constrctor! Called"<<endl;
    }
    int ShowTime()
    {
        cout<<h<<":"<<m<<":"<<s<<endl;
        return 0;
    }
    int GetX() const
    {
        //x=99;
        cout<<x<<endl;
        return 0;
    }
};
  
int main()
{
    const Clock A(12,10,30);
    A.GetX();
    return 0;
}

第五章 继承和派生

一、继承和派生
1、定义
在C++中,比如有两个类,新类拥有原有类的全部属性叫做继承,原有类产生新类的过程叫做派生。
将原有类称之为父类或基类,由基类派生的类叫作子类或派生类。
为何采用派生和继承机制?

  1. 体现面向对象的编程思想,更好的表达各类型之间的关系。
    2.派生类除了可以继承基类的全部信息外,还可以添加自己的那些不同的、有差异的信息,就像生物进化的道理一样,派生类在拥有基类的全部基础之上还将更强大。
    3.派生类继承到基类的成员是自动、隐藏的拥有,即不需要我们重新定义,这就节省了大量的代码,体现了代码重用的软件工程思想。

eg:

#include<iostream>
using namespace std;
class Clock
{
private:
    int H;
    int M;
    int S;
public:
    int SetTime(int h,int m,int s)
    {
        this->H = h;
        this->M = m;
        this->S = s;
        return 0;
    }
    int ShowTime()
    {
        cout<<"Now:"<<H<<":"<<M<<":"<<S<<endl;
        return 0;
    }
 
};
 
class AlarmClock:public Clock
{
private:
    int AH;
    int AM;
public:
    int SetAlarm(int AH,int AM)
    {
        this->AH = AH;
        this->AM = AM;
        return 0;
    }
    int ShowAlarm()
    {
        cout<<"AlarmTime:"<<AH<<":"<<AM<<endl;
        return 0;
    }
};
 
 
int main()
{
    AlarmClock A;
    A.SetTime(19,15,50);
    A.ShowTime();
    A.SetAlarm(5,30);
    A.ShowAlarm();
    return 0;
}

2、三种继承方式
(1)公有继承

  1. 基类中的公有成员,在派生类中仍然为公有成员。当然无论派生里的成员函数还是派生类对象都可以访问。

  2. 基类中的私有成员,无论在派生类的成员还是派生类对象都不可以访问。

  3. 基类中的保护成员,在派生类中仍然是保护类型,可以通过派生类的成员函数访问,但派生类对象不可以访问!
    (2)私有继承

  4. 基类的公有和受保护类型,被派生类私有继承吸收后,都变为派生类的私有类型,即在类的成员函数里可以访问,不能在类外访问。

  5. 而基类的私有成员,在派生类无论类内还是类外都不可以访问。

可以看出来,如果为私有派生,则基类的私有成员在派生类甚至再派生出的子类中,都无法再使用。没有什么存在意义,故这种使用情况比较少。
(3)保护继承

  1. 基类的公有成员和保护类型成员在派生类中为保护成员。

  2. 基类的私有成员在派生类中不能被直接访问。
    三、派生类的构造函数
    (1)无参构造函数
    在构建一个派生类的对象时,系统会先创建一个基类。
    需要注意的是,派生类会吸纳基类的全部成员,但并不包括构造函数及后面讲的析构函数,那么就意味着创建派生类在调用自己的构造函数之前,会先调用基类的构造函数
    eg:

#include<iostream>
using namespace std;
class Clock
{
private:
    int H;
    int M;
    int S;
public:
    Clock()
    {
        cout<<"Clock's Constructor Called!"<<endl;
    }
  
};
  
class AlarmClock:public Clock
{
private:
    int AH;
    int AM;
public:
    AlarmClock()
    {
        cout<<"AlarmClock's Constructor Called!"<<endl;
    }
  
};
  
int main()
{
    AlarmClock A;
  
    return 0;
}

在这里插入图片描述

定义了一个派生类对象,派生类和基类的构造函数会自动调用,调用顺序是先调用基类的构造函数再调用的派生类构造函数。
(2)有参构造函数

#include<iostream>
using namespace std;
class Clock
{
private:
    int H;
    int M;
    int S;
public:
    Clock()
    {
        cout<<"Clock's Constructor Called!"<<endl;
    }
    Clock(int h,int m,int s)
    {
        this->H = h;
        this->M = m;
        this->S = s;
        cout<<"Clock's Constructor  with  parameter Called!"<<endl;
    }
  
};
class AlarmClock:public Clock
{
private:
    int AH;
    int AM;
public:
    AlarmClock()
    {
        cout<<"AlarmClock's Constructor Called!"<<endl;
    }
    AlarmClock(int h,int m,int s):Clock(h,m,s)
    {
        cout<<"AlarmClock's Constructor  with  parameter Called!"<<endl;
    }
};
int main()
{
    AlarmClock A(8,10,30);
    AlarmClock B;
    return 0;
}

四、派生类的析构函数
析构函数的调用顺序与构造函数完全相反 。

#include<iostream>
using namespace std;
class Clock
{
private:
    int H;
    int M;
    int S;
public:
    Clock()
    {
        cout<<"Clock's Constructor Called!"<<endl;
    }
    ~Clock()
    {
        cout<<"Clock's Destructor Called!"<<endl;
    }
  
};
  
class AlarmClock:public Clock
{
private:
    int AH;
    int AM;
public:
    AlarmClock()
    {
        cout<<"AlarmClock's Constructor Called!"<<endl;
    }
    ~AlarmClock()
    {
        cout<<"AlarmClock's Destructor Called!"<<endl;
    }
  
};
  
int main()
{
    AlarmClock A;
  
    return 0;
}

在这里插入图片描述

构造函数调用顺序:基类 -> 派生类
析构函数调用顺序:派生类 -> 基类

2、虚基类

#include <iostream>
using namespace std;
class Grandfather
{
public:
    int key;
public:
  
};
class Father1:public Grandfather
{
  
};
class Father2:public Grandfather
{
  
};
class Grandson:public Father1,public Father2
{
  
};
  
int main()
{
  
    Grandson A;
    A.key=9;//产生二义性
    return 0;
}

在这里插入图片描述
故定义方法在两个father类增加virtual声明:

class Father1:virtual public Grandfather
class Father2:virtual public Grandfather
这样派生类和基类只维护一份一个基类对象,避免多次拷贝,出现歧义。

第六章 多态性

1、多态性概述
(1)定义:
在面向对象程序设计中,指同样的方法被不同的对象执行时会有不同的执行效果。
(2)分类:
编译时多态:在编译和连接时确认的,叫做静态联编,例如函数重载,函数模板的实例化。
运行时多态:在运行的时候,才能确认执行哪段代码的,叫做动态联编,这种情况是编译的时候,还无法确认具体走哪段代码,而是程序运行起来之后才能确认。
(3)静态联编实例:

#include <iostream>
using namespace std;
#define PI 3.1415926
  
class Point
{
private:
    int x,y;
  
public:
    Point(int x=0,int y=0)
    {
        this->x = x;
        this->y = y;
    }
    double area()
    {
        return 0.0;
    }
};
class Circle:public Point
{
private:
    int r;
public:
    Circle(int x,int y,int R):Point(x,y)
    {
        r = R;
    }
    double area()
    {
        return PI*r*r;
    }
};
  
int main()
{
  
    Point A(10,10);
    cout<<A.area()<<endl;
    Circle B(10,10,20);
    cout<<B.area()<<endl;
    Point *p;
    p = &B;
    cout<<p->area()<<endl;
    Point &pp=B;
    cout<<pp.area()<<endl;
    return 0;
  
}

运行结果:
在这里插入图片描述
第一个cout输出A的面积,是Point类中的area方法,面积为0,没有问题。

第二个cout输出B的面积,很明显是派生类Circle的area方法,面积自然按公式计算得出1256.64的值,也没问题。

第三个cout输出的是Point类型指针p指向的Circle类对象的area方法,它输出了0很明显是执行了Point类里的area方法。这里C++实行的是静态联编,即在编译的时候就依据p的类型来定执行哪个area,因此是0

第四种cout也同理,把Circle类型的对象赋给Point类型的引用,C++同样实行静态联编,也输出0。
因此需要进行动态联编。
2、虚函数
虚函数允许函数在调用时与函数体的联系在运行的时候才建立,即所谓的动态联编。
一般形式:
virtual 函数返回值 函数名(形参)

{

 函数体

}

只需要把基类中的area方法声明为虚函数,那么主函数中无论Point类型的指针还是引用就都可以大胆调用,无用关心类型问题了。因为他们会依据实际指向的对象类型来决定调用谁的方法,来实现动态联编。

#include <iostream>
using namespace std;
#define PI 3.1415926
  
class Point
{
private:
    int x,y;
  
public:
    Point(int x=0,int y=0)
    {
        this->x = x;
        this->y = y;
    }
   virtual double area()//虚函数
    {
        return 0.0;
    }
};
class Circle:public Point
{
private:
    int r;
public:
    Circle(int x,int y,int R):Point(x,y)
    {
        r = R;
    }
    double area()
    {
        return PI*r*r;
    }
};
  
int main()
{
  
    Point A(10,10);
    cout<<A.area()<<endl;
    Circle B(10,10,20);
    cout<<B.area()<<endl;
    Point *p;
    p = &B;
    cout<<p->area()<<endl;
    Point &pp=B;
    cout<<pp.area()<<endl;
    return 0;
  
}

运行结果:
在这里插入图片描述
需要注意的是:

  1. 虚函数不能是静态成员函数,或友元函数,因为它们不属于某个对象。

  2. 内联函数不能在运行中动态确定其位置,即使虚函数在类的内部定义,编译时,仍将看作非内联,

  3. 构造函数不能是虚函数,析构函数可以是虚函数,而且通常声明为虚函数。

3、虚析构函数
析构函数却是可以为虚函数的,且大多时候都声明为虚析构函数。这样就可以在用基类的指针指向派生类的对象在释放时,可以根据实际所指向的对象类型动态联编调用子类的析构函数,实现正确的对象内存释放。
eg:

#include <iostream>
using namespace std;
class Point
{
private:
    int x,y;
    int *str;
  
public:
    Point(int x=0,int y=0)
    {
        this->x = x;
        this->y = y;
        str = new int[100];
    }
    ~Point()
    {
        delete []str;
        cout<<"Called Point's Destructor and Deleted str!"<<endl;
    }
  
  
};
class Circle:public Point
{
private:
    int r;
    int *str;
public:
    Circle(int x,int y,int R):Point(x,y)
    {
        r = R;
        str = new int[100];
    }
    ~Circle()
    {
        delete []str;
        cout<<"Called Circle's Destructor and Deleted str!"<<endl;
    }
  
};
  
  
  
int main()
{
  
    Point *p;
    p = new Circle(10,10,20);
    delete p;
  
    return 0;
  
}

运行结果:
在这里插入图片描述
基类中没有用virtual声明的析构函数,且基类和派生类当中都有动态内存开辟,那么我们在主函数中也动态开辟内存的方式创建一个Circle类,然后删除,这样会造成仅调用基类的析构函数,造成内存泄漏。
解决方法:
把基类中析构函数声明为virtual,则结果大有不同!这个时候多态效应出现,会先调用释放派生类的空间,然后再释放基类的内存空间,完美结束。
eg:

#include <iostream>
using namespace std;
class Point
{
private:
    int x,y;
    int *str;
  
public:
    Point(int x=0,int y=0)
    {
        this->x = x;
        this->y = y;
        str = new int[100];
    }
    ~Point()
    {
        delete []str;
        cout<<"Called Point's Destructor and Deleted str!"<<endl;
    }
  
  
};
class Circle:public Point
{
private:
    int r;
    int *str;
public:
    Circle(int x,int y,int R):Point(x,y)
    {
        r = R;
        str = new int[100];
    }
    ~Circle()
    {
        delete []str;
        cout<<"Called Circle's Destructor and Deleted str!"<<endl;
    }
  
};
  
  
  
int main()
{
  
    Point *p;
    p = new Circle(10,10,20);
    delete p;
  
    return 0;
  
}

4、纯虚函数和抽象类
(1)纯虚函数
指没有函数体的虚函数。即:
virtual 返回值 函数名(形参)=0;
(2)抽象类
包含纯虚函数的类就是抽象类,一个抽象类至少有一个纯虚函数。

抽象类的存在是为了提供一个高度抽象、对外统一的接口,然后通过对台的特性使用各自不同的方法,是C++面向对象设计和工程的核心思想之一。

抽象类的特点总结如下:

  1. 抽象类无法实例出一个对象来,只能作为基类让派生类完善其中的纯虚函数,然后再实例化使用。

  2. 抽象类的派生来依然可以不完善基类中的纯虚函数,继续作为抽象类被派生。直到给出所有纯虚函数的定义,则成为一个具体类,才可以实例化对象。

  3. 抽象类因为抽象、无法具化,所以不能作为参数类型、返回值、强转类型

  4. 接着第三条,但抽象类可以定义一个指针、引用类型,指向其派生类,来实现多态特性。

第八章 文件操作

1、C++读写文件操作
ofstream: 写操作(输出)的文件类 (由ostream引申而来)

ifstream: 读操作(输入)的文件类(由istream引申而来)

fstream: 可同时读写操作的文件类 (由iostream引申而来)

需要包含头文件:

#include

2、打开文件
需要用到在fstream类中的成员函数open()实现打开文件的操作,open函数是istream、ostream、fstream的成员函数,它的原型如下:

void open(const char *filename, ios::openmode mode);
第一个参数filename表示要打开的文件路径
第二个参数mode表示打开文件的模式,如下:
在这里插入图片描述

除此以外,还可以用过‘|’或运算符将多个参数进行如何使用
在这里插入图片描述
3、读文件操作
eg:

//在devC++运行
#include <fstream>
#include <iostream>
using namespace std;
int main ()
{
 
   char data[100];
   // 以写模式打开文件
   ifstream file;
    file.open("d:\\test.txt");
    file >> data;
    cout<<data;
   // 关闭打开的文件
   file.close();
   return 0;}

注意:
对于C/C++而言,它可以打开读写的文件并非只能是txt文件,比如样例代码中是dat文件,事实上任何后缀文件都可以打开、读写、关闭操作。
4、写文件操作
eg:

#include <fstream>
#include <iostream>
using namespace std;
int main ()
{
    char data[100]="Welcome to dotcpp";
    ofstream file;
    file.open("d:\\dotcpp.dat");
    file << data;
    file.close();//关闭文件
    return 0;
}

注意:对于即便D盘下不存在dotcpp.dat这个文件的情况下,运行此程序仍然会自动新建并向其中写入data的数据。如果dotcpp.dat存在且存在内容,则在写文件时会删除原来内容再写入。
5、关闭文件操作
在结束相关操作之后,只需要在最后调用close成员函数即可,即会断开文件与程序的关联,结束操作。
eg:

#include <fstream>
#include <iostream>
using namespace std;
int main ()
{
    char data[100]="Welcome to dotcpp";
    ofstream file;
    file.open("d:\\dotcpp.dat");
    file << data;
    file.close();   //调用close函数,关闭文件操作
    return 0;}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值