C++(三) 类与对象:封装 构造 继承

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

具有相同性质的对象,我们可以抽象称为类,比如人属于人类,车属于车类

类的介绍

类的三大特性:封装 继承 多态

类是在结构体的基础上进化而来

类由成员变量(属于对象),成员方法(属于类)构成

创建对象时开辟的空间是用来存储成员变量的。当类是空类的,类的对象开辟的空间是1字节大小,但这一字节空间在类不是空类时,也会被利用

成员方法是属于类的,不属于对象。所有对象调用的方法,都是同一类方法

类和封装

引入

从C语言的学习中,我们了解到,当一个结构体作为参数传入一个函数时,系统会复制一个结构体的副本作为参数传入函数

此时我们定义一个结构体和一个函数,并将一个结构体作为参数传入该函数

struct Base
{
    int x;
    int y;
    int z;
};
void Function(Base b)
{
}
int main()
{
    Base a;
    Function(a);
    return 0;
}

程序编译后,我们进入反汇编进行观察

发现,结构体确实被复制了一个副本传入了函数。但是这样的操作导致程序运行会很慢。因此我们通常需要把结构体的指针作为参数传入函数

现我们将上述程序做以下调整,以下这个程序才是我们正常写代码应该的样子

struct Base
{
    int x;
    int y;
};
int Max(Base* pb)
{
    if(pb->x > pb->y)
    {
        return pb->x;
    }
    else
    {
        retrun pb->y;
    }
}
int main()
{
    Base base;
    base.x = 1;
    base.y = 2;//注意:直接用结构体指向成员时用.,用结构体指针指向成员时用->
    int x = Max(&base);
    printf("%x\n",x);
}

此时类对象内存大小是8字节

此时我们再将结构体作为参数传入该函数时,是将结构体的指针传入

在上程序中,函数是在结构体外面的,如果我们把函数写到结构体里面,结构体的大小是不会因此改变的。

现在我们将函数放到结构体中

struct Base
{
    int x;
    int y;
    int Max(Base* pb)
    {
        if(pb->x > pb->y)
        {
            return pb->x;
        }
        else
        {
            retrun pb->y;
        }
    }
};
int main()
{
    Base base;
    base.x = 1;
    base.y = 2;//注意:直接用结构体指向成员时用.,用结构体指针指向成员时用->
    int x = base.Max(&base);
    printf("%x\n",x);
}

此时函数放入类中,类对象内存大小仍然是8字节,因此我们可以得出以下结论:

在C++中,类内的成员变量和成员函数分开存储。只有非静态成员变量才属于类的对象上,占有对象空间,静态成员变量不占对象空间,所有类型一个函数实例函数也不占对象空间,共享

函数放在结构体里面和外面是两种不同的形式,具体有以下区别

1.函数对于结构体的大小没有任何的影响。函数地址在编译以后已经确定,不在结构体中。所有类对象共用一个函数

2.对于编译器:当函数放在结构体中时,使用该函数时必须声明该函数属于哪个结构体。而函数在结构体外面时,可以直接使用

3.当函数在结构体中时,使用该函数时,编译器会在函数传参时多传一个参数,即结构体指针。现我们进到反汇编进行查看

发现在第三行,多传了一个参数到函数中。

现我们针对第三点区别,做一个程序解析

struct Base
{
    int x;
    int y;
    void Max()
    {
    }
};
int main()
{
    Base base;
    base.x = 1;
    base.y = 2;//注意:直接用结构体指向成员时用.,用结构体指针指向成员时用->
    base.Max();
    printf("%x\n",x);
}

我们进入反汇编

发现即使这个函数在使用的时候不需要传入任何参数,但编译器仍然会传入一个参数即结构体指针到函数中

总结:当我们把函数放入结构体中时,使用该函数时,编译器会自行传递给函数一个结构体首地址的参数

由此引出:

1.所谓类其实就是结构体的套壳,带了函数的结构体罢了。结构体类型,简单点就是说类

2.封装就是把函数放在结构体里面了,使函数和结构体成员成为一个整体。在使用这个函数的时候,编译器会自行传入结构体首地址,这样使用结构体中的变量比较方便。这同时也就是封装的意义

3.类和结构体唯一的区别就在于两者的默认访问权限不同结构体默认权限为公共,类默认权限为私有

this指针

我们在上文所提到的,编译器往一个结构体中传入的一个参数其实就是叫作this指针的东西,this指针是隐含每一个非静态成员函数内的一种指向被调用的成员函数所属的对象首地址的指针。this指针本质是一个指针常量,但不需要我们自行定义,反而可以直接使用,这都是因为这是编译器为我们做好的事情了

现在让我们了解如何使用this指针:

struct Base
{
    int x;
    int y;
    void init(int x, int y)
    {
        x = x;
        y = y;
    }
};
int main()
{
    Base base;
    base.init(1, 2);
    int x = base.x;
    return 0;
}

在上述的程序中,我们运行以后可以发现,x并没有成功赋值。这是因为在init函数中,出现了两个x和两个y,编译器无法正确识别这些x和y究竟是结构体的x和y还是函数传参的x和y。

这时候this指针就派上用场了

struct Base
{
    int x;
    int y;
    void init(int x, int y)
    {
        this->x = x;
        this->y = y;
    }
};
int main()
{
    Base base;
    base.init(1, 2);
    int x = base.x;
    return 0;
}

此时再次运行程序,x就成功被赋值了。这是因为在init函数中x加上this指针就告诉了编译器这个x是结构体自己的x,不是函数传参的x了。因此this指针的一个用处就是用来区分同名的形参和成员变量

this指针和普通指针的区别:this指针不可以直接进行++--等等操作,this指针的指向不可修改。如果this指针重新赋值的话,this指针就不再指向它本应指向的结构体的首地址了,这时候我们在使用结构体的函数时,就会发生错误。因此this指针的另一个用处就是在类的非静态成员函数中返回对象本身,即return *this

构造函数

我们将以一个程序的不断进化进行讲解

struct Person
{		
	int age;	
	int level;	
}			
int main(int argc, char* argv[])
{		
	Person p;
    p.age = 10;
    p.level = 20;
	return 0;	
}

在上述的程序中,我们创建了一个结构体,并在main函数中对其进行了声明和定义。在这个程序中,我们似乎觉得每次声明一个结构体后,在对其成员一一赋值是很麻烦的一件事。因此我们接下来在这个结构体中添加一个函数,使其可以让我们方便对成员赋值

struct Person
{		
	int age;	
	int level;	
    Person(int age, int level) //构造函数
    {
        this.age = age;
        this.level = level;
    }
    void Print() //成员函数
    {
        printf("%d %d",age, level);
    }
}			
int main(int argc, char* argv[])
{		
	Person p(1, 2);
    p.print();
	return 0;	
}

通过Print()函数进行了正确的打印,我们发现结构体的成员被进行了赋值,程序正常运行。

仔细观察这个程序,我们清晰的发现,Person()函数没有返回值,没有返回类型,而且函数名和结构体名一模一样,并且这个函数在调用的时候是在结构体对象创建时调用的。我们把这个函数叫做构造函数,构造函数在创建对象时使用,其作用是在创建对象时,编译器自动调用该函数为对象的成员属性赋值进行初始化。

在上述程序中,Print()函数也就是正常加到结构体中的函数,我们称之为成员函数。

成员函数和构造函数区别:构造函数在创建对象时调用,而成员函数需要在我们创建完对象以后在进行调用

注意:我们在结构体中定义了构造函数以后,在创建对象时,必须使用构造函数

当我们定义了构造函数以后,每次创建对象都必须调用构造函数,这和我们之前之间创建对象的习惯是不一样的,并且有时候我们并不是必须要使用构造函数。为了解决这种麻烦,我们可以在结构体中再次定义一个没有参数的构造函数。这样在我们创建对象时,可以自行选择这两个构造函数的其中一个进行应用。

struct Person
{		
	int age;	
	int level;	
    Person(int age, int level) //构造函数
    {
        this.age = age;
        this.level = level;
    }
    Person()
    {
    }
    void Print() //成员函数
    {
        printf("%d %d",age, level);
    }
}			
int main(int argc, char* argv[])
{		
	Person p;
    p.print();
	return 0;	
}

如上进行程序的改写后,依然可以正常运行。

这样修改程序的方法叫做函数重载。函数重载可以适用于任何函数,注意进行重载的函数的名字和原函数名字一样,重载的函数的参数不能和之前的函数参数个数一样(相同个数,不同参数类型除外),否则编译器调用函数时分不清楚在到底是哪个函数

构造函数使用的另外一种方法:如果创建结构体对象是匿名对象时,直接Person(10, 5);代码执行完毕立刻析构

构造函数特点:

1.与类同名

2.没有返回值

3.创建对象的时候执行

4.主要用于初始化对象

5.一个类中可以有多个构造函数,多个构造函数之间构成重载关系。编译器不要求类中必须有构造函数,如果自己没有定义构造函数,编译器会默认使用一个空的构造函数

6.子类创建对象时,编译器会先调用其父类的构造函数(如果继承了多层父类,则会最先调用最老的祖宗的构造函数),再调用子类的构造函数

析构函数

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

我们仍将以一个程序的不断进化进行讲解

struct Person
{		
	int age;	
	int level;
    char* arr;//在栈中分配空间,结构体对象声明周期结束后,自动释放内存空间
    Person(int age, int level) //构造函数
    {
        this.age = age;
        this.level = level;
        arr = (char*)malloc(1024); //在堆中为数组arr分配1024字节空间,结构体对象声明周期结束后,不会自动释放内存空间。我们需要手动释放这份空间
    }
    void Print() //成员函数
    {
        printf("%d %d",age, level);
        free(arr);此处为我们手动释放了arr内存空间
    }
    void Print(int x) //成员函数
    {
        printf("%d %d",age, level);
        free(arr);此处为我们手动释放了arr内存空间
    }
}			
int main(int argc, char* argv[])
{		
	Person p(5, 10);
    p.print();
    p.print(5);
	return 0;	
}

在上述的程序中,我们在构造函数中为arr在堆中申请了内存。由于堆内存不会自动释放,因此我们定义了两个Print()函数,它们都有释放我们在构造函数中申请的arr内存空间的功能。但如果我们同时使用这两个Print()函数时,由于 arr内存空间被前一个Print()函数释放了,就会发生错误。因此我们发现,如果我们在成员函数中去释放堆内存,这似乎不太可行。只有在当结构体对象释放以后,这个堆内存再释放,也是合理的。

因此我们再次修改程序

struct Person
{		
	int age;	
	int level;
    char* arr;//在栈中分配空间,结构体对象声明周期结束后,自动释放内存空间
    Person(int age, int level) //构造函数
    {
        this.age = age;
        this.level = level;
        arr = (char*)malloc(1024); //在堆中为数组arr分配1024字节空间,结构体对象声明周期结束后,不会自动释放内存空间。我们需要手动释放这份空间
    }
    void Print() //成员函数
    {
        printf("%d %d",age, level);
    }
    ~Peoson()
    {
        free(arr);
    }

}			
int main(int argc, char* argv[])
{		
	Person p(5, 10);
    p.print();
	return 0;	
}

此时再次运行程序,我们发现arr内存空间释放了。

注意:析构函数不可以重载

析构函数的特点:

1.一个类只能有一个析构函数,不能重载。

2.不能带任何参数和返回值

3.主要用于清理工作

4.编译器不要求必须提供,如果没有提供,编译器会自动提供一个空的析构函数

继承

继承的本质就是数据的复制

我们仍然以一段代码的不断优化展开讲解

struct Student
{
	int age;
	int sex;
	int code;
	int score;
};	 			
struct Teacher
{
	int age;
	int sex;
	int level;
	int classId;
};		
void Test()
{
    Teacher tea;
	tea.age = 1;
	tea.sex = 2;
	tea.level= 3;
	tea.classId = 4;
}
int main(int argc, char* argv[])
{		
    Test();
	return 0;	
}

在上述程序中,我们分别定义了Person,Student,Teacher三个结构体,并写入了一个Test()函数用来给结构体赋值

现我们观察Test()函数的反汇编

现在我们将Tset()函数进行修改如下

void Test()
{  	
	Student stu;
	stu.age = 1;
	stu.sex = 2;
	stu.code = 3;
	stu.score = 4;
}

我们再次观察该函数的反汇编

我们可以看到,这两个Test()函数虽然是对不同的结构体赋值,但它的反汇编,其实没什么区别。

由此可以发现,如果我们要定义很多类似的结构体,而这些结构体的某些成员它们都是一样的。如果我们对这些结构体进行定义的话,每个结构体相同的成员都要重新定义就会造成代码的冗余。因此为了解决这个问题,C++引入了继承。

现我们应用继承去修改我们的程序

struct Person
{	
	int age;
	int sex;
};
struct Teacher:Person//Teacher类继承Person类
{

	int level;
	int classId;
};	
struct Student:Person//Student类继承Person类
{  
	int code;
	int score;
};	 			
void Test()//测试函数
{  	
	Student stu;
	stu.age = 1;
	stu.sex = 2;
	stu.code = 3;
	stu.score = 4;

}			
int main(int argc, char* argv[])
{		
	Test();
	return 0;	
}

在上述程序中,我们把Student和Teacher结构体中相同的成员单独拿出来构造了一个新的结构体Person,并在Student和Teacher结构体定义后加上了:Person,意味着它们继承了Person结构体。此时在测试函数中,我们可以使用Student中未定义的成员而在Person中定义的成员

此时我们去查看Test()函数反汇编

我们发现,这个Test()函数和上面未使用继承的Test()反汇编是一样的,并没有改变。因此我们发现,继承后的结构体和没继承的原本的结构体其实都是一个东西

在上面的代码中,我们可以得出以下结论:

1.Person称为父类或基类,Student称为子类或派生类

2.tea和stu成为对象或实例

3.可以用父类的指针指向子类的对象,可以访问子类中父类的成员,但不能访问子类自己的成员 

现我们解析结论三,修改上述代码如下,使Test()函数中,子类指针指向子类对象

struct Person
{	
	int age;
	int sex;
};
struct Student:Person //Student类继承Person类
{  
	int code;
	int score;
};	 			
void Test()
{	
	Student stu;  //创建Student类的对象
	Student* p = &stu;  //定义Student类的指针,指向stu对象首地址
	printf("%d %d\n",p->age,p->code);  //可以用类指针访问成员变量
}			
int main(int argc, char* argv[])
{		
	Test();
	return 0;	
}

上述代码可正常运行,现我们将Test()函数中,使父类指针指向子类对象

void Test()
{	
	Student stu;  //创建Student类的对象
	Person* p = &stu;  //父类指针指向子类的对象
	printf("%d %d\n",p->age,p->sex);  //1 2
}		

此时程序仍然可以正常运行。

接下来我们以图示进行解析,下图是父类Person和子类Student的构成图。子类Student实际上由父类的成员和自己的成员共同组成

现我们将父类Person指针指向子类Student。这时,父类Person指针指向了子类Student的父类成员起始位置,可以访问父类成员,但他不能访问子类的成员。这是因为父类指针能够指向的对象范围比子类对象大小要小,此时访问会越界。具体图示如下:

由此我们我们得出结论:父类指针可以指向子类对象

既然父类指针可以指向子类对象,那我们现研究子类指针能否指向父类对象。我们再次修改Test函数:

void Test()
{	
	Person person;  //创建Student类的对象
	Student* p = &person;  //子类指针指向父类的对象
	printf("%d %d %d %d\n",p->age,p->sex,p->code,p->score); 
}

我们发现,这个函数无法正常运行,因此子类指针无法指向父类对象

但是在特殊情况下,我们将父类对象强制转换成子类对象,这时,子类指针可以指向父类对象。具体代码如下

void Test()
{	
	Person person;  //创建Student类的对象
	Student* p = (Student*)&person;  //子类指针指向父类的对象
	printf("%d %d %d %d\n",p->age,p->sex,p->code,p->score); //1 2 未知数据 未知数据
}			

在这种情况下,程序可以正常运行,但我们在实践中,可能会出现一些错误,因此不建议这样使用。

多重继承

我们以一段代码的不断优化,去讲解该知识点

现有如下代码,有三个结构体X,Y,Z,它们依次继承,并有Test()函数去创建Z的对象z,并进行赋值以及打印z的大小

struct X
{	
	int a;
    int b;
};	
struct Y:X
{	
	int c;
    int d;
};	
struct Z:Y
{	
	int e;
    int f;
};	 			
void Test()
{	
	Z z;
	z.a = 1;
	z.b = 2;
	z.c = 3;
    z.d = 4;
    z.e = 5;
    z.f = 6;
	printf("%d\n",sizeof(z)); 
}			
int main(int argc, char* argv[])
{		
	Test();
	return 0;	
}

程序运行以后,我们发现z的大小是24字节

进入Test()函数反汇编中,我们依然可以看到z被正常赋值了

这是因为Z类的构成由X和Y和Z共同构造

现修改代码如下:该代码使类Y中有了一个和类X同名的成员

struct X
{	
	int a;
    int b;
};	
struct Y:X
{	
	int a;
    int d;
};	
struct Z:Y
{	
	int e;
    int f;
};	 			
void Test()
{	
	Z z;
	printf("%d\n",sizeof(z)); 
}			
int main(int argc, char* argv[])
{		
	Test();
	return 0;	
}

程序运行后,z的大小依然是24。因此即使一个类多重继承了其他类,其他类有重复名字的成员,这个类依然把它们全部继承下去了。但是当这个类去使用重复名字的成员时,必须声明是继承的哪个类的成员。操作如下

struct X
{	
	int a;
    int b;
};	
struct Y:X
{	
	int a;
    int d;
};	
struct Z:Y
{	
	int e;
    int f;
};	 			
void Test()
{	
	Z z;
    z.X::a = 1;
    z.b = 2
    z.Y::a = 3;
    z.d = 4;
    z.e = 5;
    z.f = 6;
	printf("%d\n",sizeof(z)); 
}			
int main(int argc, char* argv[])
{		
	Test();
	return 0;	
}

我们进入test()函数中查看反汇编

我们发现,此时的函数反汇编和之前的函数的反汇编没有一点区别

但正常情况下,我们不建议一个类继承一个类,然后另一个类继承这个类来实现多重继承,我们建议一个类直接继承另外的几个类。代码如下

struct X
{	
	int a;
    int b;
};	
struct Y
{	
	int a;
    int d;
};	
struct Z:X,Y
{	
	int e;
    int f;
};	 			
void Test()
{	
	Z z;
    z.X::a = 1;
    z.b = 2
    z.Y::a = 3;
    z.d = 4;
    z.e = 5;
    z.f = 6;
	printf("%d\n",sizeof(z)); 
}			
int main(int argc, char* argv[])
{		
	Test();
	return 0;	
}

注意:如果我们使用类中重名的成员时,如果不声明类的重名成员中谁是谁是的成员的话,编译器会将这些重名的成员都默认为重名成员中的某一个进行使用

程序书写优化

当一个结构体中的函数过多时,会造成代码的冗杂与不美观,为了解决这个问题,我们可以把函数在结构体中只进行一个声明和实现放在两个不同的文件中,这样代码就会看起来美观简洁。具体操作如下:

1.我们创建一个.h头文件,在该文件中定义结构体以及声明成员函数。现我们命名该头文件为MyClass.h

注意:.h头文件中的后缀.h其实没什么含义,换个其他后缀也都可以,比如.exe。

struct Test
{
	int x;
	int y;
    int z;
    void Init(int x, int y, int z);
    void Function();
};

2.我们创建.cpp文件,在该文件中实现成员函数.现我们命名该cpp文件为MyClass.cpp

void Test::Init(int x, int y, int z)//::表示该函数属于的范围,此函数属于Test结构体
{
}
void Test::Function()
{
}

3.此时我们在我们编写程序的.cpp中包含我们创建的头文件后,正常使用这些函数

#include<MyClass.h>
void Test1()
{
    Test t;
    t.Function;
}
int main(int argc, char* argv[])
{
    Test();
    return 0;
}

继承的三种方式

public权限:表示一个成员(属性、方法等)哪里都可以用。所以当我们对外提供一些成员时,将这些成员设置为public权限。当子类以public权限继承父类的成员属性时,子类继承的成员属性权限和父类的原本的成员属性权限相同

protect权限:私有成员变量或函数在类的外部是不可访问的,只有本类和子类以及和友元函数可以问访问保护成员。当子类以protect权限继承父类的成员属性时,父类private属性成员,子类不可被继承,其余继承的父类成员权限都是protect权限

private权限:表示一个成员只用于类内部使用,即只有本类和友元函数才可以访问private的成员。当我们不对外提供一些成员时,就将这些成员设置为private权限。当子类以private权限继承父类的成员属性时,父类private属性成员,子类不可被继承,其余继承的父类成员权限都是private权限

注意:无论是哪一种权限设置的成员变量,在结构体中都是占有空间大小的

由于protect权限基本用不到,因此此处不做讲解,需要使用时再另找资料学习便可

现我们深入研究public权限和private权限:

以下是这两个权限的应用代码

struct Test2
{
private:
    int x;
public:
    int y;
    void Print()
    {
        printf("%d %d",x,y);
    }
}
void TestMethod()
{
    Test2 t;
    t.x = 10;
    t.y = 5; 
}
int main(int argc, char* argv[])
{
    TestMethod();
    Test2 test;
    test.Print;
    return 0;
}

上述程序并不能运行。

TestMethod函数是外部函数,该函数对t.x赋值时,由于Test2结构体中,x为private权限无法访问,这时该函数就无法正常运行

但是test.Print()函数可以正常运行,因为该函数是Test内部函数

private访问

为了保证一个类的成员属性是一个合法且合理的状态(比如下面的代码,将Person的age设置为200就是不合理的),我们一般将成员属性设置为private,使其不能直接的去访问,程序更加的安全合理。

方法一:函数访问

设置了private的成员,我们不可以直接访问,但可以通过函数去访问。代码演示如下:

struct Person
{
private:   //私有成员变量
	int age;
public:  //共有成员方法
	void SetAge(int age)
    {
		if(age > 0 && age <= 100)
        {
			this->age = age;  //private成员变量只有类中的其他成员可以访问
		}
        else //这是当age赋值不合理的时候的情况
        {
            this->age = 0;
        }
	}
	int GetAge()
    {
		return age;
	}
};
int main(int argc, char* argv[])
{		
	Person p;
	p.SetAge(18);
	printf("%d\n",p.GetAge());
	return 0;	
}

这个代码结合private的属性,便可以轻松理解

方法二:内存访问

下面我们以代码进行演示

struct Person{
private:
	int x;
	int y;
public:
	int z;
	void Init(int x,int y,int z)
    {
		this->x = x;
		this->y = y;
		this->z = z;
	}
};
int main(int argc, char* argv[])
{		
	Person p;
	p.Init(1,2,3);    
	int* pt = (int*)&p;  //定义一个int*指针,访问x,y,z的值即可
	printf("%d %d %d\n",*pt,*(pt + 1),*(pt + 2));  //1 2 3
	return 0;	
}

在上述代码中,我们将指针指向结构体,之后便可以正常去访问结构体的内存了,这也意味着我们可以访问到了private权限的成员。

struct和class

从此知识点开始,我们将正式使用类

虽然从广义上来说类和结构体是一个东西,但它们在狭义上是有一定的区别的

区别一:编译器默认class中的成员为private,而struct中的成员为public

区别二:如果一个子类继承一个父类时,没有声明以什么方式继承,默认以private的方式继承。

现我们开始详解区别二

我们首先以结构体来演示区别二:

struct Base
{
    int x;
    int y;
}
struct Sub:Base
{
    int a;
    int b;
}
void TestMethod()
{
    Sub sub;
    sub.x = 10;
    sub.a = 20;
}
int main(int argc, char* argv())
{
    TestMethod();
    retrun 0; 
}

该程序运行没有任何问题

我们首先以类来演示区别二:

class Base
{
    int x;
    int y;
}
class Sub:Base
{
    int a;
    int b;
}
void TestMethod()
{
    Sub sub;
    sub.x = 10;
    sub.a = 20;
}
int main(int argc, char* argv())
{
    TestMethod();
    retrun 0; 
}

该程序无法正常运行,函数无法正常为类成员赋值,这正是因为类成员默认是private权限的原因

现在我们将以上两个类的成员权限改为public

class Base
{
public:
    int x;
    int y;
}
class Sub:Base
{
public:
    int a;
    int b;
}
void TestMethod()
{
    Sub sub;
    sub.x = 10;
    sub.a = 20;
}
int main(int argc, char* argv())
{
    TestMethod();
    retrun 0; 
}

此时运行程序可以发现,在TestMethod函数中,a可以赋值,但x不可以。这是因为在未声明以何种方式使一个类继承另一个类时,默认以private方式继承,即继承过来的东西都是private权限。因此在上程序中,TestMethod函数中,sub.x无法直接赋值。

此时我们在上程序,Sub继承Base的方式进行修改

class Base
{
public:
    int x;
    int y;
}
class Sub:public Base
{
public:
    int a;
    int b;
}
void TestMethod()
{
    Sub sub;
    sub.x = 10;
    sub.a = 20;
}
int main(int argc, char* argv())
{
    TestMethod();
    retrun 0; 
}

此时,程序可以正常运行,函数正常赋值

private继承

class Base
{
private:
	int x;
	int y;
};
class Sub:public Base
{
public:
	int m;
    int n;
};
void TestMethod()
{
    Sub sub;
    printf("%d",sizeof(sub))
}
int main(int argc, char* argv[])
{		
	TestMethod();
	return 0;	
}

在上述程序中,Sub继承了Base,而Base中有Private成员。

通过函数TestMethod()打印了16,由此说明,一个类可以继承另一个类的private成员。

虽然在上述程序中Sub以public的方式继承了Base,但是继承过来的Base的private权限属性我们并不可以访问,哪怕是通过类内部函数也不可以

class Base
{
private:
	int x;
	int y;
};
class Sub:public Base
{
public:
	int m;
    int n;
    void T()
    {
        x = 1;
    }
};
void TestMethod()
{
    Sub sub;
    sub.x = 1;
}
int main(int argc, char* argv[])
{		
	TestMethod();
	return 0;	
}

通过上述程序,我们发现,函数T和函数TestMethod都无法运行,由此得出结论,哪怕是以public方式继承过来的private成员,不管是子类的内部函数还是外部函数都不可以访问。因此private权限的成员,只能在它被定义的类的内部被使用。

但是,我们可以利用指针去访问继承过来的private权限成员,操作如下:

class Base
{
private:
	int x;
	int y;
public:
    Base()
    {
        x = 1;
        y = 2;
    }
};
class Sub:public Base
{
public:
	int m;
    int n;
};
void TestMethod()
{
    Sub sub;
    sub.m = 3;
    sub.n = 4;
    int *p = (int*)&sub;
    printf("%d\n",*(p));
    printf("%d\n",*(p + 1));
    printf("%d\n",*(p + 2));
    printf("%d\n",*(p + 3));

}
int main(int argc, char* argv[])
{		
	TestMethod();
	return 0;	
}

程序正常运行,并正确打印 1 2 3 4。由此可见,指针万能

子类对象的创建和父类的关系

class Base
{
private:
	int x;
	int y;
public:
    Base()
    {
        printf("abc");
    }
};
class Sub:public Base
{
public:
	int m;
    int n;
};
void TestMethod()
{
    Sub sub;
}
int main(int argc, char* argv[])
{		
	TestMethod();
	return 0;	
}

在上述程序中,我们使用TestMethod函数时,创建Sub的对象sub时,打印了abc

由此我们进入TestMethod函数反汇编 

我们并没有定义Sub的构造函数,因此编译器自行构造了一个。我们跟进去这个构造函数

我们发现在这个构造函数内部,调用了Base的构造函数。现我们再跟进去

发现执行了Base的构造函数

因此我们得出结论,当子类对象创建时,程序会先调用父类的构造函数,然后再继续执行自己的构造函数。我们继续程序验证

class Base
{
private:
	int x;
	int y;
public:
    Base()
    {
        printf("Base的构造函数");
    }
};
class Sub:public Base
{
public:
	int m;
    int n;
    Sub()
    {
        printf("Sub的构造函数");
    }
};
void TestMethod()
{
    Sub sub;
}
int main(int argc, char* argv[])
{		
	TestMethod();
	return 0;	
}

此时程序执行,会先打印Base的构造函数,然后再打印Sub的构造函数。正好验证了我们的结论

注意:如果是多重继承,子类在创建对象时,会从开始的父类执行其构造函数,然后依次执行接下来的父类的构造函数,直到子类自己的构造函数执行完毕。

继承与构造

现有如下父类和子类

class Base
{
public:
    int x;
    int y;
    Base(int x, int y)
    {
        this->x = x;
        this->y = y;
    }
}
class Sub1 : public Base
{
private:
    int A;
public:
    Sub1(int A)
    {
        this->A = A;
    }
};

我们发现,程序并不能编译过去,这是因为当子类在使用构造函数的时候,会优先调用父类的默认构造函数。而在上述程序中,父类不存在默认构造函数,因此在子类在调用构造函数时需先调用父类构造函数。为此我们必须在子类的构造函数后显式的调用父类的构造函数。格式为子类构造函数:父类构造函数,以此告诉编译器在调用子类的构造函数时调用了父类的构造函数。

下面我们以以下代码进行演示

#include<stdio.h>
class Base
{
public:
    int x;
    int y;
    Base(int x, int y)
    {
        this->x = x;
        this->y = y;
    }
    void Print()
    {
        printf("%d %d", x, y);
    }
};
class Sub1 : public Base
{
private:
    int A;
public:
    Sub1(int x, int y, int A):Base(x, y)
    {
        this->A = A;
    }
};
int main(int argc, char* argv[]) {
    Sub1 sub(1, 2, 3);
    Sub1* p = &sub;
    return 0;
}

此时,运行程序,进入sub内存,发现其成员变量x,y,A分别是1,2,3。实际上,这就是一个构造函数调用先后的顺序罢了:先调用父类的构造函数,后调用子类构造函数,函数传参父类子类共同使用

内部类

内部类:类里面定义的类就是内部类。内部类和本类不存在隶属关系,其单独存在,不会影响它存在的当前类的大小

如下我们以代码进行讲解

如上图,程序运行以后,我们可以清晰的发现Cobject只有4字节大小。由此我们可以验证,内部类确实不影响原类的大小

内部类是单独存在的,因此两者之间不存在什么特殊关系,也无法访问对方的私有成员

下面我们依然以一个程序进行验证

在上图程序中,我们发现原类虽然可以通过this指针指向内部类成员,但它并不可以去访问内部类私有的成员。同样的内部类也不可以访问原类成员

声明:当我们在外部创建内部类成员时需要对内部类进行声明:声明格式:原类::内部类 名称;。注意此时

权限:如果我们希望可以在外部创建内部类对象时,那么需要把内部类定义在原类public权限中。如果我们不希望内部类在外部创建对象,那就将内部类定义到原类private权限内即可。甚至我们可以将内部类定义到原类的一个函数内中作用:如果我们需要实现一些功能而用到一个类,但是其他的模块、类用不到,这时我们可以就把这个类写到当前所需要使用类中。这样做的好处便是封装性好

实现:内部类和原类都可以在类内声明,类外实现。这是因为这俩本来就是互相独立存在的

在堆中创建对象

我们可以在如下区域创建对象:

1.全局变量区,即函数外面

2.在栈中创建对象,即函数里面

3.在堆中创建对象

在C语言中,我们可以通过malloc(N)函数去申请一块内存,申请的这一块内存就是在堆中分配的。

在堆中创建对象我们可以使用new、delete这两个关键词来创建和释放;

Person* p = new Person();

delete p;

注意:使用new创建对象以后,不再使用该对象时,一定要用delete去释放该对象的内存空间

我们通过一个程序会查看new、delete这两个关键词的行为。

由上程序可以发现,在我们使用new时,编译器在堆中创建了对象并调用了构造函数。使用delete时,编译器会释放空间并调用析构函数:

现在我们进入反汇编代码,观察new的执行过程:

我们发现,new关键词生成了一个函数,我们进入这个函数观察

我们又可以发现,这个函数又调用了一个函数,我们再跟进去查看

我们发现这个函数也调用了另一个函数,我们再跟进去

我们发现这个函数又调用了另一个函数,我们再跟进去

我们发现,它又调用一个函数,再跟进去

我们发现,在new执行的最后关头,它最后一次调用了一个函数,这个函数是windows的。

因此,我们发现,在使用new关键字的时候,编译器一共调用了以下诸多函数:

在执行完这些函数以后,编译器会调用构造函数:call @ILT+0(Person::Person) (00401005)。

现在我们再来观察以下malloc函数的反汇编调用步骤:

call malloc (00401a20) → _nh_malloc_dbg → _heap_alloc_dbg → _heap_alloc_base → HeapAlloc

此时我们便惊人的发现,所谓的new关键字其实就是malloc+构造函数。

我们利用同样的方法跟进delete关键字的反汇编 :

我们一直跟到最后就会发现delete关键字会先调用析构函数然后再去调用free函数,也就是说delete关键字的本质就是析构函数+free。

new和delete应用

如果我们想要在堆中申请数组,需要使用new[]、delete[]这两个关键词来创建和释放。

 C、C++的方式在堆中申请、释放int数组:

int* p = (int*)malloc(sizeof(int)*10); free(p);

int* p = new int[10]; delete[] p;

/C、C++的方式在堆中申请、释放Class类型数组:

Person* p = (Person*)malloc(sizeof(Person)*10); free(p);

Person* p = new Person[10]; delete[] p;

malloc和new[]的区别:

1.malloc不会调用构造函数

2.new[]会调用构造函数,数组中对象创建一次则调用一次。例如new Person[10]调用10次构造函数

同理free和delete[]的区别和上述是一致的。

delete和delete[]是有区别的,如果使用new[]在堆中创建对象,使用delete去释放则只会释放第一个对象,其他的不会释放。而delete[]则释放全部对象

static

用static修饰的变量就是一个内存分配在全局区的私有全局变量

面向过程中的static

void MyFunction(int nFlag)
{
    static char szBuffer[0x10];
    if(nFlag)
    {
        strcpy(strBuffer, "编程达人");
    }
    else()
    {
    	printf(“%s\n”,szBuffer);
    }
}
int main(int argc, char* argv[])
{
	MyFunction(1);
	MyFunction(0);
	MyFunction(0);
	MyFunction(0);
	MyFunction(0);
	return 0;
}

在上述程序中如果我们正常定义szBuffer:char szBuffer[0x10] = {0},这时程序正常运行,并不会打印任何东西。而实际上该程序运行以后,我们发现打印了4个编程达人,这就是static造成的。这是因为static修饰的strBuffer数组是一个全局变量,并不随着函数的再使用而再创建。只不过只能在该函数使用罢了。

现在我们进入反汇编代码中来进行验证

如上图所示,szBuffer的地址就是一个全局区的地址,这也就验证了在函数中将一个局部变量前加一个static,那么该变量就变成了全局变量。而该全局变量其他函数是不可以访问的,这也就意味着该变量是私有的。

面向过程中static的另一个作用:当我们将一个新的函数定义到一个新文件中时:在新的头文件中声明该函数,在新的cpp文件实现该函数。如果我们希望新的函数别的文件可以使用,那么我们需要在函数的声明与实现前加上extern。这时我们的主文件便可以使用了。(注意包含新的头文件)。但如果新的函数并不希望别的文件使用,那么我们在函数的实现与声明前加上static便可以了,这时该函数只能在它所属的文件中使用。同样的变量也适用。注意:函数前的extern可以省略,但变量前的extern不可以

面向对象设计中的static

静态成员变量

正是因为静态成员变量是全局变量,才有了上图中的两种访问形式

我们观察如下类的定义

注意:z并不是通过访问定义的,而是通过对类的初始化定义的

现在我们去打印该类的大小,发现是8,这就验证了z其实是一个全局变量,只不过只有该类可以使用罢了

现在我们演示private权限的 静态成员变量的访问:

通过类中函数Fn()便可以正常访问private权限的静态成员变量z了

注意:静态成员变量虽然也是全局变量,但他和真正的全局变量不冲突,命名一样不会产生错误

总结:

1.所有类对象共享同一份数据

2.在编译阶段分配内存

3.类内声明,类外初始化

4.可通过对象以及类名进行访问

静态成员函数

当类中定义了一个普通成员函数时,我们去调用该函数需要先创建对象,然后通过对象去调用。但是当类中定义了一个静态成员函数时,这个静态成员函数并不属于任何一个对象,而是直接属于类的全局函数。当需要调用时,直接通过类 去调用而不是通过对象

static的经典应用:单子模式:有些时候我们希望定义的类有且只有一个对象存在,有如下两个实现思路:

1.禁止对象被随便创建

2.保证对象只有一个存在

相关的应用:当我们刷新一个页面时,并不会弹出一个新的页面,仍然是当时页面

以下我们以一个程序进行演示

class CSingleton
{
private:
	CSingleton(){};
	static CSingleton* m_pInstance; //定义静态成员
punlic:
	static CSingleton* GetInstance()
	{
		if(m_pInstance == NULL)
		{
			m_pInstance = new CSingleton()
        }
		return m_pInstance;
    }
}
CSingleton* CSingleton:: m_pInstance = NULL; //初始化静态成员
int main(int argc, char* argv[])
{
	CSingleton * p1 = CSingleton:: GetInstance();
	CSingleton * p2 = CSingleton:: GetInstance();
    return 0;
}	

该程序的关键点:private static

运行程序,我们可以发现p1,p2指向的是同一地址

总结:

1.所有对象共享同一个函数

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

3..静态成员存在于内存,无需生成对象就可被调用

4..静态函数不能直接调用非静态的成员变量

5.不能使用this引用

匿名对象

匿名对象:不加对象名的类对象,用于只使用一次类对象。周期只有一行,进入下一行时,该对象立刻销毁,调用析构函数。

应用如下:Playerase().func()  

const修饰成员变量

常函数:

1.成员函数后加const后我们称为这个函数为常函数

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

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

常对象:

1.声明对象前加const称该对象为常对象

2.常对象只能调用常函数

如下一个程序进行演示与讲解

class Person
{
public:
    int m_A;
    mutable int m_B; //const函数中该变量可被修改
    Person()
    {
        m_A = 0;
        m_B = 0;
    }
    void ShowPerson() const
    {
        this->m_A = 100  //错误,const修饰成员函数,表示指针指向的内存空间的数据不能修改
        this->m_B = 100;  //m_B被mutable修饰的变量了,所以它的值可以被修改
    }
}
void test01()
{
    const Person person; //常量对象
    person.m_A = 100; //错误,常对象不能修改成员变量的值
    std::cout << person.m_A << std::endl; //但是可以访问 
    person.m_B = 100; //常对象可以修改mutable修饰成员变量
    person.MyFunc(); //错误,常对象不能调用const修饰的函数
}

作业

1.设计一个类,有两个Int类型的成员x,y,并在类内部定义4个函数分别实现对x,y的加法、减法、乘法与除法的功能

#include<iostream>
struct calculator
{
	int x;
	int y;
	int Add()
	{
		return x + y;
	}
	int Sub()
	{
		return x - y;
	}
	int Div()
	{
		return x / y;
	}
	int Mul()
	{
		return x * y;
	}
};
int main()
{
	calculator calculator1;
	calculator1.x = 10;
	calculator1.y = 5;
	std::cout << calculator1.Add() << std::endl;
	std::cout << calculator1.Sub() << std::endl;
	std::cout << calculator1.Mul() << std::endl;
	std::cout << calculator1.Div() << std::endl;
	return 0;
}

2.定义一个空结构体,查看其大小

#include<iostream>
struct calculator
{
};
int main()
{
	calculator calculator1;
	std::cout << sizeof(calculator) << std::endl;
	return 0;
}

大小为1

3.判断下面两个代码能否正常执行,并说明理由

代码1:

#include <stdio.h>
struct Person{          
    void Fn_1(){    
        printf("Person:Fn_1()\n");
    }    
    void Fn_2(){    
        printf("Person:Fn_2()%x\n");
    }    
};                  
int main(int argc, char* argv[]){                
    Person* p = NULL;
    p->Fn_1();    
    p->Fn_2();        
    return 0;    
}

正常执行。

已知函数地址在编译以后已经确定,不在结构体中。所有类对象共用一个函数

虽然该结构体指针指向了一个空地址,但它仍然可以去调用它自己的函数
代码2:

#include <stdio.h>
struct Person{          
    int x;    
    void Fn_1(){    
        printf("Person:Fn_1()\n");
    }    
    void Fn_2(){    
        x = 10;   //这里相当于this->x = 10;
        printf("Person:Fn_2()%x\n");
    }    
};                 
int main(int argc, char* argv[]){        
    Person* p = NULL;        
    p->Fn_1();    
    p->Fn_2();        
    return 0;    
}

无法运行

由于该结构体有成员变量,但由于结构体指针指向了一个空地址,通过空地址给变量赋值是非法访问内存的,因此Fn_2()函数在给x赋值时出错

4.设计一个结构DateInfo,要求其满足下述要求:

有三个成员: int year; int month;int day;
要求有个带参数的构造函数,其参数分别为对应年、月、日。
有一个无参数的构造函数,其初始的年、月、日分别为:2015、4、2。
要求有一个成员函数实现日期的设置:SetDay(int day)
要求有一个成员函数实现日期的获取: GetDay()
要求有一个成员函数实现年份的设置: SetYear(int year)
要求有一个成员函数实现年份的获取: GetYear()
要求有一个成员函数实现月份的设置: SetMonth(int month)
要求有一个成员函数实现月份的获取: GetMonth()

#include <iostream>
struct DateInfo 
{
	int year; 
	int month; 
	int day;
	DateInfo(int year, int month, int day)
	{
		this->year = year;
		this->month = month;
		this->day = day;
	}
	DateInfo()
	{
		year = 1949;
		month = 10;
		day = 1;
	}
	void SetDay(int day)
	{
		this->day = day;
	}
	int GetDay()
	{
		return day;
	}
	void SetYear(int year)
	{
		this->year = year;
	}
	int GetYear()
	{
		return year;
	}
	void SetMonth(int month)
	{
		this->month = month;
	}
	int GetMonth()
	{
		return month;
	}
};
int main(int argc, char* argv[]) 
{
	DateInfo DateInfo1;
	std::cout << DateInfo1.GetYear() << std::endl;
	std::cout << DateInfo1.GetMonth() << std::endl;
	std::cout << DateInfo1.GetDay() << std::endl;
	DateInfo1.SetDay(5);
	DateInfo1.SetMonth(5);
	DateInfo1.SetYear(2021);
	std::cout << DateInfo1.year << std::endl;
	std::cout << DateInfo1.month << std::endl;
	std::cout << DateInfo1.day << std::endl;
	DateInfo DateInfo2(2000, 1, 1);
	std::cout << DateInfo2.GetYear() << std::endl;
	std::cout << DateInfo2.GetMonth() << std::endl;
	std::cout << DateInfo2.GetDay() << std::endl;
	return 0;
}

5.设计一个结构 TimeInfo,要求其满足下述要求:

该结构中包含表示时间的时、分、秒

设置该结构中时、分、秒的函数

获取该结构中时、分、秒的三个函数:GetHour(),GetMinute()和GetSecond()

#include <iostream>
struct TimeInfo
{
	int Hour; 
	int Minute; 
	int Second;
	TimeInfo(int Hour, int Minute, int Second)
	{
		this->Hour = Hour;
		this->Minute = Minute;
		this->Second = Second;
	}
	int GetHour()
	{
		return Hour;
	}
	int GetMinute()
	{
		return Minute;
	}
	int GetSecond()
	{
		return Second;
	}
};
int main(int argc, char* argv[]) 
{
	TimeInfo TimeInfo(12, 10, 5);
	std::cout << TimeInfo.GetHour() << std::endl;
	std::cout << TimeInfo.GetMinute() << std::endl;
	std::cout << TimeInfo.GetSecond() << std::endl;
	return 0;
}

6.让TimeInfo继承DateInfo,分别使用DataInfo和TimeInfo的指针访问TimeInfo对象的成员

#include <iostream>
struct DateInfo
{
	int year;
	int month;
	int day;
	DateInfo(int year, int month, int day)
	{
		this->year = year;
		this->month = month;
		this->day = day;
	}
	DateInfo()
	{
		year = 1949;
		month = 10;
		day = 1;
	}
	void SetDay(int day)
	{
		this->day = day;
	}
	int GetDay()
	{
		return day;
	}
	void SetYear(int year)
	{
		this->year = year;
	}
	int GetYear()
	{
		return year;
	}
	void SetMonth(int month)
	{
		this->month = month;
	}
	int GetMonth()
	{
		return month;
	}
};
struct TimeInfo : DateInfo
{
	int Hour; 
	int Minute; 
	int Second;
	TimeInfo(int Hour, int Minute, int Second)
	{
		this->Hour = Hour;
		this->Minute = Minute;
		this->Second = Second;
	}
	int GetHour()
	{
		return Hour;
	}
	int GetMinute()
	{
		return Minute;
	}
	int GetSecond()
	{
		return Second;
	}
};
int main(int argc, char* argv[]) 
{
	TimeInfo timeInfo(16, 0, 0);
	TimeInfo* a = &timeInfo;
	DateInfo* b = &timeInfo;
	b->year = 1949;
	b->month = 10;
	b->day = 1;
	std::cout << a->year << a->month << a->day << a->Hour << a->Minute << a->Minute << std::endl;
	std::cout << b->year << b->month << b->day << std::endl;
	return 0;
}

7.设计一个结构叫做MyString,要求该结构能够完成以下功能:

构造函数能够根据实际传入的参数分配实际存储空间
提供一个无参的构造函数,默认分配大小为1024个字节
析构函数释放该空间
编写成员函数SetString,可以将一个字符串赋值给该结构
编写成员函数PrintString,可以将该结构的内容打印到屏幕上
编写成员函数AppendString,用于向已有的数据后面添加数据
编写成员函数Size,用于得到当前数据的真实长度

#include <iostream>

struct MyString
{
	char* str;
	MyString(int size)
	{
		str = (char*)malloc(size);
	}
	MyString()
	{
		str = (char*)malloc(1024);
	}
	~MyString()
	{
		free(str);
	}
	void SetString(const char* Instr)
	{
		strcpy(str, Instr);
	}
	void PrintString()
	{
		printf("%s", str);
	}
	void AppendString(const char* Addstr)
	{
		strcat(str, Addstr);
	}
	int Size()
	{
		return strlen(str);
	}
};

int main(int argc, char* argv[]) 
{
	MyString String;
	String.SetString("123");
	String.PrintString();
	String.AppendString("456");
	String.PrintString();
	std::cout << String.Size() << std::endl;
	return 0;
}

8.将上面的所有练习改为class实现,并添加private/public进行权限控制,而且将类的定义与实现分开来写:定义写到xxx.h中,函数实现写在xxx.cpp中

1.calculator类

//Class.h
#pragma once
struct calculator
{
private:
	int x;
	int y;
public:
	calculator(int x, int y);
	int Add();
	int Sub();
	int Div();
	int Mul();
};

//Class.cpp
#include"Class.h"
calculator::calculator(int x, int y)
{
	this->x = x;
	this->y = y;
}
int calculator::Add()
{
	return x + y;
}
int calculator :: Sub()
{
	return x - y;
}
int calculator :: Div()
{
	return x / y;
}
int calculator :: Mul()
{
	return x * y;
}

//Test.cpp
#include<iostream>
#include"Class.h"
int main()
{
	calculator calculator1(5, 10);
	std::cout << calculator1.Add() << std::endl;
	std::cout << calculator1.Sub() << std::endl;
	std::cout << calculator1.Mul() << std::endl;
	std::cout << calculator1.Div() << std::endl;
	return 0;
}



2.DateInfo类

//Class.h
struct DateInfo
{
private:
	int year;
	int month;
	int day;
public:
	DateInfo(int year, int month, int day);
	DateInfo();
	void SetDay(int day);
	int GetDay();
	void SetYear(int year);
	int GetYear();
	void SetMonth(int month);
	int GetMonth();
};
//Class.cpp
#include"Class.h"
DateInfo::DateInfo(int year, int month, int day)
{
	this->year = year;
	this->month = month;
	this->day = day;
}
DateInfo::DateInfo()
{
	year = 1949;
	month = 10;
	day = 1;
}
void DateInfo::SetDay(int day)
{
	this->day = day;
}
int DateInfo::GetDay()
{
	return day;
}
void DateInfo::SetYear(int year)
{
	this->year = year;
}
int DateInfo::GetYear()
{
	return year;
}
void DateInfo::SetMonth(int month)
{
	this->month = month;
}
int DateInfo::GetMonth()
{
	return month;
}

//Test.cpp
#include<iostream>
#include"Class.h"
int main(int argc, char* argv[])
{
	DateInfo DateInfo1;
	std::cout << DateInfo1.GetYear() << std::endl;
	std::cout << DateInfo1.GetMonth() << std::endl;
	std::cout << DateInfo1.GetDay() << std::endl;
	DateInfo1.SetDay(5);
	DateInfo1.SetMonth(5);
	DateInfo1.SetYear(2021);
	std::cout << DateInfo1.GetYear() << std::endl;
	std::cout << DateInfo1.GetMonth() << std::endl;
	std::cout << DateInfo1.GetDay() << std::endl;
	DateInfo DateInfo2(2000, 1, 1);
	std::cout << DateInfo2.GetYear() << std::endl;
	std::cout << DateInfo2.GetMonth() << std::endl;
	std::cout << DateInfo2.GetDay() << std::endl;
	return 0;
}

TimeInfo类

//Class.h
struct DateInfo
{
private:
	int year;
	int month;
	int day;
public:
	DateInfo(int year, int month, int day);
	DateInfo();
	void SetDay(int day);
	int GetDay();
	void SetYear(int year);
	int GetYear();
	void SetMonth(int month);
	int GetMonth();
};
class TimeInfo : public DateInfo
{
private:
	int Hour;
	int Minute;
	int Second;
public:
	TimeInfo(int Hour, int Minute, int Second);
	int GetHour();
	int GetMinute();
	int GetSecond();
};
//Class.cpp
#include"Class.h"
DateInfo::DateInfo(int year, int month, int day)
{
	this->year = year;
	this->month = month;
	this->day = day;
}
DateInfo::DateInfo()
{
	year = 1949;
	month = 10;
	day = 1;
}
void DateInfo::SetDay(int day)
{
	this->day = day;
}
int DateInfo::GetDay()
{
	return day;
}
void DateInfo::SetYear(int year)
{
	this->year = year;
}
int DateInfo::GetYear()
{
	return year;
}
void DateInfo::SetMonth(int month)
{
	this->month = month;
}
int DateInfo::GetMonth()
{
	return month;
}
TimeInfo::TimeInfo(int Hour, int Minute, int Second)
{
	this->Hour = Hour;
	this->Minute = Minute;
	this->Second = Second;
}
int TimeInfo::GetHour()
{
	return Hour;
}
int TimeInfo::GetMinute()
{
	return Minute;
}
int TimeInfo::GetSecond()
{
	return Second;
}
//Test.cpp
#include<iostream>
#include"Class.h"
int main(int argc, char* argv[])
{
	TimeInfo timeInfo(16, 0, 0);
	TimeInfo* a = &timeInfo;
	DateInfo* b = &timeInfo;
	b->SetYear(1949);
	b->SetMonth(10);
	b->SetDay(1);
	std::cout << a->GetYear() << a->GetMonth() << a->GetDay()<< a->GetHour() << a->GetMinute() << a->GetSecond() << std::endl;
	std::cout << b->GetYear() << b->GetMonth() << b->GetDay() << std::endl;
	return 0;
}

MyString类

//Class.h
struct MyString
{
private:
	char* str;
public:
	MyString(int size);
	MyString();
	~MyString();
	void SetString(const char* Instr);
	void PrintString();
	void AppendString(const char* Addstr);
	int Size();

};
//Class.cpp
#include <iostream>
#include"Class.h"
MyString::MyString(int size)
{
	str = (char*)malloc(size);
}
MyString::MyString()
{
	str = (char*)malloc(1024);
}
MyString::~MyString()
{
	free(str);
}
void MyString::SetString(const char* Instr)
{
	strcpy(str, Instr);
}
void MyString::PrintString()
{
	printf("%s\n", str);
}
void MyString::AppendString(const char* Addstr)
{
	strcat(str, Addstr);
}
int MyString::Size()
{
	return strlen(str);
}
//Test.cpp
#include <iostream>
#include"Class.h"
int main(int argc, char* argv[])
{
	MyString String;
	String.SetString("123");
	String.PrintString();
	String.AppendString("456");
	String.PrintString();
	std::cout << String.Size() << std::endl;
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值