第三章 类和类对象

 一、类

1、C++扩充的结构体一般格式如下:

  struct 结构名 {

     数据;//数据成员

     函数;//成员函数

  };

Complex  A;//定义结构体Complex的[变量A]

 A.init(1.1,2.2);// 调用成员函数init,给real和imag赋初值.【编译通过】

因为在默认情况下,结构体中的成员是公有的

2、类类型代替结构体类型

Complex  A;//定义类Complex的[对象A]

 A.init(1.1,2.2);//【编译不通过】

因为私有成员不能被类外的对象访问,编译错误。

总结:    

类和结构的主要区别是: 在默认情况下,

     【结构体中的成员是公有的;

        类中的成员是私有的】。

        常见的叫法:【声明类、定义对象】


说明:

1、不能在类声明中给数据成员赋初值,只有在对象定义之后才能给数据成员赋初值。

    class abc {

      private:

        char a=‘q’;//错误

        int  b=33;//错误

};


3、成员函数的声明(在类中)与定义【三种】

第一种定义方式:在类声明中只给出成员函数的原型,而将成员函数的定义放在类外部。

第二种定义方式:将成员函数直接定义在类内部,编译时是作为内联函数的方式来处理(内联函数隐式定义)

第三种方式:在类外部成员函数的定义前冠以关键字“inline”,使它起到内联函数的作用。【代码置换作用】


4、对象的定义【两种】

方法1:在声明类的同时,直接定义对象,即在声明类的右花括号“ }”后,直接写出属于该类的对象名表。

   class Student{【声明类】

      //..  ..

   }  stu1,stu2;

  方法 2: 声明了类之后,在使用时再定义对象。 

  class  Student {

       // …

  } ;

 Student  stu1, stu2;

请注意:

1、声明了一个类便声明了一种类型,这时没有给它分配存储空间,只有定义了对象后,系统才为对象分配存储空间


5、对象中成员的访问【三种】

(1) 通过对象名和对象选择符访问对象中的成员(“.”操作符)

注:类外部的对象可以通过公有成员函数对私有成员进行间接访问

(2)通过指向对象的指针访问对象中的成员(“->”操作符)

Date d, *ptr;    //定义对象d和指向类Date的指针变量ptr

ptr=&d;          //使ptr指向对象d

cout<year;  //输出ptr指向对象中的成员year

第三章 类和类对象_第1张图片

(3)通过对象的引用访问对象中的成员

Date d1; //定义类Date的对象d1

Date &d2=d1; //定义类Date的引用d2,

cout<

cout<

6、类的作用域和类成员的访问属性

所谓类的作用域就是指【在类的声明中的一对花括号所形成的作用域】

二、构造函数与析构函数

都是一种特殊的成员函数

1、对象的定义

2、对象的初始化

3、 对象的初始化可采用以下几种方法:【三种】

    1. 定义对象时对数据成员直接赋值

        Complex c1={1.1,2.2};

        //注意{}号,[要求类中所有的成员,都是公有的]

    2. 调用普通的成员函数来完成【常用】

      Complex A;

      A.init(1.1,2.2);

    3. 调用构造函数来完成【重视】

【构造函数:它是在定义对象的同时被自动调用的】。

【默认构造函数:不带任何参数,只能为对象开辟一个存储空间,而不能给对象中的数据成员赋初值。】

 形式一: 类名 对象名[(实参表)];//【注意与java的初始化不一样,无new】

        Complex A(1.1,2.2);// 建立对象时,编译系统会自动地调用构造函数,对对象进行初始化

形式二:类名 *指针变量名=new 类名[(实参表)];

        Complex *pa = new Complex(1.1,2.2);


例如:下面的用法是错误的:

           A.Complex(1.1,2.2);(X)

三、用成员初始化列表对【数据成员初始化】

1、在【构造函数】中一般用【赋值语句】对数据成员的初始化进行

2、用【成员初始化列表】来实现对数据成员的初始化

A(int i1,float f1):i(i1),f(f1){ [记一下]

// i=i1;

// f=f1

}

说明:

1、【对于用const修饰的数据成员】,或是【引用类型的数据成员】,是不允许用赋值语句直接赋值的。因此,只能用成员初始化列表对其进行初始化。

2、数据成员是按照它们在类中声明的顺序进行初始化的,与它们在成员初始化列表中列出的顺序无关。

四、构造函数的重载

只要一个类定义了一个构造函数,系统将不再给它提供默认的构造函数

定义对象A3时,不能调用自动生成的默认的构造函数,于是出现【编译出错】

五、带【默认参数】的构造函数

为什么要使用默认参数:【因为有些构造函数的参数值在大部分情况是相同的,例如大学学年制度4年】

调用构造函数产生的【二义性】:

声明了Complex(double r=0.0,double i=0.0);

就不能再声明无参数的构造函数Complex( );

Complex S1; 因为调用这句语句时,编译系统将无法判断应该调用哪个构造函数,因此产生了二义性

六、析构函数

任务有:

1. 释放分配给对象的内存空间。

2. 其他指定的任务

识别:

①析构函数与类名相同,但它前面必须加一个波浪号(~)

②析构函数没有参数,也没有返回值,因此它不能被重载。

③当撤消对象时,编译系统会自动地调用析构函数

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

说明:

 (1) 若没有显式地为一个类定义析构函数,编译系统会自动地生成一个缺省的析构函数。

Student∷~student(){ 

}

在以下情况,对象将被撤消,编译系统也会自动地调用析构函数:

① 主程序main()运行结束。

② 如果一个对象被定义在一个函数体内,则当这个函数结束时,该对象的析构函数被自动调用。

 ③ 若一个对象是使用new运算符动态创建的,在使用delete运算符释放它时,delete会自动调用析构函数

7、对象数组与对象指针

(1)对象数组初始化方法:

情况一:带有一个参数的构造函数【两种】

构造函数只有一个参数exam(int n){ x=n; }

exam ob[4]={11, 22, 33, 44};//通过[初始值表]给对象数组赋值

exam ob[4]={exam(11), exam(22),exam(33), exam(44)};//通过[初始值表]给对象数组赋值的另一种方法[记一下,因为不常用到]

情况二:不带参数的构造函数和带一个参数的构造函数

exam( ){ x=123;}

exam(int n) { x=n;}

exam ob[4]={55,66};

for循环打印运行结果: 55 66 123 123

情况三:带有多个参数的构造函数[记一下,难理解,可以用构造函数直接初始化对象]

Box(int h=10,int w=10,int len=10) { height=h; width=w; length=len; }

Box box1[3]={ Box(10,12,15),Box(15,18,20), Box(16,20,26) }//box1[0],box1[1],box1[2]

(2)对象指针

1、对象指针就是用于存放对象地址的指针变量。

exe *p;  //定义类exe的对象指针变量P

2、用对象指针访问对象数组[也记一下]

    exe *p;    //定义对象指针变量p

    exe ob[2];   //定义对象数组ob[2]

    p=ob;      //把对象数组的第一个元素的地址赋给对象指针变量p

    p++;        //访问第二个元素

3、this指针

this指针属于对象指针

当一个对象要调用成员函数时,this指针中就装着该对象的地址, 成员函数就根据这个指针,找到相应的数据,然而进行相应的操作。

例如:当执行a.disp()时,相当于执行cout<<"\nthis="<<&a<<"when x="<

存在形式:可隐可显

4、string类

string str1,str2;   //定义string类对象str1和str2

string str3(“China”);  //定义string类对象str3,同时对其初始化

第三章 类和类对象_第2张图片

5、向函数传递对象

(1)传值参数

(2)传址调用 

用变量指针做函数参数    swap(int* m,int* n){}

调用     swap(&a,&b);

(3)传址调用

void swap(int& m,int& n){}

swap(a,b);


(4)对象作为参数

 传值调用:

void swap(op ob){}

obj.swap(obj);

传址调用:

void swap(op *ob){}

obj.swap(&obj);

(5)对象引用作为参数【更加方便直接】

void swap(op &ob){}

obj.swap(obj);

6、对象的赋值和拷贝

对象的赋值说明:

(1)在使用对象赋值语句进行对象赋值时,两个对象的类型必须相同,否则编译错误

(2) 对象赋值是通过默认赋值运算符函数实现的

拷贝构造函数:其作用是在建立一个新对象时,使用一个已经存在的对象去初始化这个新对象。

Point p2(p1);

point(int a,int b) //构造函数

{x=a;y=b}

point(const point& p) //自定义拷贝构造函数,相当于: const point& p=p1

注还有一个默认的拷贝构造函数,代码不给出

特点:

①该函数与类同名,并且不能指定返回值类型(因为它也是一种构造函数)

②该函数只有一个参数,并且是同类对象的引用

③每个类必须有一个拷贝构造函数

调用拷贝构造函数的两种方法:

  ①  “代入”法调用 point p2(p1);

  ②  “赋值”法调用 point  p2=p1;

调用拷贝构造函数的三种情况:P124-P126(不易理解)

(1) 当用类的一个对象去初始化该类的另一个对象时

(2) 当函数的形参是类的对象, 在调用函数进行形参和实参结合时

(3) 当函数的返回值是类的对象, 在函数调用完毕将返回值(对象)带回函数调用处时

P128

8、静态成员

1、静态数据成员---访问

private:

    int count;

public:

    Student( ) { count++;  }

main(){

    int count=0;

    Stu1.count 

    Stu2.count}

实际打印结果: count=0 count= -858993459count= -858993459


二者是独立的,互不相干

分析:一个学生对象的count仅仅属于这个学生对象,而不是所有学生对象所共享的,因此count不能表示所有学生的总人数

解决办法:为了实现同一个类的多个对象之间的数据【共享】,C++提出了【静态数据成员】的概念。

private:

    static int count;

用类名访问静态数据成员并初始化int Student::count=0;

说明:

(1) 静态数据成员属于类(准确地说,是属于类中一个对象集合),而不像普通数据成员那样属于某一对象,因此可以使用“类名∷”访问静态的数据成员。

(2) 静态数据成员初始化应在类外单独进行,而且应在定义对象之前进行。一般在主函数main 之前,类声明之后的特殊地带为它提供定义和初始化。

(3) 如果对静态数据成员赋初值,则编译系统会自动赋予初值0

(4)公有静态数据成员可以在对象定义之前被访问[下图]

(5)公有静态数据成员可通过对象进行访问[下图]

第三章 类和类对象_第3张图片

(6) 私有的静态数据成员不能被外界直接访问。必须通过公有的成员函数间接访问

(7) C++支持静态数据成员的一个主要原因是可以不必使用全局变量,静态数据成员的主要用途是定义各个对象所公用的数据,如统计总数、平均数等。

2、静态成员函数static---调用

静态成员函数属于整个类,是该类所有对象共享的成员函数,而不属于类中的某个对象。

调用公有静态成员函数的一般格式有如下几种:

    类名::静态成员函数名(实参表)

    对象.静态成员函数名(实参表)

    对象指针->静态成员函数名(实参表)

说明:

(1) 一般情况下,静态函数成员主要用来访问静态数据成员,达到了对同一个类中对象之间共享数据的目的。

(2)静态成员函数一般公有的

(3) 使用静态成员函数的一个原因是,可以在建立任何对象之前调用静态成员函数,以处理静态数据成员,这是普通成员函数不能实现的功能。

int main()

  { Small_cat::total_disp(); 

    Small_cat w1(0.5),w2(0.6),w3(0.4);

    …

    return 0;}

(4) 编译系统将静态成员函数限定为内部连接,与现行文件相连接的其他文件中的同名函数不会与该函数发生冲突。

第三章 类和类对象_第4张图片

(6) 一般而言,静态成员函数不访问类中的非静态成员,非静态成员由普通成员函数访问 

(7)若静态成员函数需要访问非静态成员,静态成员函数只能通过对象名(对象指针或引用)访问该对象的非静态成员。

9、友元函数

A、【友元函数不是当前类的成员函数:所以它不能直接调用对象成员,它必须通过对象(对象指针或对象引用)作为入口参数,来调用该对象的成员】,但它可以访问该类所有的成员,包括私有成员(从而提高了程序运行的效率)、保护成员和公有成员。在类中声明友元函数时,需在其函数名前加上关键字friend

第三章 类和类对象_第5张图片
定义友元函数,形参是类girl的对象的引用,在此函数名前不要加关键字friend, 也不要加“类名::”  只有成员函数才那样定义

调用友元函数:

main(){

disp(g1); } //调用友元函数}

说明:

(1) 一个函数可以是多个类的友元函数。【当一个函数需要访问多个类时,友元函数非常有用】

例如; 定义函数prdata()是类girl和类boy的友元函数[记得要其中一个类的提前引用声明]

(2)不足之处: 破坏了数据的隐蔽性和类的封装性

B、友元成员函数

一个类的成员函数也可以作为另一个类的友元,这种成员函数称为【友元成员函数】,友元成员函数不仅可以访问自己所在类对象中的私有成员和公有成员,还可以访问friend声明语句所在类对象中的所有成员

C、友元类

一个类可以作为另一个类的友元

说明方法是在另一个类声明中加入语句:friend 类名;

class Y { … };

class X {

      friend  Y;//类Y中的所有成员函数都可以访问类X中的公有和私有成员

    };

D、类的组合

在一个类中内嵌另一个类的对象作为数据成员,称为类的组合。该内嵌对象称为对象成员,也称为子对象

E、 常引用

如果在说明引用时用const修饰,只能读,不能写,则被说明的引用为常引用。

F、 常对象【 在定义对象时必须进行初始化,且不能更新】

类名 const 对象名[(参数表)];

const 类名 对象名[(参数表)];

需要注意的地方:

错误1:不允许间接地更改常对象的数据成员

错误2:不允许直接更改常对象的数据成员

错误3:不允许常对象调用普通的成员函数【常对象只能调用它的常成员函数,而不能调用普通的成员函数】

G、常对象成员

1、使用const说明的数据成员

只能通过初始化列表对该数据成员进行初始化

Date::Date(int y,int m,int d) :year(y),month(m),day(d)

{ }

2、常成员函数

void showDate();//普通成员函数

void showDate() const;//常成员函数

void Date::showDate() const//别忘了,这里也要加上const

第三章 类和类对象_第6张图片

你可能感兴趣的:(第三章 类和类对象)