wordpress主题的安装,甘肃网站建设方案优化,企业名录搜索软件 2022,自己做的网站会被黑吗C 类对象
C类定义
本质上是一个数据类型的蓝图#xff0c;定义了类的对象包含的信息#xff0c;以及可以在这个类对象上执行哪些操作。类的定义是以class开头#xff0c;后面接类的名称。类的主体是包含在一个花括号中#xff0c;类的定义之后#xff0c;必须跟着一…C 类对象
C类定义
本质上是一个数据类型的蓝图定义了类的对象包含的信息以及可以在这个类对象上执行哪些操作。类的定义是以class开头后面接类的名称。类的主体是包含在一个花括号中类的定义之后必须跟着一个分号或者一个声明列表例子关键字public确定了类成员的访问属性使得在类对象作用域中公共成员在类的外部是可以被访问到的。也可以将其定位为private或者protected。
class Box
{public:double length; // 盒子的长度double breadth; // 盒子的宽度double height; // 盒子的高度
};
类对象详解
概念描述类成员函数类的成员函数是指那些把定义和原型写在类定义内部的函数就像类定义中的其他变量一样。类访问修饰符类成员可以被定义为 public、private 或 protected。默认情况下是定义为 private。构造函数 析构函数类的构造函数是一种特殊的函数在创建一个新的对象时调用。类的析构函数也是一种特殊的函数在删除所创建的对象时调用。C 拷贝构造函数拷贝构造函数是一种特殊的构造函数它在创建对象时是使用同一类中之前创建的对象来初始化新创建的对象。C 友元函数友元函数可以访问类的 private 和 protected 成员。C 内联函数通过内联函数编译器试图在调用函数的地方扩展函数体中的代码。C 中的 this 指针每个对象都有一个特殊的指针 this它指向对象本身。C 中指向类的指针指向类的指针方式如同指向结构的指针。实际上类可以看成是一个带有函数的结构。C 类的静态成员类的数据成员和函数成员都可以被声明为静态的。
类成员函数
类的成员函数是指那些把定义和原型写在类定义内部的函数就像类定义中的其他变量一样。类成员函数是类的一个成员它可以操作类的任意对象可以访问对象中的所有成员。需要使用成员函数来访问类的成员而不是直接访问这些类的成员成员函数可以定义在类定义内部或者单独使用范围解析运算符 :: 来定义。在类定义中定义的成员函数把函数声明为内联的即便没有使用 inline 标识符。按照如下方式定义 Volume() 函数
class Box
{public:double length; // 长度double breadth; // 宽度double height; // 高度double getVolume(void){return length * breadth * height;}
};
也可以在类的外部使用范围解析运算符 :: 定义该函数如下所示
double Box::getVolume(void)
{return length * breadth * height;
} :: 运算符之前必须使用类名。调用成员函数是在对象上使用点运算符.这样它就能操作与该对象相关的数据如下所示
Box myBox; // 创建一个对象myBox.getVolume(); // 调用该对象的成员函数
访问修饰符 关键字public、private和protected称之为访问修饰符
class Base {public:// 公有成员protected:// 受保护成员private:// 私有成员};
公有成员在程序中类的外部是可访问的。可以不使用任何成员函数来设置和获取公有变量的值私有成员变量或函数在类的外部是不可访问的甚至是不可查看的。只有类和友元函数可以访问私有成员。默认情况下类的所有成员都是私有的。例如在下面的类中width 是一个私有成员这意味着如果没有使用任何访问修饰符类的成员将被假定为私有成员。一般会在私有区域定义数据在公有区域定义相关的函数以便在类的外部也可以调用这些函数
#include iostreamusing namespace std;class Box
{public:double length;void setWidth( double wid );double getWidth( void );private:double width;
};// 成员函数定义
double Box::getWidth(void)
{return width ;
}void Box::setWidth( double wid )
{width wid;
}// 程序的主函数
int main( )
{Box box;// 不使用成员函数设置长度box.length 10.0; // OK: 因为 length 是公有的cout Length of box : box.length endl;// 不使用成员函数设置宽度// box.width 10.0; // Error: 因为 width 是私有的box.setWidth(10.0); // 使用成员函数设置宽度cout Width of box : box.getWidth() endl;return 0;
} 保护成员变量或函数与私有成员十分相似但有一点不同保护成员在派生类即子类中是可访问的。例子中从父类 Box 派生了一个子类 smallBox。在这里 width 成员可被派生类 smallBox 的任何成员函数访问。
继承中的特点
有public, protected, private三种继承方式它们相应地改变了基类成员的访问属性。
public 继承基类 public 成员protected 成员private 成员的访问属性在派生类中分别变成public, protected, privateprotected 继承基类 public 成员protected 成员private 成员的访问属性在派生类中分别变成protected, protected, privateprivate 继承基类 public 成员protected 成员private 成员的访问属性在派生类中分别变成private, private, private
但无论哪种继承方式上面两点都没有改变
private 成员只能被本类成员类内和友元访问不能被派生类访问protected 成员可以被派生类访问。默认使用protected继承在struct中默认使用public继承
继承方式基类的public成员基类的protected成员基类的private成员继承引起的访问控制关系变化概括public继承仍为public成员仍为protected成员不可见基类的非私有成员在子类的访问属性不变protected继承变为protected成员变为protected成员不可见基类的非私有成员都为子类的保护成员private继承变为private成员变为private成员不可见基类中的非私有成员都称为子类的私有成员
类构造函数 析构函数
类的构造函数
类的构造函数是类的一种特殊的成员函数它会在每次创建类的新对象时执行。构造函数的名称与类的名称是完全相同的并且不会返回任何类型也不会返回 void。构造函数可用于为某些成员变量设置初始值。
#include iostreamusing namespace std;class Line
{public:void setLength( double len );double getLength( void );Line(); // 这是构造函数private:double length;
};// 成员函数定义包括构造函数
Line::Line(void)
{cout Object is being created endl;
}void Line::setLength( double len )
{length len;
}double Line::getLength( void )
{return length;
}
// 程序的主函数
int main( )
{Line line;// 设置长度line.setLength(6.0); cout Length of line : line.getLength() endl;return 0;
}
带参数的构造函数
默认的构造函数没有任何参数但如果需要构造函数也可以带有参数。这样在创建对象时就会给对象赋初始值如下面的例子所示
#include iostreamusing namespace std;class Line
{public:void setLength( double len );double getLength( void );Line(double len); // 这是构造函数private:double length;
};// 成员函数定义包括构造函数
Line::Line( double len)
{cout Object is being created, length len endl;length len;
}void Line::setLength( double len )
{length len;
}double Line::getLength( void )
{return length;
}
// 程序的主函数
int main( )
{Line line(10.0);// 获取默认设置的长度cout Length of line : line.getLength() endl;// 再次设置长度line.setLength(6.0); cout Length of line : line.getLength() endl;return 0;
}
使用初始化列表来初始化字段
使用初始化列表来初始化字段
Line::Line( double len): length(len)
{cout Object is being created, length len endl;
}
等效于以下的语法
Line::Line( double len)
{length len;cout Object is being created, length len endl;
}假设有一个类 C具有多个字段 X、Y、Z 等需要进行初始化同理地可以使用上面的语法只需要在不同的字段使用逗号进行分隔如下所示
C::C( double a, double b, double c): X(a), Y(b), Z(c)
{....
}
类的析构函数
类的析构函数是类的一种特殊的成员函数它会在每次删除所创建的对象时执行。析构函数的名称与类的名称是完全相同的只是在前面加了个波浪号~作为前缀它不会返回任何值也不能带有任何参数。析构函数有助于在跳出程序比如关闭文件、释放内存等前释放资源。
拷贝构造函数
拷贝构造函数是一种特殊的构造函数它在创建对象时是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于
通过使用另一个同类型的对象来初始化新创建的对象。复制对象把它作为参数传递给函数。复制对象并从函数返回这个对象。
如果在类中没有定义拷贝构造函数编译器会自行定义一个。如果类带有指针变量并有动态内存分配则它必须有一个拷贝构造函数。拷贝构造函数的最常见形式如下
classname (const classname obj) {// 构造函数的主体
}
obj 是一个对象引用该对象是用于初始化另一个对象的
#include iostreamusing namespace std;class Line{public:int getLength(void);Line(int len);//简单的构造函数Line(const Line Obj);//拷贝构造函数~Line();//析构函数private:int *ptr;
};//成员函数的定义包括析构函数
Line::Line(int len) {cout调用构造函数endl;//为指针分配内存ptr new int;*ptr len;
}
Line::Line(const Line Obj) {cout 调用拷贝构造函数并且为指针ptr分配内存 endl;ptr new int;*ptr *Obj.ptr;//拷贝值
}
Line::~Line() {cout释放内存endl;delete ptr;
}
int Line::getLength(void) {return *ptr;
}
void display(Line obj){coutline 大小obj.getLength()endl;
}
int main(){Line line(10);display(line);return 0;
}
使用已有的同类型的对象来初始化新创建的对象
int main(){Line line1(10);Line line2 line1;//这里调用了拷贝构造函数display(line1);display(line2);return 0;
}
C 友元函数
类的友元函数是定义在类外部但有权访问类的所有私有private成员和保护protected成员。尽管友元函数的原型有在类的定义中出现过但是友元函数并不是成员函数。友元可以是一个函数该函数被称为友元函数友元也可以是一个类该类被称为友元类在这种情况下整个类及其所有成员都是友元。如果要声明函数为一个类的友元需要在类定义中该函数原型前使用关键字 friend如下所示
class Box
{double width;
public:double length;friend void printWidth( Box box );void setWidth( double wid );
};
例子
#include iostreamusing namespace std;class Box
{double width;
public:friend void printWidth( Box box );void setWidth( double wid );
};// 成员函数定义
void Box::setWidth( double wid )
{width wid;
}// 请注意printWidth() 不是任何类的成员函数
void printWidth( Box box )
{/* 因为 printWidth() 是 Box 的友元它可以直接访问该类的任何成员 */cout Width of box : box.width endl;
}// 程序的主函数
int main( )
{Box box;// 使用成员函数设置宽度box.setWidth(10.0);// 使用友元函数输出宽度printWidth( box );return 0;
}
C内联函数
C 内联函数是通常与类一起使用。如果一个函数是内联的那么在编译时编译器会把该函数的代码副本放置在每个调用该函数的地方。对内联函数进行任何修改都需要重新编译函数的所有客户端因为编译器需要重新更换一次所有的代码否则将会继续使用旧的函数。如果想把一个函数定义为内联函数则需要在函数名前面放置关键字 inline在调用函数之前需要对函数进行定义。如果已定义的函数多于一行编译器会忽略 inline 限定符。在类定义中的定义的函数都是内联函数即使没有用 inline 说明符。
this指针
每一个对象都可以通过使用this指针来访问自己的地址this是一个所有成员函数的隐含参数因此在成员函数内部可以用来指向调用对象。友元函数没有this指针因为友元不是类的成员只有成员才可以使用this指针。
#include iostreamusing namespace std;class Box
{public:// 构造函数定义Box(double l2.0, double b2.0, double h2.0){cout Constructor called. endl;length l;breadth b;height h;}double Volume(){return length * breadth * height;}int compare(Box box){return this-Volume() box.Volume();}private:double length; // Length of a boxdouble breadth; // Breadth of a boxdouble height; // Height of a box
};int main(void)
{Box Box1(3.3, 1.2, 1.5); // Declare box1Box Box2(8.5, 6.0, 2.0); // Declare box2if(Box1.compare(Box2)){cout Box2 is smaller than Box1 endl;}else{cout Box2 is equal to or larger than Box1 endl;}return 0;
}
指向类的指针
一个指向 C 类的指针与指向结构的指针类似访问指向类的指针的成员需要使用成员访问运算符 -就像访问指向结构的指针一样。与所有的指针一样您必须在使用指针之前对指针进行初始化。
#include iostreamusing namespace std;class Box
{public:// 构造函数定义Box(double l2.0, double b2.0, double h2.0){cout Constructor called. endl;length l;breadth b;height h;}double Volume(){return length * breadth * height;}private:double length; // Length of a boxdouble breadth; // Breadth of a boxdouble height; // Height of a box
};int main(void)
{Box Box1(3.3, 1.2, 1.5); // Declare box1Box Box2(8.5, 6.0, 2.0); // Declare box2Box *ptrBox; // Declare pointer to a class.// 保存第一个对象的地址ptrBox Box1;// 现在尝试使用成员访问运算符来访问成员cout Volume of Box1: ptrBox-Volume() endl;// 保存第二个对象的地址ptrBox Box2;// 现在尝试使用成员访问运算符来访问成员cout Volume of Box2: ptrBox-Volume() endl;return 0;
}
C 类的静态成员
使用 static 关键字来把类成员定义为静态的。当声明类的成员为静态时这意味着无论创建多少个类的对象静态成员都只有一个副本。静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句在创建第一个对象时所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化
静态成员函数
如果把函数成员声明为静态的就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用静态函数只要使用类名加范围解析运算符 :: 就可以访问。静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。静态成员函数有一个类范围他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建。
静态成员函数与普通成员函数的区别
静态成员函数没有 this 指针只能访问静态成员包括静态成员变量和静态成员函数。普通成员函数有 this 指针可以访问类中的任意成员而静态成员函数没有 this 指针。
C 继承
面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类这使得创建和维护一个应用程序变得更容易。这样做也达到了重用代码功能和提高执行效率的效果。当创建一个类时您不需要重新编写新的数据成员和成员函数只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类新建的类称为派生类。继承代表了 is a 关系。例如哺乳动物是动物狗是哺乳动物因此狗是动物等等。
基类 派生类
一个类可以派生自多个类这意味着它可以从多个基类继承数据和函数。定义一个派生类我们使用一个类派生列表来指定基类。类派生列表以一个或多个基类命名形式如下
class derived-class: access-specifier base-class其中访问修饰符 access-specifier 是 public、protected 或 private 其中的一个base-class 是之前定义过的某个类的名称。如果未使用访问修饰符 access-specifier则默认为 private。
假设有一个基类 ShapeRectangle 是它的派生类如下所示
#include iostreamusing namespace std;// 基类
class Shape
{public:void setWidth(int w){width w;}void setHeight(int h){height h;}protected:int width;int height;
};// 派生类
class Rectangle: public Shape
{public:int getArea(){ return (width * height); }
};int main(void)
{Rectangle Rect;Rect.setWidth(5);Rect.setHeight(7);// 输出对象的面积cout Total area: Rect.getArea() endl;return 0;
}
访问控制和继承
派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问则应在基类中声明为 private。可以根据访问权限总结出不同的访问类型如下所示
访问publicprotectedprivate同一个类yesyesyes派生类yesyesno外部的类yesnono
一个派生类继承了所有的基类方法但下列情况除外
基类的构造函数、析构函数和拷贝构造函数基类的重载运算符基类的友元函数
继承类型
当一个类派生自基类该基类可以被继承为 public、protected 或 private 几种类型。继承类型是通过上面讲解的访问修饰符 access-specifier 来指定的。
几乎不使用 protected 或 private 继承通常使用 public 继承。当使用不同类型的继承时遵循以下几个规则
公有继承public当一个类派生自公有基类时基类的公有成员也是派生类的公有成员基类的保护成员也是派生类的保护成员基类的私有成员不能直接被派生类访问但是可以通过调用基类的公有和保护成员来访问。保护继承protected 当一个类派生自保护基类时基类的公有和保护成员将成为派生类的保护成员。私有继承private当一个类派生自私有基类时基类的公有和保护成员将成为派生类的私有成员。
多继承
多继承即一个子类可以有多个父类它继承了多个父类的特性。C 类可以从多个类继承成员语法如下
class 派生类名:继承方式1基类名1,继承方式2基类名2,…
{
派生类类体
};
其中访问修饰符继承方式是 public、protected 或 private 其中的一个用来修饰每个基类各个基类之间用逗号分隔如上所示。现在一起看看下面的实例
#include iostreamusing namespace std;// 基类 Shape
class Shape
{public:void setWidth(int w){width w;}void setHeight(int h){height h;}protected:int width;int height;
};// 基类 PaintCost
class PaintCost
{public:int getCost(int area){return area * 70;}
};// 派生类
class Rectangle: public Shape, public PaintCost
{public:int getArea(){ return (width * height); }
};int main(void)
{Rectangle Rect;int area;Rect.setWidth(5);Rect.setHeight(7);area Rect.getArea();// 输出对象的面积cout Total area: Rect.getArea() endl;// 输出总花费cout Total paint cost: $ Rect.getCost(area) endl;return 0;
}
另外多继承(环状继承)
A-D, B-D, C-(AB)
class D{......};
class B: public D{......};
class A: public D{......};
class C: public B, public A{.....};
这个继承会使D创建两个对象,要解决上面问题就要用虚拟继承格式。格式class 类名: virtual 继承方式 父类名
class D{......};
class B: virtual public D{......};
class A: virtual public D{......};
class C: public B, public A{.....};
例子
#include iostreamusing namespace std;
//基类class D
{
public:D(){coutD()endl;}~D(){cout~D()endl;}
protected:int d;
};class B:virtual public D
{
public:B(){coutB()endl;}~B(){cout~B()endl;}
protected:int b;
};class A:virtual public D
{
public:A(){coutA()endl;}~A(){cout~A()endl;}
protected:int a;
};class C:public B, public A
{
public:C(){coutC()endl;}~C(){cout~C()endl;}
protected:int c;
};int main()
{cout Hello World! endl;C c; //D, B, A ,Ccoutsizeof(c)endl;return 0;
}
注意事项
与类同名的函数是构造函数。~ 类名的是类的析构函数。
重载运算符和重载函数
C 允许在同一作用域中的某个函数和运算符指定多个定义分别称为函数重载和运算符重载。重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明但是它们的参数列表和定义实现不相同。当您调用一个重载函数或重载运算符时编译器通过把您所使用的参数类型与定义中的参数类型进行比较决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程称为重载决策。
C 中的函数重载
在同一个作用域内可以声明几个功能类似的同名函数但是这些同名函数的形式参数指参数的个数、类型或者顺序必须不同。不能仅通过返回类型的不同来重载函数。下面的实例中同名函数 print() 被用于输出不同的数据类型
#include iostream
using namespace std;class printData
{public:void print(int i) {cout 整数为: i endl;}void print(double f) {cout 浮点数为: f endl;}void print(char c[]) {cout 字符串为: c endl;}
};int main(void)
{printData pd;// 输出整数pd.print(5);// 输出浮点数pd.print(500.263);// 输出字符串char c[] Hello C;pd.print(c);return 0;
}
C 中的运算符重载
可以重定义或重载大部分 C 内置的运算符。这样就可以使用自定义类型的运算符。重载的运算符是带有特殊名称的函数函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样重载运算符有一个返回类型和一个参数列表。
Box operator(const Box);
声明加法运算符用于把两个 Box 对象相加返回最终的 Box 对象。大多数的重载运算符可被定义为普通的非成员函数或者被定义为类成员函数。如果我们定义上面的函数为类的非成员函数那么我们需要为每次操作传递两个参数如下所示
Box operator(const Box, const Box);
下面的实例使用成员函数演示了运算符重载的概念。在这里对象作为参数进行传递对象的属性使用 this 运算符进行访问如下所示
#include iostream
using namespace std;class Box
{public:double getVolume(void){return length * breadth * height;}void setLength( double len ){length len;}void setBreadth( double bre ){breadth bre;}void setHeight( double hei ){height hei;}// 重载 运算符用于把两个 Box 对象相加Box operator(const Box b){Box box;box.length this-length b.length;box.breadth this-breadth b.breadth;box.height this-height b.height;return box;}private:double length; // 长度double breadth; // 宽度double height; // 高度
};
// 程序的主函数
int main( )
{Box Box1; // 声明 Box1类型为 BoxBox Box2; // 声明 Box2类型为 BoxBox Box3; // 声明 Box3类型为 Boxdouble volume 0.0; // 把体积存储在该变量中// Box1 详述Box1.setLength(6.0); Box1.setBreadth(7.0); Box1.setHeight(5.0);// Box2 详述Box2.setLength(12.0); Box2.setBreadth(13.0); Box2.setHeight(10.0);// Box1 的体积volume Box1.getVolume();cout Volume of Box1 : volume endl;// Box2 的体积volume Box2.getVolume();cout Volume of Box2 : volume endl;// 把两个对象相加得到 Box3Box3 Box1 Box2;// Box3 的体积volume Box3.getVolume();cout Volume of Box3 : volume endl;return 0;
}
可重载运算符
双目算术运算符 (加)-(减)*(乘)/(除)% (取模)关系运算符(等于)! (不等于) (小于) (大于(小于等于)(大于等于)逻辑运算符||(逻辑或)(逻辑与)!(逻辑非)单目运算符 (正)-(负)*(指针)(取地址)自增自减运算符(自增)--(自减)位运算符| (按位或) (按位与)~(按位取反)^(按位异或), (左移)(右移)赋值运算符, , -, *, / , % , , |, ^, , 空间申请与释放new, delete, new[ ] , delete[]其他运算符()(函数调用)-(成员访问),(逗号)[](下标)
不可重载运算符
.成员访问运算符.*, -*成员指针访问运算符::域运算符sizeof长度运算符?:条件运算符# 预处理符号
运算符重载的案例
序号运算符和实例1一元运算符重载2二元运算符重载3关系运算符重载4输入/输出运算符重载5 和 -- 运算符重载6赋值运算符重载7函数调用运算符 () 重载8下标运算符 [] 重载9类成员访问运算符 - 重载
一元运算符重载 一元运算符只对一个操作数进行操作下面是一元运算符的实例
递增运算符 和递减运算符 -- 一元减运算符即负号 - 逻辑非运算符 !
一元运算符通常出现在它们所操作的对象的左边比如 !obj、-obj 和 obj但有时它们也可以作为后缀比如 obj 或 obj--。下面的实例演示了如何重载一元减运算符 - 。
二元运算符重载
二元运算符需要两个参数下面是二元运算符的实例。平常使用的加运算符 、减运算符 - 、乘运算符 * 和除运算符 / 都属于二元运算符。就像加()运算符。下面的实例演示了如何重载加运算符
#include iostream
using namespace std;class Box
{double length; // 长度double breadth; // 宽度double height; // 高度
public:double getVolume(void){return length * breadth * height;}void setLength( double len ){length len;}void setBreadth( double bre ){breadth bre;}void setHeight( double hei ){height hei;}// 重载 运算符用于把两个 Box 对象相加Box operator(const Box b){Box box;box.length this-length b.length;box.breadth this-breadth b.breadth;box.height this-height b.height;return box;}
};
// 程序的主函数
int main( )
{Box Box1; // 声明 Box1类型为 BoxBox Box2; // 声明 Box2类型为 BoxBox Box3; // 声明 Box3类型为 Boxdouble volume 0.0; // 把体积存储在该变量中// Box1 详述Box1.setLength(6.0); Box1.setBreadth(7.0); Box1.setHeight(5.0);// Box2 详述Box2.setLength(12.0); Box2.setBreadth(13.0); Box2.setHeight(10.0);// Box1 的体积volume Box1.getVolume();cout Volume of Box1 : volume endl;// Box2 的体积volume Box2.getVolume();cout Volume of Box2 : volume endl;// 把两个对象相加得到 Box3Box3 Box1 Box2;// Box3 的体积volume Box3.getVolume();cout Volume of Box3 : volume endl;return 0;
}关系运算符重载
C 语言支持各种关系运算符 、 、 、 、 等等它们可用于比较 C 内置的数据类型可以重载任何一个关系运算符重载后的关系运算符可用于比较类的对象。下面的实例演示了如何重载 运算符
输入/输出运算符重载
C 能够使用流提取运算符 和流插入运算符 来输入和输出内置的数据类型。可以重载流提取运算符和流插入运算符来操作对象等用户自定义的数据类型。在这里有一点很重要我们需要把运算符重载函数声明为类的友元函数这样我们就能不用创建对象而直接调用函数。下面的实例演示了如何重载提取运算符 和插入运算符 。
C多态
多态按照字面意思是指多种形态。当类之间存在层次结构的时候并且类之间是通过继承关系关联的时候就会遇到多态。多态是指调用成员函数的时候会根据调用函数的对象的类型不同来执行不同的函数。例子基类shape会被派生为两个类
#include iostream
using namespace std;class Shape {protected:int width, height;public:Shape( int a0, int b0){width a;height b;}int area(){cout Parent class area : endl;return 0;}
};
class Rectangle: public Shape{public:Rectangle( int a0, int b0):Shape(a, b) { }int area (){ cout Rectangle class area : endl;return (width * height); }
};
class Triangle: public Shape{public:Triangle( int a0, int b0):Shape(a, b) { }int area (){ cout Triangle class area : endl;return (width * height / 2); }
};
// 程序的主函数
int main( )
{Shape *shape;Rectangle rec(10,7);Triangle tri(10,5);// 存储矩形的地址shape rec;// 调用矩形的求面积函数 areashape-area();// 存储三角形的地址shape tri;// 调用三角形的求面积函数 areashape-area();return 0;
}//Parent class area
//Parent class area
出现上面的错误原因是 调用函数 area() 被编译器设置为基类中的版本这就是所谓的静态多态或静态链接 - 函数调用在程序执行前就准备好了。有时候这也被称为早绑定因为 area() 函数在程序编译期间就已经设置好了。 正确的使用方法在 Shape 类中area() 的声明前放置关键字 virtual如下所示
class Shape {protected:int width, height;public:Shape( int a0, int b0){width a;height b;}virtual int area(){cout Parent class area : endl;return 0;}
};
虚函数
虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时会告诉编译器不要静态链接到该函数。在程序中任意点可以根据所调用的对象类型来选择调用的函数这种操作被称为动态链接或后期绑定。
纯虚函数
在基类中定义虚函数以便在派生类中重新定义该函数从而更好地适用于对象但是在基类中又不能对虚函数给出有意义的实现这个时候就会用到纯虚函数。可以把基类中的虚函数 area() 改写如下
class Shape {protected:int width, height;public:Shape( int a0, int b0){width a;height b;}// pure virtual functionvirtual int area() 0;
}; 0 告诉编译器函数没有主体上面的虚函数是纯虚函数。
C 数据抽象
数据抽象是指只向外界提供关键信息并隐藏其后台的实现细节即只表现必要的信息而不呈现细节这个依赖于接口和实现相互分离的编程设计技术。举一个现实生活中的真实例子比如一台电视机用户可以打开和关闭、切换频道、调整音量、添加外部组件如喇叭、录像机、DVD 播放器但是不知道它的内部实现细节也就是说并不知道它是如何通过缆线接收信号如何转换信号并最终显示在屏幕上。因此可以说电视把它的内部实现和外部接口分离开了无需知道它的内部实现原理直接通过它的外部接口比如电源按钮、遥控器、声量控制器就可以操控电视。就 C 编程而言C 类为数据抽象提供了可能。它向外界提供了大量用于操作对象数据的公共方法也就是说外界实际上并不清楚类的内部实现。例如程序可以调用 sort() 函数而不需要知道函数中排序数据所用到的算法。实际上函数排序的底层实现会因库的版本不同而有所差异只要接口不变函数调用就可以照常工作。在 C 中可以使用类来定义用户自己的抽象数据类型ADT。可以使用类 iostream 的 cout 对象来输出数据到标准输出如下所示
#include iostream
using namespace std;int main( )
{cout Hello C endl;return 0;
}
不需要理解 cout 是如何在用户的屏幕上显示文本。只需要知道公共接口即可cout 的底层实现可以自由改变。
访问标签强制抽象
在 C 中可以使用访问标签来定义类的抽象接口。一个类可以包含零个或多个访问标签
使用公共标签定义的成员都可以访问该程序的所有部分。一个类型的数据抽象视图是由它的公共成员来定义的。使用私有标签定义的成员无法访问到使用类的代码。私有部分对使用类型的代码隐藏了实现细节。
访问标签出现的频率没有限制。每个访问标签指定了紧随其后的成员定义的访问级别。指定的访问级别会一直有效直到遇到下一个访问标签或者遇到类主体的关闭右括号为止。
数据抽象的好处
数据抽象有两个重要的优势
类的内部受到保护不会因无意的用户级错误导致对象状态受损。类实现可能随着时间的推移而发生变化以便应对不断变化的需求或者应对那些要求不改变用户级代码的错误报告。
如果只在类的私有部分定义数据成员编写该类的作者就可以随意更改数据。如果实现发生改变则只需要检查类的代码看看这个改变会导致哪些影响。如果数据是公有的则任何直接访问旧表示形式的数据成员的函数都可能受到影响。
数据抽象的实例
C 程序中任何带有公有和私有成员的类都可以作为数据抽象的实例。请看下面的实例
#include iostream
using namespace std;class Adder{public:// 构造函数Adder(int i 0){total i;}// 对外的接口void addNum(int number){total number;}// 对外的接口int getTotal(){return total;};private:// 对外隐藏的数据int total;
};
int main( )
{Adder a;a.addNum(10);a.addNum(20);a.addNum(30);cout Total a.getTotal() endl;return 0;
}
上面的类把数字相加并返回总和。公有成员 addNum 和 getTotal 是对外的接口用户需要知道它们以便使用类。私有成员 total 是用户不需要了解的但又是类能正常工作所必需的。
设计策略
抽象把代码分离为接口和实现。所以在设计组件时必须保持接口独立于实现这样如果改变底层实现接口也将保持不变。在这种情况下不管任何程序使用接口接口都不会受到影响只需要将最新的实现重新编译即可。
C 数据封装
所有的 C 程序都有以下两个基本要素
程序语句代码这是程序中执行动作的部分它们被称为函数。程序数据数据是程序的信息会受到程序函数的影响。
总结
封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念这样能避免受到外界的干扰和误用从而确保了安全。数据封装引申出了另一个重要的 OOP 概念即数据隐藏。数据封装是一种把数据和操作数据的函数捆绑在一起的机制数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。C 通过创建类来支持封装和数据隐藏public、protected、private。我们已经知道类包含私有成员private、保护成员protected和公有成员public成员。默认情况下在类中定义的所有项目都是私有的。例如
class Box
{public:double getVolume(void){return length * breadth * height;}private:double length; // 长度double breadth; // 宽度double height; // 高度
};
变量 length、breadth 和 height 都是私有的private。这意味着它们只能被 Box 类中的其他成员访问而不能被程序中其他部分访问。这是实现封装的一种方式。为了使类中的成员变成公有的即程序中的其他部分也能访问必须在这些成员前使用 public 关键字进行声明。所有定义在 public 标识符后边的变量或函数可以被程序中所有其他的函数访问。把一个类定义为另一个类的友元类会暴露实现细节从而降低了封装性。理想的做法是尽可能地对外隐藏每个类的实现细节。 C中, 虚函数可以为private, 并且可以被子类覆盖因为虚函数表的传递但子类不能调用父类的private虚函数。虚函数的重载性和它声明的权限无关。一个成员函数被定义为private属性标志着其只能被当前类的其他成员函数(或友元函数)所访问。而virtual修饰符则强调父类的成员函数可以在子类中被重写因为重写的时候并没有与父类发生任何的调用关系故而重写是被允许的。 编译器不检查虚函数的各类属性。被virtual修饰的成员函数不论他们是private、protect或是public的都会被统一的放置到虚函数表中。对父类进行派生时子类会继承到拥有相同偏移地址的虚函数表相同偏移地址指各虚函数相对于VPTR指针的偏移则子类就会被允许对这些虚函数进行重载。且重载时可以给重载函数定义新的属性例如public其只标志着该重载函数在该子类中的访问属性为public和父类的private属性没有任何关系 纯虚函数可以设计成私有的不过这样不允许在本类之外的非友元函数中直接调用它子类中只有覆盖这种纯虚函数的义务却没有调用它的权利。
C 接口抽象类
接口描述了类的行为和功能而不需要完成类的特定实现。C 接口是使用抽象类来实现的抽象类与数据抽象互不混淆数据抽象是一个把实现细节与相关的数据分离开的概念。如果类中有一个函数被声明为纯虚函数则这个类就是抽象类。纯虚函数是通过在声明中使用 0 来指定的如下所示
class Box
{public:// 纯虚函数virtual double getVolume() 0;private:double length; // 长度double breadth; // 宽度double height; // 高度
};
设计抽象类通常称为 ABC的目的是为了给其他类提供一个可以继承的适当的基类。抽象类不能被用于实例化对象它只能作为接口使用。如果试图实例化一个抽象类的对象会导致编译错误。因此如果一个 ABC 的子类需要被实例化则必须实现每个虚函数这也意味着 C 支持使用 ABC 声明接口。如果没有在派生类中重写纯虚函数就尝试实例化该类的对象会导致编译错误。可用于实例化对象的类被称为具体类。
抽象类的实例
请看下面的实例基类 Shape 提供了一个接口 getArea()在两个派生类 Rectangle 和 Triangle 中分别实现了 getArea()
#include iostreamusing namespace std;// 基类
class Shape
{
public:// 提供接口框架的纯虚函数virtual int getArea() 0;void setWidth(int w){width w;}void setHeight(int h){height h;}
protected:int width;int height;
};// 派生类
class Rectangle: public Shape
{
public:int getArea(){ return (width * height); }
};
class Triangle: public Shape
{
public:int getArea(){ return (width * height)/2; }
};int main(void)
{Rectangle Rect;Triangle Tri;Rect.setWidth(5);Rect.setHeight(7);// 输出对象的面积cout Total Rectangle area: Rect.getArea() endl;Tri.setWidth(5);Tri.setHeight(7);// 输出对象的面积cout Total Triangle area: Tri.getArea() endl; return 0;
}
设计策略
面向对象的系统可能会使用一个抽象基类为所有的外部应用程序提供一个适当的、通用的、标准化的接口。然后派生类通过继承抽象基类就把所有类似的操作都继承下来。外部应用程序提供的功能即公有函数在抽象基类中是以纯虚函数的形式存在的。这些纯虚函数在相应的派生类中被实现。这个架构也使得新的应用程序可以很容易地被添加到系统中即使是在系统被定义之后依然可以如此。