C++基础之对象的关系

0
11

一、变量

1、变量和类的生命周期
变量的生存期是指变量所占据的内存空间由分配到释放的时期。变量有效的范围称为其作用域。全局变量是程序中定义在所有函数(包括main函数)之外的任何变量,其作用域
是程序从变量定义到整个程序结束的部分。这意味着全局变量可以被所有定义在全局变量之后的函数访问。全局变量及静态变量分配的空间在全局数据区,它们的生存期为整个程序的执行期间。
⚫ 而局部变量,如在函数内或程序块内说明的变量,被分配到局部数据区,如栈区等。这种分配是临时的,一旦该函数体或程序块运行结束,所分配的空间就会被撤销。局部变量的生存期从被说明处开始,到所在程序块结束处结束。
⚫ 对于静态变量,如果没有进行初始化,系统会自动初始化为0。局部变量如果没有进行初始化,则其值是不确定的。
⚫ 使用new运算符创建的变量具有动态生存期。从声明处开始,直到用delete运算符释放存储空间或程序结束时,变量生存期结束。类的对象在生成时调用构造函数,在消亡时调用析构函数,在这两个函数调用之间即是对象的生存期。

2、在类中,也可以使用const关键字定义成员变量和成员函数,甚至是类的对象。由关键字const修饰的类成员变量称为类的常量成员变量。类的常量成员变量必须进行初始化,而且只能通过构造函数的成员初始化列表的方式进行。使用const修饰的函数称为常量函数。定义类的对象时如果在前面添加const关键字,则该对象称为常量对象。定义常量对象或常量成员变量的一般格式如下:
const 数据类型 常量名=表达式;
⚫ 定义常量函数的格式如下:类型说明符 函数名(参数表)const;
⚫ 在对象被创建以后,其常量成员变量的值就不允许被修改,只可以读取其值。对于常量对象,只能调用常量函数。总之,常量成员变量的值不能修改,常量对象中的各个属性值均不能修改。
⚫ 不能通过常量对象调用普通成员函数 const Person person;person.getName();//错误的

一、封闭类

当生成封闭类的对象并进行初始化时,它包含的成员对象也需要被初始化,需要调用成员对象的构造函数。在定义封闭类的构造函数时,需要添加初始化列表,指明要调用成员对象的哪个构造函数。在封闭类构造函数中添加初始化列表的格式如下:
封闭类名::构造函数名(参数表): 成员变量1(参数表),成员变量2(参数表),…{…}
初始化列表中的成员变量既可以是成员对象,也可以是基本数据类型的成员变量。对于成员对象,初始化列表的“参数表”中列出的是成员对象构造函数的参数(它指明了该成员对象如何初始化)。
先调用成员对象的构造函数,再调用封闭类对象的构造函数。

2、封闭类的复制构造函数
如果封闭类的对象是用默认复制构造函数初始化的,那么它包含的成员对象也会用复制构造函数初始化。

//成绩类
class Course{
    public:
        int id;
        string name;
        Course();
        Course(Course &course);
        Course(int id,string name);
};
Course::Course(Course &course){
    cout << "Course 复制构造函数" << endl;
    this->id=course.id;
    this->name=course.name;
}
Course::Course(int id,string name){
    this->id=id;
    this->name=name;
    cout << "Course 普通构造函数" << endl;
};
//学生类
class Student{
    public:
        int id;
        string name;
        //学生类中包含课程(Course),Student 称为封闭类
        Course course;
        Student(int id,string name,int cId,string cName);
        void show();   
};
Student::Student(int id,string name,int cId,string cName):id(id),name(name),course(cId,cName){
   cout << "Student 普通构造函数" << endl;
}
void Student::show(){
   cout << "name:"  << this->name  <<" course:" << this->course.name<< endl;
}
int main(){
    Student student(1,"小米",1,"数学");
    student.show();
    Student student2(2,"美团",1,"C++");
    student2.show();
}

三、this指针

C++语言规定,当调用一个成员函数时,系统自动向它传递一个隐含的参数。该参数是一个指向调用该函数的对象的指针,称为this指针,从而使成员函数知道对哪个对象进行操作。
⚫ C++规定,在非静态成员函数内部可以直接使用this关键字,this就代表指向该函数所作用的对象的指针。
⚫ 在一般情况下,在不引起歧义时,可以省略“this->”,系统采用默认设置。
⚫ 静态成员是类具有的属性,不是对象的特征,this表示的是隐藏的对象的指针,所以静态成员函数没有this指针。

void Person::setName(string name){
    this->name=name;
}
string Person::getName(){
    return this->name;
}
void Person::setId(int id){
    this->id=id;
}
int Person::getId(){
    return this->id;
}

四、对象的性质

1、同一类的对象之间可以相互赋值,
  如语句:Point A,B; A.Setxy(25,55); B=A;
2、可以使用对象数组,
  如语句:Point A[3]; 定义数组A可以存储3个Point类的对象。
3、也可以使用指向对象的指针,使用取地址运算符&将一个对象的地址置于该指针中,
  如语句: Point * p=&A; p->Display();
4、对象可以用作函数参数。
如果参数传递采用传对象值的方式,会在传值过程中产生副本,即临时对象,所以在被调用函数中对形参所作的改变不影响调用函数中作为实参的对象。
如果采取传对象的引用(传地址)的方式,当形参对象被修改时,相应的实参对象也将被修改。如果采用传对象地址值的方式,则使用对象指针作为函数参数,效果与传对象的引用一样。
C++推荐使用对象的引用作为参数传递,因为这样不会产生副本(即临时对象),在程序执行时不用调用构造函数,可直接进入函数体执行语句,在函数结束后,也不用调用析构函数析构临时对象。如想避免被调用函数修改原来对象的数据成员,可使用const修饰符。

  • void print(Point a){a.Display;} //对象作为函数参数
  • void print(Point&a){a.Display;} //对象引用作为函数参数
  • void print(Point * p){p->Display;} //对象指针作为参数

它们的原型声明分别为:print(Point),print(Point&),print(Point *)。
对于对象A,print(&A)调用的是原型为print(Point *)的函数形式。
另外,函数重载不能同时采用如上3种同名函数,因为函数使用的参数为对象或对象引用时,编译系统无法区别这两个函数,重载只能选择其中的一种。

五、类的使用权限

使用类的权限
①类本身的成员函数可以使用类的所有成员(私有和公有成员)。
②类的对象只能访问公有成员函数。例如输出x只能使用A.Getx(),不能使用A.x。
③其他函数不能使用类的私有成员,也不能使用公有成员函数,它们只能通过定义类的对象为自己的数据成员,然后通过类的对象使用类的公有成员函数。
④虽然一个类可以包含另外一个类的对象,但这个类也只能通过被包含的类的对象使用那个类的成员函数,通过成员函数使用数据成员。person 是 student的成员,student不能直接操作person中的属性,只能 Student.person.成员的方式来操作person中的成员

class Person{
    public:
        int id;
        string name;    
};
class Student{
    public:
        Person person;
}

六、不完全的类的声明

类不是内存中的物理实体,只有当使用类产生对象时,才进行内存分配,这种对象建立的过程称为实例化。
类必须在其成员使用之前先进行声明。然而,有时也可以在类没有完全定义之前就引用该类,此时将类作为一个整体来使用,如声明全局变量指针:

class MembersOnly; //不完全的类声明
MembersOnly * club; //定义全局变量类指针
Void main(){… 函数体} //主函数
class MembersOnly{…函数体}; //完全定义该类

不完全声明的类不能实例化,否则会编译出错;不完全声明仅用于类和结构,企图存取没有完全声明的类成员,也会引起编译错误。

七、空类:

尽管类的目的是封装代码和数据,它也可以不包括任何声明,如:class Empty{ }; 这种空类没有任何行为,但可以产生空类对象。
在开发大的项目时,需要在一些类还没有完全定义或实现时进行先期测试,定义空类可保证代码能正确被编译,从而允许先测试其中的一部分。此时常给空类增加一个无参数构造函数,以消除强类型检查的编译器产生的警告。如
class Empty{
  public:Empty(){ }
};

八、类作用域

①声明类时所使用的一对花括号形成类作用域,在类作用域中声明的标识符只在类中可见,如:class example { int num; } ;
  int i=num; //定义整型数i,并用num赋值;错误,因为num在类外并没有定义,所以在此不可见
  int num; //重新定义整型数num;正确,因为此num是重新定义的一个整型数,与类中说明的数据成员num具有不同的作用域
②如果某成员函数的实现是在类定义之外给出的,则类作用域也包含该成员函数的作用域,因此,当在该成员函数内使用一个标识符时,编译器会先在类定义域中寻找,如下例:

class Myclass {
    int number; //定义属于类Myclass的整型数number
    public:
        void set(int);
};
int number; //这个number不属于类Myclass
void Myclass::set(int i){
    number=i; //使用的是类Myclass中的标识符number
};

③类中的一个成员名可以通过使用类名和作用域运算符来显式的指定,称为成员名限定,例如:
void Myclass::set(int i){
  Myclass::number=i; //显示指定访问Myclass类中的标识符number
}
④在程序中,对象的生存期由对象说明来决定;类中各数据成员的生存期由对象的生存期决定,随对象存在和消失。
⑤使用struct关键字设计类与class相反,struct的默认控制权限是public,一般不用struct设计类,而是用struct设计只包含数据的结构,然后用这种结构作为类的数据成员。

<

发布回复

请输入评论!
请输入你的名字