【C++学习笔记】十、c++中类和对象的使用

本文是谭浩强老师c++程序设计第九章的内容小结。

1.构造函数与类对象的初始化

(1)对类错误的初始化

对于一般数据,通常可以在声明的同时初始化,但是类不同,因为类不是一个实体,而是一个抽象类型,并不占用内存,因此没地方存放数据,所以不能在声明中初始化,即下述形式是错误的:

class Time
{
    hour = 0;
    minute = 0;
    sec = 0;
}; //是错误的

(2)用构造函数初始化数据成员

构造函数是一种特殊的成员函数,与其他成员函数不同,它无需用户调用,在建立对象时会自动执行调用过程,这一点与在类中直接赋值的不同点是,只有在函数调用时才会分配存储空间,对当前对象赋值,不调用时不占内存。

构造函数的名字必须与类名相同,不能随意命名,因此它的形式如下例:

class Time
{public
    Time()  //定义构造函数
        {
            hour = 0;
            minute = 0;
            sec = 0;
        } 
    void set_time();
    void show_time();
private:
    int hour;
    int minute;
    int sec;
    
};

对于构造函数注意以下几点:

a. 构造函数可以定义在类内或类外,类内形式如上例,类外要写上类名,并用作用域限定符连接,如:

Time::Time()

b. 构造函数的调用发生在建立对象时,调用时才为数据分配存储单元;

c. 构造函数没有返回值,也没有类型,因此定义时不声明类型,它的作用只是初始化对象;

d. 构造函数不需要被用户调用,也不能被调用;如果用户没定义构造函数,系统会自己生成,不过此时构造函数为空,也不起初始化作用;

e. 可以用一个类初始化另一个类,如:

Time t1;

Time t2 = t1;

f. 在构造函数中不仅可以包含数据成员赋值语句,还可以包含其他语句,如cout;

(3)带参数的构造函数

定义形式:

构造函数名(类型1,形参1,类型2,形参2,...)

由于构造函数不能被用户调用,因此不能写成fun(a,b)的形式调用,它只能在建立对象时由系统调用,因此建立对象时应该写成:

类名 对象名(实参1,实参2...)

(4)用参数初始化表初始化数据成员

该方法不在函数体内对数据成员初始化,而是在函数部首实现,形式如下:

类名::构造函数名(参数列表):初始化成员表

{

    构造函数体

}

如下所示:

Box::Box(int h, int w, int len):height(h),width(w),length(len){}

注意:若成员是数组,不能在初始化参数表中赋值,要在构造函数体中赋值,如:

class Student
{
public:
    Student(int n, char s, nam[]):num[n],sex(s)
        {strcpy(name,mam);} //函数体
private:
    int num;
    char sex;
    char name[20];
};

//可以如下定义对象
Student stud1(10101, 'm', "wang_li");

(5)构造函数的重载

在一个类中可以定义多个构造函数,这些构造函数可以有相同的名字,而他们的参数个数或类型不相同,这就是重载,这种情况下,系统会按照调用时参数的形式来确定调用的是哪个构造函数。

(6)使用默认参数的构造函数

即在声明构造函数时直接指定默认参数的方法,这是在定义时可以不指定默认参数,如下例:

class Box
{
public:
    Box(int h=10;int w=10;int len=10);//声明构造函数
private:
    int height;
    int width;
    int length;
};

//定义构造函数
Box::Box(int h, int w, int len)//不指定默认参数
{
height = h;
width = w;
length = len;
}

注意:类中定义了全部是默认参数的构造函数后,不能再定义重载函数,否则出现歧义,系统不知道调用谁。

2. 析构函数

 析构函数是一个特殊的成员函数,它是与构造函数作用相反的函数,它不是为了删除对象,二是在撤销对象占用的内存之前完成一些清理工作。如释放资源,同时还可以用来执行“用户希望在最后一次使用对象之后所执行的任何操作”,如输出相关信息等。一般情况下,类的设计者应该在声明类的同时定义析构函数,以指定如何完成清理工作,若用户没有定义析构函数,系统也会自动生成,但是它只是徒有析构函数的名称和形式,其实什么操作都不执行,要想让析构函数完成任何工作,都需要在定义的析构函数中指定。
函数形式:

在类名前加一个取反符号~

如:

Student(int n, string nam, char s)
{
    num=n;
    name = nam;
    sex =s;
}//定义有参数的构造函数
~Student() //定义析构函数

 

3. 调用构造函数和析构函数的顺序

一般情况下,调用析构函数的次序和调用构造函数的次序相反,即最先被调用的构造函数,其对应的(同一对象中的)析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用。也就是,先构造的后析构,后构造的先析构。它相当于一个栈,先进后出。

4. 对象数组

对象数组即数组中每个元素都是同类的对象。

如:Student stud[50]; //假设已经声明了Student类,定义stud数组,有50个元素。

在建立数组时,同样要调用构造函数,若有50个元素,就需要调用50次构造函数。如果构造函数只有一个参数,在定义数组时可以直接在等号后边的花括号内提供实参,如:

Student stud[3]={60,70,78};

如果构造函数有多个参数,则不能用在定义数组时直接提供所有实参的方法,因为系统会分不清给谁,此时应该用如下形式:

Student stud[3]={

    Student(1001,18,87);

    Student(1002,19,76);

    Student(1003,18,72);

};

5. 对象指针

(1)指向对象的指针

对象的指针是对象空间的起始地址,其定义的一般形式为:

类名 *对象指针名

如:

Time *pt; //定义pt为指向Time类对象的指针变量

Time t1; // 定义t1为Time类对象

pt = &t1; // 将t1的起始地址赋给pt

可以通过对象指针来访问对象和对象的成员,形式如下:

访问数据:(*pt).time或pt->time;

访问函数:(*pt).get_time()或pt->get_time();

(2)指向对象成员的指针

a. 指向对象数据成员的指针:

数据类型名 *指针变量名

如:

int *p1;

p1 = &t1.hour;

b. 指向对象成员函数的指针:

指向普通函数的指针:类型名 (*指针变量名)(参数列表);如:void (*p) ();

而指向成员函数的指针不同,因为成员函数是类中的成员,指向它的指针也必须指明它所属的类,因此形式如下:

数据类型名 (类名:: *指针变量名)(参数列表)

如:

void(Time:: *p2)();

p2 = &Time::get_time;

(3)this指针

this指针是指向本类对象的指针,它的值是当前被调用的成员函数所在的对象的起始地址。

this指针是隐式使用的,它是作为参数被传递给成员函数的;也就是说这些是编译系统自动实现的,不需要人为加入this指针,也不必将对象的地址传给this指针。

如:return (*height)相当于return((*this).height)

6. 共用数据的保护

(1)常对象

定义对象时加关键字const,可以指定对象为常对象,常对象必须有初值,定义形式如下:

类名 const 对象名[(实参表)];或者:const 类名 对象名[(实参表)];

如:Time const t1(12,34,46); //定义t1是常对象

这样在t1的生命周期中,对象t1所有的数据成员的值都不能被修改。

同时,通过该对象只能调用类中的常成员函数,不能调用普通的函数,若想调用普通函数,必须将该函数变为常函数(如果该函数不改变对象中数据成员的值),将函数变为常函数的形式是:

void get_time() const;

若想修该常对象中某个数据成员的值,需要将数据成员声明为mutable,如:

mutable int count;

也就是把count声明为可变类型。

(2)常对象成员

a. 常数据成员

用const来声明常数据成员:const int hour;

常数据成员只能通过构造函数的参数初始化表对其赋初值,其他情况都不可以。

b. 常成员函数

类型名 函数名(参数表)const

如:void get_time() const;

如果将成员函数声明为常函数,则只能引用本类的数据成员,而不能修改它们。

常成员函数不能调用另一个非常成员函数

c. 指向对象的常指针

类名 *const 指针变量

如:Time *const p;

指向对象的常指针的值不能改变,也就是说它所指向的对象不能改变,但是对象的值可以被改变;

d. 指向常对象的指针变量

const 类型名 *指针变量名

如:const Time *p;

若一个变量已经被声明为常变量,那就只能用指向常变量的指针指向它;而指向常变量的指针既可以指向常变量,也可以指向非常变量,此时不能通过改变指针来改变变量的值,也就是说只有通过指针访问,该变量具有常变量属性,若直接访问该变量,可以改变该变量的值。

e. 对象的常引用

一个变量的引用即是这个变量的别名,常引用即是声明为const的引用。

如: void fun(const Time &t);此时函数不会修改其对应实参的值。

7. 对象的动态建立和释放

与结构体相同,仍是用new和delete来动态建立对象或撤销对象。

详见:https://blog.csdn.net/zl3090/article/details/86619016

8. 对象赋值和复制

(1)对象的赋值:

对象名1 = 对象名2;

注意:二者类型相同,且是对数据成员的赋值;

(2)对象的复制:

类名 对象2(对象1)

用已有的对象1复制出一模一样的对象2。

赋值是对一个已经存在的对象赋值,也就是赋值之前需要先声明;

而复制是从无到有建立一个新的对象。

9. 静态成员

(1)静态数据成员

如果希望各个对象中数据成员的值是一样的,就可以把它定义为静态数据成员,关键字是static,如:

static int height;

静态数据成员可以初始化,但必须在类体外初始化,同时不能用参数初始化列表进行初始化,如:

int Box::height = 10;

静态数据成员在内存中只占一份数据,它的值可以被改变,一旦被改变,对象中所有的该成员的值都会被改变。

(2)静态成员函数

静态成员函数与静态成员数据都是类的一部分,而不是对象的一部分,如果在类外调用,必须加类名和域运算符::。

静态成员函数形式如下:

static int volunm;

其主要用来访问静态数据成员,而不能访问非静态成员。

10. 友元

(1)友元函数

类的友元函数在类体中用friend进行声明,它可以访问其对应函数的私有成员。

友元函数既可以将普通函数声明为友元函数,也可以将类中其他成员函数声明为友元函数。

如:friend void display( Time &);//声明display函数为Time类的友元函数;

friend void Time::display(Date &);//声明Time中的display函数为本类的友元成员函数。

(2)友元类

不仅可以将一个函数声明为类的友元函数,还可以将一个类声明为另一个类的友元类,形式如:

friend 类名;

在A类的定义体中用friend B声明B类为其友元类。

友元关系是单向的,B是A的友元类,说明B可以访问A,但是A不是B的友元类,A不能访问B的私有数据;

同时,友元关系不能传递,B是A的友元类,C是B的友元类,C并不是A的友元类。

11.类模板

类模板解决的是若干个类功能相同而数据类型不同的情况。

声明类模板时,在类之前要加一行:

template <class 类型参数名>

对于类模板的一般形式为:

类模板 <实际类型名> 对象名(参数表)

例子如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值