C++从结构到类

一 结构和类的区别

在C++中,class和struct做类型定义时,二者之间 只有两点区别: 

默认继承权限:如果不明确指定,来自class的继 承按照private继承处理,来自struct的继承按照 public继承处理; 

成员的默认访问权限:class的成员默认是private 权限,struct默认是public权限。

二 类的定义和类的访问权限

  注意定义类的时候,类的内部不能够对数据进行初始化即可。

  类的访问权限,private,public,protected

    在关键字public后面声明,它们是类与外部 的接口,可以被程序中任何代码访问。

    在关键字private后面声明,只允许本类中的 函数及友元访问,而其他的函数不能访问。 如果紧跟在类名称的后面声明私有成员,则关键字 private可以省略。

    在关键字protected后面声明,能被本类成员 函数、派生类成员函数和友元访问,其他函 数无法访问。 保护类型的性质和私有类型的性质相似,其差别在 于继承过程中对产生的新类影响不同。

    protected可以被派生类使用?

 

  附,条件编译语句

// student.h
#ifndef STUDENT_H //条件编译
#define STUDENT_H
class Student
{
public: //公有成员函数,外部接口
void input(char* pid,char* pname,int a,float s);
void modify(float s);
void display();
private: //私有数据成员,外部不可见
char* id;
char* name;
int age;
float score;
};
#endif //条件编译结束

  STUDENT_H作为被定义过的标识符,如果程序中已经定义了STUDENT_H,则不在执行从ifndef到endif语句之间的所有语句。防止多次编译。

 

  重新附带我的新的理解

  一般是一个头文件.h前面有这个东西

  应该是为了防止大型开发中,多个.cpp,include "xx.h"为了避免链接成一个文件时出现的错误,所以加上一个if not define 然后下面的define的内容应该

是可以随意取的。

  

  

 

 

 三  类的成员函数

     直接在类内实现,或者在类外实现。

 

四 类的对象的圆点访问和指针访问

   当使用关键字new在堆上动态创建一个对象时,它实际上做了三件事:获得一块内存空间、调用构造函数、返回正确的指针

  所以 student *obj1=new student 等加于student *obj1=new student(),这里只是无参构造

  等价于student obj(),等价于student obj

  有参构造的时候,上面的这两大类方法都必须要加上参数。

#include "student.h"
#include <iostream>

int main()
{
    student *obj1 = new student;//貌似有构造函数有参数的话这么做会报错?
    obj1->set(1001, "TheDa1");
    obj1->display();
    student obj2;//无参构造函数
    obj2.set(1002, "TheDa2");
    obj2.display();

    return 0;
}

   注意,只是单纯地声明一个指针对象是不会调用构造函数的。

   

  像这样student *obj1 = new student为指针对象分配了内存空间才会调用构造函数。

五 类的构造函数

  构造函数的特点

1.构造函数是成员函数,函数体可写在类体内,也可写在类 体外。

2.构造函数的函数名与类名相同,且不指定返回值类型,它 有隐含的返回值,该值由编译系统内部使用。

3.构造函数可以没有参数,也可以有参数,因此可以重载, 即可以定义参数不同的多个构造函数。

4.每个类都必须有一个构造函数。如果类中没有显式定义构 造函数,则编译系统自动生成一个缺省形式的构造函数, 作为该类的公有成员。 程序中不能直接调用构造函数,在定义对象时编译系统自 动调用构造函数。

5.如果显式定义了构造函数,则默认构造函数将不存在。

  方法1 成员初始化表

  例如这样Classy::Classy(int n, int m) :mem1(n), mem2(m), mem3(m*n + 2)

  使用前面的参数列表来初始化

    

#include <iostream>
using namespace std;
Class Sample
{
 private:
 int x;
 int &rx;
 const float pi;
 public:
 Sample(int x1):x(x1),rx(x),pi(3.14)
 { } 
void Print()
 {
 cout<<"x="<<x<<" "<< <<"rx="<<rx<<"
 << "pi="<<pi<<endl;
 }
};
int main()
{
 Sample a(10);
 a.Print();
 return 0;
}

 

  方法2 具有默认参数的构造函数

date.h

 

注意到,具有默认参数的构造函数,在其实现的时候,没有必要指明默认参数。

 

#include <iostream>
using namespace std;
class Date //定义日期类Date
{
public: //声明类成员
Date(int y=2011, int m=1, int d=1);
void ShowDate( )
 private:
int year; int month; int day;
}; //以括号及分号结束,体现封装

date.cpp

#include “date.h”
Date::Date(int y, int m, int d)
{
 year=y; month=m; day=d;
 cout<<"constructing…"<<endl;
}
void Date::ShowDate( )
{ cout<<"Date:"<<year<<"."<<month<<"."<<day;
 cout<<endl;
}

Date date1;

Date date2(2005);

Date date3(2006,12,15);

从前往后顶替就完事了

 

注意:

如果带默认参数的构造函数去掉默认参数后,与重载的构造函数形式一致
那么就会出现二义性
既然带默认参数的构造函数能够与重载的构造函数出现二义性。说明你重载的构造函数没有意义。
他并不能区分一种构造类型。
也就是说,你直接用带默认参数的构造函数来代替重载的那个构造函数就可以了
 

   方法3 重载构造函数

   方法4 拷贝构造函数

  1.拷贝构造函数是成员函数,函数体可写在类体内,也 可以写在类体外。

  2.拷贝构造函数名与类名相同,并且也不指定返回值类 型。

  3.拷贝构造函数只有一个参数,并且是对同类对象的引 用。

  4. 每个类都必须有一个拷贝构造函数。如果类中没有显 式定义复制构造函数,则编译系统自动生成一个缺省 形式的复制构造函数,作为该类的公有成员。

  

class 类名
{
public:
 类名(参数表); //构造函数
 类名(const 类名&引用名); //拷贝构造函数
…
};
//拷贝构造函数的实现,参数是本类对象的引用
类名::类名 (const 类名& 引用名)
{
 函数体
}

 

如下

#include <iostream>
using nsmespace std;
#include <conio.h>
class TAdd
{
public:
 TAdd(int a,int b) //构造函数
 {   
   x=a; y=b;   cout<<"constructor."<<endl; } TAdd(const Tadd &p) //复制构造函数 {
   x=p.x; y=p.y;    cout<<"copy constructor."<<endl; } ~TAdd() //析构函数 { cout<<"destructor."<<endl; } int add(){ return x+y; } //成员函数 private: int x,y; };

  然后使用的时候就是

  TAdd p1(2,3); //定义并初始化对象p1

  TAdd p2(p1); //定义对象p2并用对象p1初始化对象p2

 

  重要:拷贝构造函数在如下三种情况中自动调用

  1.Student s1("03410101","zhang hua",19,95);

   Student s2(s1); //用对象s1初始化对象s2,复制构造函数被调用

    这种一般用法

  2.当对象作为函数的实参传递给函数的形参时

  3.当函数的返回值是类的对象,函数调用完成返回时

示例程序如下:

date.h

#ifndef xx
#define xx
#include <iostream>
using namespace std;
class Date //定义日期类Date
{
public: //声明类成员
    Date(int y = 2011, int m = 1, int d = 1);
    Date(const Date &date);
    ~Date();
    void ShowDate();
private:
    int year; int month; int day;
}; //以括号及分号结束,体现封装
#endif

date.cpp

#include "student.h"
Date::Date(int y, int m, int d)
{
    year = y; month = m; day = d;
    cout << "Constructing…"<<endl; 
}
        Date::Date(const Date &date1)
    {
        year = date1.year; month = date1.month; day = date1.day;
        cout << "Copy Constructing…" << endl;
    }
    void Date::ShowDate()
    {
        cout << "Date:" << year << "."<<month<<"." << day;
        cout << endl;
    }
    Date::~Date()
    {
        cout << "destructing…" << endl;
    }

main.cpp

#include "student.h"
#include <iostream>

Date Fun(Date date2)
{
    Date date3(date2); //调用拷贝构造函数
    return date3;
}
int main()
{
    Date obj1(1999, 3, 20);
    Date obj3;
    Date obj2(obj1); //调用拷贝构造函数
    Date obj4 = obj2;//调用拷贝构造函数
    obj3 = obj2;//没有效果
    obj3 = Fun(obj2);//拷贝构造函数,传参一次,返回一次,函数内部一次
    obj3.ShowDate();
    return 0;
}

执行结果

Constructing…
Constructing…
Copy Constructing…
Copy Constructing…
Copy Constructing…
Copy Constructing…
Copy Constructing…
destructing…
destructing…
destructing…
Date:1999.3.20
destructing…
destructing…
destructing…
destructing…

 

有关上面的

obj3 = Fun(obj2);这一句的进一步的解析
#include "student.h"
#include <iostream>

Date Fun(Date date2,Date date4)
{
    Date date3(date2); //调用拷贝构造函数
    return date3;
}
int main()
{
    Date obj1(1999, 3, 20);
    Date obj3;
    Date obj2(obj1); //调用拷贝构造函数
    Date obj4 = obj2;
    obj3 = obj2;
    obj3 = Fun(obj2,obj2);
    obj3.ShowDate();
    return 0;
}

如果加入一个新的无关紧要的形参,那么运行结果将会是

Constructing…
Constructing…
Copy Constructing…
Copy Constructing…
Copy Constructing…
Copy Constructing…
Copy Constructing…
Copy Constructing…
destructing…
destructing…
destructing…
destructing…
Date:1999.3.20
destructing…
destructing…
destructing…
destructing…

可见,最开始的时候,传给形参date2的时候,拷贝构造函数一次

新建date3的时候,拷贝构造函数一次

然后返回的时候,拷贝构造函数一次

 

退出这个程序的时候

销毁date2一次,新的例子date4销毁一次

date3销毁一次

返回值销毁一次

原来3次,后来4次

 

再注意引用类型作为函数的返回值对于构造函数和析构函数的新的影响

总所周知,上面的函数的传参的过程中,函数调用完成的时候调用一次拷贝构造函数

这其实是在内存中建立了返回值的副本

但是如果使用返回值类型的函数的话,就不会在内存中出现这个副本了

上代码

#include "student.h"
#include <iostream>

Date &Fun(Date date2)
{
    Date date3(date2); //调用拷贝构造函数
    return date3;
}
int main()
{
    Date obj1(1999, 3, 20);
    Date obj3;
    Date obj2(obj1); //调用拷贝构造函数
    Date obj4 = obj2;//调用拷贝构造函数
    obj3 = obj2;//没有效果
    obj3 = Fun(obj2);//拷贝构造函数,传参一次,返回一次,函数内部一次
    obj3.ShowDate();
    return 0;
}
/*

Constructing…
Constructing…
Copy Constructing…
Copy Constructing…
Copy Constructing…
Copy Constructing…
destructing…
destructing…
Date:1999.3.20
destructing…
destructing…
destructing…
destructing…


*/

 

结果很显然了


 

六 析构函数

 

1.析构函数也是类的一个公有成员函数,它的名称是 由类名前面加“~”构成,也不指定返回值类型。

2.和构造函数不同的是,析构函数不能有参数,因此 不能重载。

 

使用示例

#include<iostream>
class Point
{
 public:
 Point(int xx,int yy);
 ~Point( ); //析构函数
 //...其它函数原型
 private:
 int X,int Y;
};
Point::Point(int xx,int yy)
{
 X=xx; Y=yy;
}
Point::~Point( )
{
}

1.析构函数是成员函数,函数体可写在类体内,也可以写在 类体外。

2.析构函数的函数名与类名相同,并在前面加“~”字符,用 来与构造函数加以区别。

3.析构函数不指定返回值类型。

4.析构函数没有参数,因此不能重载。一个类中只能定义一 个析构函数。

5.每个类都必须有一个析构函数。如果类中没有显式定义析 构函数,则编译系统自动生成一个缺省形式的析构函数, 作为该类的公有成员。 6. 析构函数在对象生存期结束前由编译系统自动调用。

 

注意:

1. 析构函数并不能收回对象本身所占用的内存。在执行完 程序的最后一条语句后,编译系统自动调用析构函数,

执行完析构函数后,再由编译系统收回对象所占用的内 存。

2. 如果在构造函数中new创建对象,则在析构函数中用 delete进行删除,以回收所占内存空间。

如下代码

#include <iostream>
using namespace std;
class String
{
 private:
 char *text;
 public:
 String(char *ch)
 {
   text=new char[strlen(ch)+1];
   strcpy(text,ch);
   cout<<"constructing…"<<endl;
 }
~String( )
 {
   delete [ ]text;
   cout<<"destructing…"<<endl;
 }
 void Show()
 {
   cout<<"text="<<text<<endl;
 }
};
int main( )
{
   String string("Hello!");
   string.Show();
   return 0;
}

 

 

3. 调用构造函数的顺序与创建对象的顺序相同,调用析构 函数的顺序与创建对象的顺序相反。

 

 

七 深拷贝和浅拷贝

  

posted @ 2019-11-18 22:23  TheDa  阅读(266)  评论(0编辑  收藏  举报