460 likes | 628 Views
类和对象 语法(其他). 本章要点. 类的静态成员 对象指针 动态创建 类作用域 this 指针 类的组合 常对象和类的常成员 类模板 和 函数模板 友元. 1 类的静态成员. 思考:静态成员存放在内存的哪一区? 定义静态成员的 作用 在类的定义中,使用关键字 static 修饰的成员,称为类的 静态成员 ; 类定义中没有使用关键字 static 修饰的成员称为 实例成员 。 类的静态成员包括静态数据成员和静态函数成员。. class A { public:
E N D
本章要点 • 类的静态成员 • 对象指针 • 动态创建 • 类作用域 • this指针 • 类的组合 • 常对象和类的常成员 • 类模板 和 函数模板 • 友元
1 类的静态成员 • 思考:静态成员存放在内存的哪一区? • 定义静态成员的作用 • 在类的定义中,使用关键字 static 修饰的成员,称为类的静态成员;类定义中没有使用关键字static修饰的成员称为实例成员。 • 类的静态成员包括静态数据成员和静态函数成员。
class A { public: static int i; // 声明静态数据成员i int j; // j是类的实例数据成员 }; int A::i = 0; // 静态成员变量的初始化 静态数据成员 • 在类的定义中,如果定义数据成员时,使用了关键字static,则该数据成员就成为静态的数据成员。而没有使用static修饰的数据成员,称为类的实例数据成员。 • 类的实例数据成员是属于该类的某个具体实例(对象)的。当程序中没有为类创建任何实例(对象)时,相应的实例数据成员也不存在。同一个类的不同实例(对象)的同名的实例数据成员是相互独立变量。 • 类的静态数据成员属于类,而不属于类的某个具体实例(对象),在程序的运行过程中,类的静态数据成员只存在一份拷贝,并被该类的所有实例共享。 • 必须在类外的文件作用域中定义静态数据成员---为它们分配存储空间。例如: int A::i; • 在程序中可以使用类名和域解析操作符::直接访问类的公有的静态数据成员;也可以使用对象名加点操作符访问静态数据成员。
例1 静态数据成员和实例数据成员的区别和访问公有的静态数据成员的方法 ClassA.h Main.cpp class A { public: A() { j=0;} A(int jj) { j=jj;} static int i; int j; }; #include<iostream.h> #include“ClassA.h” int A::I =0; // 全局对象,若放在.h中会引起 // 重复定义这样的错误 void main() { A::i = 10; cout<<“类A的静态成员i等于"<<A::i<<endl; A a1, a2(1); a1.i=100; a1.j=2; cout<<"类A的静态成员i等于"<<a2.i<<endl; cout<<"对象a1的实例成员j等于"<<a1.j<<endl; cout<<"对象a2的实例成员j等于"<<a2.j<<endl; } 不要试图在头文件中定义(初始化)静态数据成员。
静态函数成员 • 在声明类的成员函数时,如果使用了关键字static,则该成员函数为静态的成员函数;如果声明成员函数时,没有使用关键字static,则称该成员函数为实例成员函数。 • 类的静态成员函数属于类,而不属于类的具体实例(对象)。 • 既可以使用类名加域解析操作符调用公有的静态成员函数,也可以使用对象名像调用实例成员函数一样调用公有的静态成员函数。 • 在类的静态成员函数中只能访问类的静态数据成员,而不能访问类的实例数据成员。而在类的实例成员函数中,既可以访问类的静态数据成员,也可以访问实例数据成员。
例2 类的静态函数成员 ClassA.h class A { public: A() { j=0;} A(int jj) { j=jj;} staticvoid seti(int ii) { i=ii;} //静态成员函数,只能访问静态数据成员 staticint geti() { return i;} //静态成员函数,只能访问静态数据成员 void setj(int jj) { j=i+jj;} /*实例成员函数,既可以访问实例数据成员 也可以访问静态数据成员*/ int getj() { return j;} private: staticint i; //声明静态数据成员 int j; //声明实例数据成员 };
例2 类的静态函数成员 Main.cpp #include<iostream.h> #include“ClassA.h” int A::i; //在文件作用域中定义静态数据成员 void main() { A::seti(10); //使用类名调用静态成员函数 cout<<"类的静态数据成员i等于"<<A::geti()<<endl; A a1; a1.seti(100); //使用对象调用静态成员函数 cout<<"类的静态数据成员i等于"<<a1.geti()<<endl; a1.setj(10); cout<<"对象a1的实例数据成员j等于"<<a1.getj()<<endl; }
静态成员变量的应用 例3 使用静态数据成员统计程序运行过程中创建Circlr类的实例(对象)的个数 问题分析:由于类的静态数据成员被所有对象共享,所以可以用来统计程序中创建对象的数目。可以为Circle类添加一个静态整型数据成员numberOfCircle,用来统计Circle对象的数目,程序中创建一个Circle对象,就把numberOfCircle的值加1;那么静态成员numberOfCircle加1的操作应放在哪里呢?我们知道,只要创建类的对象,系统就会自动调用类的构造函数,以初始化对象中的实例数据成员,所以可以把为numberOfCircle加1的操作放在类的构造函数中。
int HClassTstA::m_StaI = 0; HClassTstA::HClassTstA() { m_StaI++; m_InsI = 0; m_InsI++; } HClassTstA::~HClassTstA() { m_StaI--; m_InsI--; } void HClassTstA::SetI(int i) { m_InsI = i; m_StaI = i; } void HClassTstA::ShowI() { cout<<"静态变量 m_StaI:"<<m_StaI<<endl; cout<<"实例变量 m_InsI:"<<m_InsI<<endl; } 课堂练习 class HClassTstA { public: HClassTstA(); ~HClassTstA(); void SetI(int i); void ShowI(); private: static int m_StaI; int m_InsI; }; HClassTstA a1; a1.ShowI(); HClassTstA a2; a2.ShowI(); a1.SetI(99); a1.ShowI(); a2.ShowI();
本章要点 • 类的静态成员 • 对象指针 & 动态创建 • 类作用域 • this指针 • 类的组合 • 常对象和类的常成员 • 类模板 和 函数模板 • 友元
2 对象指针 • 指向类的对象的指针称为对象指针。定义对象指针的语法形式如下: 类名 *对象指针名例如:Circle *ptrToCir; • 可以使用运算符&获得对象的内存地址,来为对象指针赋值。例如: Circle c; Circlr *ptrToCir = &c; • 为了和对象名相区别,对象指针使用操作符->访问其公有成员。语法如下: 对象指针->成员名; 例如: cout<<"圆c1的面积为"<<ptrToCir->area()<<endl;
动态创建对象 • 使用new操作符在程序运行过程中动态创建类的对象。语法形式如下: 对象指针 = new 类名(初值列表); 例如: Circle *ptrToCir = new Circle(10.0); • 对于程序中动态创建的对象,当使用完毕后,应使用delete操作符将其删除,以释放该对象所占用的“堆”内存。此时,系统将自动调用该对象所属类的析构函数。语法格式如下: delete 对象指针名; 例如: delete ptrToCir;
动态创建对象数组 • 动态创建对象数组的语法如下: 对象指针名 = new 类名[下标表达式列表] ; 例如: Circle *ptrToCir = new Circle[10]; • 使用操作符new创建的对象数组,在程序结束前应使用操作符delete将其删除。语法格式如下: delete[ ] 对象指针名; 例如: delete[ ] ptrToCir;
本章要点 • 类的静态成员 • 对象指针 & 动态创建 • 类作用域 • this指针 • 类的组合 • 常对象和类的常成员 • 类模板 和 函数模板 • 友元
类成员具有类作用域 • 在类中定义的成员,只能在类的成员函数内部使用成员名称进行访问,它们都具有类作用域。 • 在类体外部访问类成员时,必须使用对象名加点操作符,或者使用对象指针加->操作符。对于类的静态成员,还可以使用类名加域解析操作符::来访问。
具有类作用域的数据成员被局部变量屏蔽 • 类的数据成员具有类作用域,但是,当在类的某个成员函数内部定义了与类的数据成员同名的局部变量时,在该成员函数内部,类的数据成员被同名的局部变量所屏蔽。例如: class A { public: void fun( ); …… private: int val; …… }; void A::fun( ) { float val; //定义和数据成员同名的局部变量 val = 10.0; //这里的val代表局部变量val,而不是类的数据成员val }
本章要点 • 类的静态成员 • 对象指针 & 动态创建 • 类作用域 • this指针 • 类的组合 • 常对象和类的常成员 • 类模板 和 函数模板 • 友元
this指针 • this指针是一个对象指针,它是C++编译器为类的每个实例成员函数加上的一个隐含参数,指向调用成员函数的当前对象。 • 在函数内部就可以使用this指针访问当前对象的实例成员。例如:Circle类的实例成员函数area可以写成如下形式: float Circle::area() //定义求面积的公有成员函数 { return this->radius* this->radius* 3.14159; }
使用this指针访问被同名的局部变量屏蔽的类的数据成员使用this指针访问被同名的局部变量屏蔽的类的数据成员 • 除了在实例成员函数中引用当前对象外,this指针还有一些重要的用途,其一是用来访问那些被同名的局部变量屏蔽的类的数据成员。 • 例如: Circle::Circle(float radius) //定义带参数的构造函数 { if(radius >0) this->radius=radius; else this->radius=0; numberOfCircle++; }
使用this指针从实例成员函数返回当前对象的引用 • this指针的另一个重要用途是,从实例成员函数返回当前对象的引用 。 • 例如: Circle &Circle:: compareCircle(Circle &c) { if(c.radius>radius) return c; //返回参数对象 else return *this; //使用this返回当前对象 }
本章要点 • 类的静态成员 • 对象指针 & 动态创建 • 类作用域 • this指针 • 类的组合 • 常对象和类的常成员 • 类模板 和 函数模板 • 友元
类的组合 • 如果一个类的数据成员为另一个类的对象,则称这种结构为类的组合。例如 : • 当创建容器类对象时,也将同时创建所有内嵌的对象;此时编译器将自动调用各个类的构造函数,那么构造函数是按照什么顺序调用的呢 ? class Point { public: Point( ); Point(int x,int y); void setx(int x); int getx( ); private: int x,y; }; class Circle { public: …… private: float radius; PointcenOfCir; };
创建容器类对象时 ,各类构造函数的调用顺序 • 创建容器类对象时,容器类的构造函数首先被调用,在其构造函数体执行之前,先自动调用内嵌对象的构造函数初始化内嵌对象,这些构造函数按照内嵌对象在类中的声明顺序被先后调用;当所有内嵌对象的构造函数执行完毕后,才开始执行容器类对象的构造函数。 • 由于内嵌对象的构造函数是在容器类构造函数被调用后,而即将执行其函数体之前被调用的,所以可以使用容器类构造函数的初始化列表向内嵌对象的构造函数传递参数。语法形式如下: 类名::类名(参数列表) : 内嵌对象1(参数列表),内嵌对象2(参数列表),…… { …… } 其中,“:内嵌对象1(参数列表),内嵌对象2(参数列表),…”称为初始化列表 • 注意:初始化列表的作用仅仅是向内嵌类对象的构造函数传递参数,而不是调用内嵌对象的构造函数。内嵌对象的构造函数是由编译器自动调用的,编译器会根据初始化列表中参数的形式决定调用内嵌对象所在类的哪一个构造函数,如果初始化列表中不包含某个内嵌对象,则编译器会为该对象自动调用默认的构造函数 。 • 再强调一点,内嵌对象构造函数的调用顺序和它们在类中声明的顺序相同,而和容器类初始化列表中的顺序无关。
组合类 示例 classA { public: A( ); A(int,int,int); …… private: int i; B b1,b2; C c; }; class B { public: B( ); B(int); …… private: int j; }; class C { public: C( ); C(int); …… private: int k; }; A::A( ) { i= 0;} /*A类的构造函数通过初始化列表向内 嵌对象的构造函数传递参数*/ A::A(int i1,int j1,int k1): b1(j1), c(k1) { i=i1;} ……
例7.9 类组合时,内嵌对象构造函数的调用顺序
本章要点 • 类的静态成员 • 对象指针 & 动态创建 • 类作用域 • this指针 • 类的组合 • 常对象和类的常成员 • 类模板 和 函数模板 • 友元
7 常对象和类的常成员 • 如同定义基本数据类型的常量一样,可以使用关键字const定义类的对象和类的成员。
常对象 • 用关键字const定义的类的对象称为常对象。常对象一经初始化,其实例数据成员的值就不能被改变。定义常对象的语法如下: const 类名 对象名; 或 const 类名 对象名(参数列表); 例如: const Circle c(10.0); • 使用常对象只能调用类的const成员函数(const成员函数是用关键字const修饰的成员函数,称为常成员函数),而不能调用非const成员函数 。
常数据成员 • 在类的定义中,用关键字const声明的数据成员称为类的常数据成员。常数据成员一经初始化就不能再被改变,而且构造函数初始化常数据成员时,必须使用初始化列表。 • 通常把常数据成员定义为静态成员,使其成为类的一个常量。例如我们想要定义一个代表圆周率的常量PI,并且该常量只在Circle类中可用,则可以为Circle类添加一个静态的常数据成员PI: class Circle { …… private: static const float PI ; //静态的常数据成员 …… }; const float Circle::PI=(float)3.14159;//在类外定义并初始化静态常数据成员
const成员函数 • 在类的定义中,用关键字const声明的成员函数称为类的const成员函数(常成员函数)。const成员函数不能修改对象的实例数据成员。声明const成员函数的语法格式如下: 返回值类型 函数名(形参列表)const; • 定义const成员函数的语法如下: 返回值类型 类名::成员函数名(形参列表)const { …… } • const成员函数不能调用类的非const成员函数。 • 可以使用关键字const重载类的成员函数。重载的const成员函数是为了提供给类的const对象使用的。
本章要点 • 类的静态成员 • 对象指针 & 动态创建 • 类作用域 • this指针 • 类的组合 • 常对象和类的常成员 • 类模板 和 函数模板 • 友元
函数模板 • 函数模板就是将具体函数中的数据类型参数化—即用通用的参数取代函数中具体的数据类型,从而形成一个通用模板来代表数据类型不同的一组函数。定义函数模板的语法如下: template <class T1,class T2,…,class Tn> 返回值类型 函数名(用模板参数取代具体类型的形参列表) { 用模板参数取代具体数据类型的函数体; } • template和class是定义模板函数时必须使用的C++关键字,class也可以用关键字typename代替。 • template后的尖括号称为模板参数列表;其中的T1、T2、…、Ti、…、Tn称为模板参数—参数化的数据类型。 • 函数的返回值类型、形参的数据类型和局部变量的数据类型可以是具体数据类型,也可以是模板参数类型。
函数模板举例 • 下面是模板函数max的的定义: template<class T> T max(T num1, T num2) { T themax; themax = (num1>=num2)? num1: num2; return themax; } • 一个函数模板可以代表一组具体的函数。 • template后的尖括号称为模板参数列表;其中的T1、T2、…、Ti、…、Tn称为模板参数—参数化的数据类型。 • 函数的返回值类型、形参的数据类型和局部变量的数据类型可以是具体数据类型,也可以是模板参数类型。
使用函数模板 • 可以像调用普通函数一样来调用函数模板。例如: int i1,i2,imax; …; imax=max(i1,i2); //使用实际参数调用函数模板 • C++编译器在编译程序时,将根据调用函数模板时所使用的实际参数的具体类型产生具体的函数,并完成函数调用 。例如C++编译器在编译上面的函数调用语句时,将产生下面的具体函数,并实现函数调用: int max(int num1,int num2) { int themax; themax = (num1>=num2)? num1: num2; return themax; }
例 定义和使用函数模板 template<class T> T max(T num1, T num2) { T themax; themax = (num1>=num2)? num1: num2; return themax; } void main() { int i1,i2,imax; float f1,f2,fmax; cout<<"请输入两个整数:"; cin>>i1>>i2; imax=max(i1,i2); //调用函数模板 cout<<"两个整数中较大的一个是:"<<imax<<endl; cout<<"请输入两个实数:"; cin>>f1>>f2; fmax=max(f1,f2); //调用函数模板 cout<<"两个实数中较大的一个是:"<<fmax<<endl; }
类模板 • 类模板就是将类中使用的某些数据类型参数化,以创建一个通用的模板,编译器使用该模板在程序中自动生成具体的类。
定义类模板 • 定义类模板的语法格式如下: template<class 模板参数1,class 模板参数2,……> class 类名 { 用模板参数取代具体数据类型的成员声明; } • 需注意两点: (1)这样定义的类模板的完整的名称为: 类名<模板参数1,模板参数2,……> ; (2)一个类模板中的所有成员函数都是模板函数 例如:例7.11中定义了模板栈类Stack<T>
创建模板类对象 • 使用类模板创建对象的语法形式如下: 用具体类型取代模板参数的类模板名 对象名; 例如: Stack<int> stackOfint; • 这里用具体类型名称int取代了类模板名Stack<T>中的模板参数T,编译器在编译这条语句时,用具体类型int取代类模板中的模板参数T自动创建一个名为Stack<int>的整数“栈”类,并创建Stack<int>类的对象stackOfint >。
例 template <class T> class HTplClass { public: HTplClass() {}; virtual ~HTplClass(){}; void SetR(Ttmp) { m_radius = tmp; }; void Show() { cout<<"半径为:"<<m_radius<<endl; } private: T m_radius; }; HTplClass<double> A; // 替换为HTplClass<int> A; A.SetR(92.22); A.Show();
本章要点 • 类的静态成员 • 对象指针 & 动态创建 • 类作用域 • this指针 • 类的组合 • 常对象和类的常成员 • 类模板 和 函数模板 • 友元
友元 • C++中的类提供了数据封装和隐藏机制:如果函数不是类的成员函数,则其中不能直接访问类的非公有成员。如果想在非成员函数中访问类的非公有成员,必须使用类提供的接口---公有的成员函数(如果有)。然而,调用函数需要时间开销,会降低程序的执行速度;如果对程序的执行速度有严格的要求,而且程序中在类外需要频繁地访问该类的非公有成员,则可以通过声明友元的方法来解决这个问题 。 • C++中可以将一个函数或一个类声明为另一个类的友元,在友元中可以直接访问该类的非公有成员。
友元函数 • 对于一个类而言,可以将一个非成员函数声明为它的友元,则该函数称为类的友元函数。在友元函数中可以使用成员名直接访问类的非公有成员。 • 要由类来声明它的友元函数---即友元函数的声明应写在类中;而函数本身不能将自己定义为类的友元。 • 声明友元函数的方法是使用关键字friend,语法格式如下: friend返回值类型 函数名(形参列表); 这样的声明语句必须写在类定义中,但是该函数不是类的成员函数。
例 将函数compareCircle声明为Circle类的友元函数,并使用它比较两个Circle类对象的大小,返回其中较大的一个。 class Circle { friendCircle compareCircle(Circle &c1,Circle &c2); 。。。 } //定义友元函数时,不需要类名限制,并非成员函数 Circle compareCircle(Circle &c1 , Circle &c2) { if(c1.radius>c2.radius) //友元函数中直接访问私有成员 return c1; else return c2; }
友元类 • 可以将一个类声明为另一个类的友元类。如果将A类声明为B类的友元类,则在A类的所有成员函数中都可以直接访问B类的非公有成员---即A类的所有成员函数都成为了B类的友元函数。声明友元类的语法格式如下: class B { …… friend class A; //将A类声明为B类的友元类 }; • 友元关系是不可逆的---即如果A类是B类的友元类,则B类不一定是A类的友元类。
本章要点 • 类的静态成员 • 对象指针 & 动态创建 • 类作用域 • this指针 • 类的组合 • 常对象和类的常成员 • 类模板 和 函数模板 • 友元