tag 标签: C plus plus

相关博文
  • 热度 23
    2018-2-12 11:15
    1547 次阅读|
    0 个评论
    C++多态(3)——运算符重载
    1. 运算符重载的定义 运算符重载也是实现多态的一个重要手段。运算符重载实现的是编译时的多态,即静态多态性。 C++ 预定义的运算符只针对基本数据类型,而对于自定义数据类型,比如类,却没有类似的运算符。 运算符函数定义的一般形式: 返回类型说明符 operator 运算符符号 ( 参数列表 ) { 函数体 } 2. 运算符重载的特点 运算符重载使用关键字 operator 关键字对重载函数进行标识和定义。运算符有 3 种形式,即前缀、后缀、中缀。它们的 operator 表示形式如下表。 运算符重载时参数个数必须固定。重载函数的参数个数与标准运算符一致,即对于一元运算符(前缀和后缀形式),重载函数有且只有一个参数;对于二元运算符(中缀形式),重载函数有且只有两个参数。 运算符重载针对类对象操作,即重载函数的参数至少有一个属于 class 类型。 #includeiostream using namespace std; class Add { public: int operand; Add() { operand = 0; } Add(int value) { operand = value; } }; Add operator+(Add a, int b) { Add sum; sum.operand = a.operand + b; return sum; } int main() { Add a(5), b; b = a + 8; cout "the sum is: " b.operand endl; system("pause"); return 0; } 3. 运算符重载的形式 运算符的重载形式有两种:一种是重载为类的成员函数,另一种是重载为类的友元函数。对于每一种重载形式,由于运算符不同,可以分为双目运算符和单目运算符的实现。 3.1 运算符重载为类的成员函数 当运算符重载为类的成员函数,称为运算符成员函数。实际使用时,总是通过该类的对象访问重载的运算符。运算符成员函数在类内声明,在类外定义,一般形式为: 返回类型 operator 运算符 ( 参数列表 ) 在类外定义的一般形式为: 返回类型 类名:: operator 运算符 ( 参数表 ) { 函数体 } 3.1.1 双目运算符重载为成员函数 左操作数是访问该重载运算符的对象本身的数据,此时成员运算符函数只有一个参数。如: class X { X operator+(X ob); } 双目运算符重载为成员函数后,就可以在主函数或者其他类中调用。在 C++ 中,一般有显式和隐式两种调用方法。 (1)显式调用:对象名 .operator 运算符号(参数列表),如 aa.operator+(bb); (2)隐式调用:对象名 重载的运算符号 对象名,如 aa+bb ; #includeiostream using namespace std; class A { public: A(int x = 0):n(x) { } int getn() { return n; } A operator+(A a); private: int n; }; A A::operator+(A a) { A temp; temp.n = n + a.n; return temp; } int main() { A ob1(1), ob2(3), ob3, ob4; ob3 = ob1 + ob2; ob4 = ob1.operator+(ob2); cout "ob3.n= " ob3.getn() endl; cout "ob4.n= " ob4.getn() endl; system("pause"); return 0; } 3.1.2 单目运算符重载为成员函数 单目运算符重载为成员函数时,操作数是访问该重载运算符对象本身的数据,由 this 指针指出 , 此时成员运算符函数没有参数。如 Class X { X operator++(); } 与双目运算符的重载类似,单目运算符重载为成员函数后,在调用时也有显式和隐式两种。 (1) 显式调用:对象名 .operator 运算符号 () ,如 a.operator++(); (2) 隐式调用:重载的运算符号 对象名,如 ++a; #includeiostream using namespace std; class A { public: A(int x = 0) { n = x; } int getn() { return n; } A operator++(); private: int n; }; A A::operator++() { ++n; return *this; } int main() { A ob(1); ++ob; cout "ob.n= " ob.getn() endl; ob.operator++(); cout "ob.n=" ob.getn() endl; system("pause"); return 0; } 3.2 运算符重载为类的友元函数 将重载的运算符成员函数定义为类的友元函数,称为友元运算符函数。它不是类的成员,在类内声明原型,在类外定义函数本身。由于友元运算符不是类的成员函数,不属于任何一个类对象,所以没有 this 指针。因此重载双目运算符时要与 2 个参数,重载单目运算符时要一个参数。友元运算符函数在类内声明的一般形式为: friend 返回类型 operator 运算符 ( 参数表 ) 在类外定义的一般形式为: 返回类型 operator 运算符 ( 参数表 ) { 函数体 } 3.2.1 双目运算符重载为友元函数 双目运算符重载为友元函数时,由于没有 this 指针,所以两个操作数都要通过友元运算符函数的参数指出。与运算符重载为类的成员函数类似,双目运算符重载为友元函数后,调用也有显式和隐式两种方法。 (1) 显式调用: operator 运算符号(参数 1 ,参数 2 ),如 operator+(a,b); (2) 隐式调用,如 a+b; #includeiostream using namespace std; class A { public: A(int x = 0, int y = 0):a(x),b(y){} int geta() { return a; } int getb() { return b; } friend A operator+(A p, A q); private: int a, b; }; A operator+(A p, A q) { A temp; temp.a = p.a + q.a; temp.b = p.b + q.b; return temp; } int main() { A ob1(1, 2), ob2(3, 4), ob3, ob4; ob3 = ob1 + ob2; ob4 = operator+(ob1, ob2); cout "ob3.a= " ob3.geta() endl; cout "ob4.a= " ob4.geta() endl; system("pause"); return 0; } 因为友元运算符函数不属于某一个类,在类外定义友元函数不需要加类名和域运算符。双目运算符重载为友元函数和双目运算符重载为成员函数的根本区别在于其操作数的个数不同,前者需要指定 2 个参数,而后者定义时只需要一个参数。 3.2.2 单目运算符重载为友元函数 与单目运算符重载为成员函数不同,单目运算符重载为友元函数时,由于没有 this 指针,所以操作数要通过友元运算符函数的参数指出。调用也分为显式和隐式两种。 (1) 显式: operator 运算符号 ( 参数 ), 如 operator++(a); (2) 隐式:对象名 重载的运算符号 对象名 , 如 ++a; #includeiostream using namespace std; class A { public: A(int x=0):n(x){} int getn() { return n; } friend A operator++(A a); private: int n; }; A operator++(A a) { ++a.n; return a; } int main() { A ob(3); cout "ob.n=" ob.getn() endl; ++ob; cout "ob.n=" ob.getn() endl; operator++(ob); cout "ob.n=" ob.getn() endl; system("pause"); return 0; } 在将运算符重载为友元函数时,除运算符 = () [] - 不能用友元函数重载外,其余运算符都可以重载。此外,使用友元函数重载单目运算符“ ++ ”和“ -- ”时,由于要改变数自身的值,所以应采用引用参数传递操作数,否则会出现错误。 4. 运算符成员函数与友元运算符函数比较 ( 1 )对于双目运算符,运算符成员函数是类的成员,带有 this 指针,只需 1 个参数;而友元运算符函数不是类的成员,不带 this 指针,参数必须是 2 个。对于单目运算符,成员运算符函数不带参数,而友元运算符函数必须带 1 个参数; ( 2 )双目运算符可被重载为友元函数,也可以重载为成员函数,但在运算符的左操作数是一个标准数据类型而右操作数是对象的情况下,必须将它重载为友元函数,原因是标准数据类型的数据不能产生对重载运算符的调用; ( 3 )运算符成员函数与友元运算符函数都可以显式和隐式方式调用; 总的来说,将运算符重载为成员函数或是友元函数,要根据实际情况和使用习惯决定。一般而言,对于双目运算符重载为友元函数较好,若运算符的操作数特别是左操作数需要进行类型转换,必须重载为友元运算符函数。若一个运算符需要修改对象的状态,则选择运算符成员函数较好。
  • 热度 2
    2018-2-10 21:37
    1752 次阅读|
    0 个评论
    C++多继承的二义性问题
    在多重继承中,需要解决的主要问题是标识符不唯一,即二义性问题。比如,当在派生类继承的多个基类中有同名成员时,派生类中就会出现标识符不唯一的情况。 在多重继承中,派生类由多个基类派生时,基类之间用逗号隔开,且每个基类前都必须指明继承方式,否则默认是私有继承。可以通过以下 3 种方法解决二义性问题。 使用运算符"::"; 使用同名覆盖的原则; 使用虚基类; 1. 使用域运算符 如果派生类的基类之间没有继承关系,同时又没有共同的基类,则在引用同名成员时,可以在成员名前面加上类名和域运算符来区别来自不同基类的成员。 2 .使用同名覆盖的原则 在派生类中重新定义与基类中同名的成员(如果是成员函数,则参数表也要相同,否则是重载)以屏蔽掉基类中的同名成员,在引用这些同名成员时,引用的就是派生类中的成员。 #includeiostream using namespace std; class Base { public: int x; void show() { cout "Base,x= " x endl; } }; class Derived :public Base { public: int x; void show() { cout "Derived , x= " x endl; } }; int main() { Derived ob; ob.x = 5; ob.show(); ob.Base::show(); ob.Base::x = 12; ob.Base::show(); system("pause"); return 0; } 3 .虚基类 在多重继承中,要引用派生类的成员时,先是在派生类自身的作用域内寻找,如果找不到再到基类中寻找。这时,如果这些基类有一个共同的基类,派生类访问这个共同基类的成员时,就有可能由于同名成员的问题而发生二义性,此时就需要虚基类来解决。 #includeiostream using namespace std; class Base { public: Base() { x = 1; } protected: int x; }; class Base1 :public Base { public: Base1() { cout "Base1,x= " x endl; } }; class Base2 :public Base { public: Base2() { cout "Base2,x= " x endl; } }; class Derived :public Base1, public Base2 { public: Derived() { cout "Derived,x= " x endl; } }; int main() { Derived obj; system("pause"); return 0; } 上边的代码表面上看类 Base1 和类 Base2 是从同一个基类 Base 派生出来的,但是其对应的却是基类 Base 的两个不同的复制。因此,当派生类 Derived 要访问变量 x 时不知从哪条路径去寻找,从而引发二义性问题。 上述代码对应的类层次结构如图 1 所示,属于非虚基类的类层次结构。要解决该问题,需要引入虚基类,其具体的做法是将公共基类声明为虚基类,这样这个公共基类就只有一个拷贝,从而不会出现二义性问题。虚基类的类层次结构如图 2 所示。 图 1 图 2 3.1 虚基类 虚基类的声明是在派生类的声明过程中进行的,其声明的一般形式为: class 派生类名 : virtual 派生方式 基类名 这种派生方式叫做虚拟继承,虚基类关键字的作用范围和派生方式与一般派生类的声明一样,只对紧跟其后的基类起作用。声明了虚基类以后,虚基类的成员在进一步的派生过程中和派生类一起维护同一个内存拷贝。 #includeiostream using namespace std; class Base { public: Base() { x = 1; } protected: int x; }; class Base1 :virtual public Base { public: Base1() { cout "Base1,x= " x endl; } }; class Base2 :virtual public Base { public: Base2() { cout "Base2,x= " x endl; } }; class Derived :public Base1, public Base2 { public: Derived() { cout "Derived,x= " x endl; } }; int main() { Derived obj; system("pause"); return 0; } 在上述代码中,由于把公共基类Base声明为类Base1和Base2的虚基类,所以由类Base1和类Base2派生的类Derived只有一个基类Base,消除了二义性。 3.2 虚基类构造函数和初始化 虚基类的初始化与一般的多继承的初始化在语法上是一样的,但是构造函数的执行顺序不同。主要在以下方面: 虚基类的构造函数的执行在非虚基类的构造函数之前; 若同一层次中包含多个虚基类,这些虚基类的构造函数按照他们被声明的先后顺序执行; 若虚基类由非虚基类派生而来,则仍然先执行基类的构造函数,再执行派生类的构造函数。 #includeiostream using namespace std; class Base { public: Base(int x1) { x = x1; cout "Base,x= " x endl; } protected: int x; }; class Base1 :virtual public Base { int y; public: Base1(int x1, int y1) :Base(x1) { y = y1; cout "Base1 ,y=" y endl; } }; class Base2 :virtual public Base { int z; public: Base2(int x1, int z1) :Base(x1) { z = z1; cout "Base2,z= " z endl; } }; class Derived :public Base1, public Base2 { int xyz; public: Derived(int x1, int y1, int z1, int xyz1) :Base(x1), Base1(x1,y1), Base2(x1,z1) { xyz = xyz1; cout "Derived,xyz = " xyz endl; } }; int main() { Derived obj(1, 2, 3, 4); system("pause"); return 0; } 上边的代码中,虚基类 Base 的构造函数只执行了一次,这是因为当派生类 Derived 调用了虚基类 Base 的构造函数之后,类 Base1 和 Base2 对虚基类 Base 构造函数的调用就被忽略,这是初始化虚基类和初始化非虚基类的不同。 在使用虚基类时要注意: 虚基类的关键字 virtual 与派生方式的关键字 public,private,protected 的书写位置无关紧要,可以先写虚基类的关键字,也可以先写派生 方式的关键字; 一个基类在作为某些类的虚基类的同时可以作为另一些类的非虚基类; 虚基类构造函数的参数必须由最新派生出来的类负责初始化,即使不是直接继承也应如此。
  • 热度 3
    2018-2-10 11:40
    1282 次阅读|
    0 个评论
    C++类的static成员
    对于某些特定类类型的全体对象而言,访问一个全局对象有时是有必要的。在程序运行的任意点可能需要统计已创建的特定类类型对象的数量,但是全局对象会破坏封装,并且没有安全保护,一般的用户代码就可以修改这个值。类可以定义 static 成员,用于解决同一个类的不同对象之间数据和函数共享的问题,用一个类的不同对象的的静态成员使用同一个内存空间。静态成员包含静态数据成员和静态函数成员。 通常,非 static 数据成员存在于类类型的每个对象中,但是 static 数据成员独立于该类的任意对象而存在,每个 static 数据成员是与类关联的对象,并不是与该类的对象关联。 静态成员函数没有 this 形参,可以直接访问所属类的 static 成员,但是不能直接使用非 static 成员。 使用静态成员的优点:( 1 )有利于类的封装,可以把 static 成员定义为私有成员,防止外部访问;( 2 ) static 成员是与特定的类关联的,在外部必须使用类名字做前缀,程序更加清晰;( 3 ) static 成员的名字是在类的作用域中,可以避免命名冲突。 (一)定义static成员 static 遵循正常的公有、私有访问限制。每一个要定义为 static 的成员前面都需要有 static ,这和访问限制不同。 static 成员函数可以直接定义在声明的后面,也可以在类的外面定义。当在类外面定义时,不需要 static 关键字。 在静态成员函数中,不可以使用 this 指针,因为静态成员函数是同一个类所有对象共有的。同样静态成员函数中也不可以使用非静态的数据成员,只可以使用类的静态数据成员。 class Student { public: static int getCount(); static int count; }; 和普通的数据成员不同, static 数据成员必须在类定义体的外部定义并初始化,定义的时候必须要有类名所前缀。 int Student::count=0; (二)使用static成员 可以使用作用域运算符“::”从类直间调用 static 成员,或者通过对象、引用该类类型对象的指针间接调用。如可以使用下面的方法访问静态成员。 Student::count=0; int n=Student::getCount(); 也可以通过类对象对静态成员进行访问: Student stu1; stu1.count=0; Student *s=syu1; int n=s-getCount(); 当在类的内部使用静态成员时,可以直接使用,不需要作用域运算符。此外可以使用非 static 成员的方法来使用 static 成员。 2.1 使用静态数据成员 #includeiostream using namespace std; class Test { public: static int n; Test(int x) { k = x; n++; } void disp() { cout "n= " n ", k=" k endl; } private: int k; }; int Test::n = 0; int main() { Test t1(10); t1.disp(); Test t2(20); t2.disp(); Test::n++; t2.disp(); system("pause"); return 0; } 2.2 使用静态成员函数 静态成员函数可以直接引用该类的静态数据成员和成员函数,但不能引用非静态成员。如果要引用非静态成员,必须通过参数传递的方式得到对象名,再通过对象名来引用。使用静态成员函数要注意的问题: 静态成员函数可以在类内定义,也可以在类外定义,在类外定义时不用再加关键字 static; 系统限定静态成员函数为内部连接,这样就不会因为与连接文件中的其他同名成员函数相冲突,保证了静态成员函数的安全性; 静态成员函数中没有隐含 this 指针; #includeiostream using namespace std; class Dot { static int t; int a, b; public: Dot(int x = 0, int y = 0) { a = x; b = y; t++; } Dot(Dot d); int geta() { return a; } int getb() { return b; } static void gett() { cout "object id:" t endl; } }; Dot::Dot(Dot d) { a = d.a; b = d.b; t++; } int Dot::t = 0; int main() { Dot::gett(); Dot d1(2, 3); cout "Dot d1:" d1.geta() "," d1.getb() endl; d1.gett(); Dot d2(d1); cout "Dot d2:" d2.geta() ", " d2.getb() endl; Dot::gett(); system("pause"); return 0; }