2017.7.6C++--------类

了解class的概念,了解继承、虚函数、重载、重写、多态、纯虚函数、多重继承等概念,它们是面向对象编程在语言层面的基础。

面向对象更重要的是思想,很多C系程序员一直都在用类来写过程式程序。

class:

C++中使用关键字 class 来定义类, 其基本形式如下:
class 类名
{

public:
//行为或属性 
protected:
//行为或属性
private:
//行为或属性
};

示例:
     定义一个点(Point)类, 具有以下属性和方法:
     ■ 属性: x坐标, y坐标
     ■ 方法: 1.设置x,y的坐标值; 2.输出坐标的信息。
实现代码:
class Point
{
public:
     void setPoint(int x, int y);
     void printPoint();
 
private:
     int xPos;
     int yPos;
};  
代码说明:
     上段代码中定义了一个名为 Point 的类, 具有两个私密属性, int型的xPos和yPos, 分别用来表示x点和y点。
     在方法上, setPoint 用来设置属性, 也就是 xPos 和 yPos 的值; printPoint 用来输出点的信息。

1 数据抽象和封装
     抽象是通过特定的实例抽取共同特征以后形成概念的过程。一个对象是现实世界中一个实体的抽象,一个类是一组对象的抽象。
     封装
是将相关的概念组成一个单元,然后通过一个名称来引用它。面向对象封装是将数据和基于数据的操作封装成一个整体对象,对数据的访问或修改只能通过对象对外提供的接口进行。

2 类定义
     几个重要名词:
(1) 类名
     遵循一般的命名规则; 字母,数字和下划线组合,不要以数字开头。
(2) 类成员
     类可以没有成员,也可以定义多个成员。成员可以是数据、函数或类型别名。所有的成员都必须在类的内部声明。
     没有成员的类是空类,空类也占用空间。
class People
{
};
sizeof(
People) = 1;    

(3) 构造函数
     构造函数是一个特殊的、与类同名的成员函数,用于给每个数据成员设置适当的初始值。

C++中的构造函数

C++中的构造函数可以分为4类:
(1)默认构造函数。以Student类为例,默认构造函数的原型为
Student();//没有参数
(2)初始化构造函数
Student(int num,int age);//有参数
(3)复制(拷贝)构造函数
Student(Student&);//形参是本类对象的引用
(4)转换构造函数
Student(int r) ;//形参是其他类型变量,且只有一个形参

默认和初始化构造函数

默认构造函数和初始化构造函数在定义类的对象的时候,完成对象的初始化工作。

#include<iostream>
using namespace std;

class People
{

};

//int main(){
// //cout << sizeof(People) << endl;
//}
class Student
{
public:
 //默认构造函数
 Student()
 {
  num = 1001;
  age = 18;
 }
 //初始化构造函数
 Student(int n, int a) :num(n), age(a){}
 //显示变量值函数,成员函数
 void showVariable(){
  cout << num <<"and"<<age<< endl;
 }
private:
 int num;
 int age;
};
int main()
{
 //用默认构造函数初始化对象S1
 Student s1;
 s1.showVariable();
 //用初始化构造函数初始化对象S2
 Student s2(1002, 18);
 s2.showVariable();
 return 0;
}

复制(拷贝)构造函数

复制构造函数用于复制本类的对象,如:
Student s2(1002,1008);
Student s3(s2);//将对象s2复制给s3。注意复制和赋值的概念不同。

复制(拷贝)构造函数

复制构造函数用于复制本类的对象,如:
Student s2(1002,1008);
Student s3(s2);//将对象s2复制给s3。注意复制和赋值的概念不同。
下面这种情况叫做赋值,不调用复制构造函数。
Student s4;
s4=s2;//这种情况叫做赋值,自己体会吧
大多数时候,在类中我们没有声明复制构造函数,而是C++自动为我们生成了一个复制构造函数,如下:

 Student(Student &b)
    {
        this.x=b.x;
        this.y=b.y;
    }


如代码所示,它的作用是将一个已存在的对象b,复制给调用该复制构造函数的对象。

具体来说,在一下情况发生时,会调用复制构造函数:
1、用复制的方法,建立一个新对象。
2、函数的形参为类的对象时。(这点和普通类型的形参类似,要复制一份实参给函数)
3、函数的返回值是类的对象,在函数中定义的对象,在函数结束后消息,需要调用复制构造函数,建立一个临时的对象,将该临时对象返回给调用该函数的对象。
注意:默认的复制构造函数,在某些情况下会出现问题,想深入学习可以自行百度。

#include<iostream>
using namespace std;

class People
{

};

//int main(){
// //cout << sizeof(People) << endl;
//}
class Student
{
public:
 //默认构造函数
 Student()
 {
  num = 1001;
  age = 18;
 }
 //初始化构造函数
 Student(int n, int a) :num(n), age(a){}
 //复制(拷贝)构造函数
 Student(Student &b)
 {
  this->num = b.num;  //this是指针,用->
  this->age = b.age;
 }
 //显示变量值函数,成员函数
 void showVariable(){
  cout << num <<"and"<<age<< endl;
 }
private:
 int num;
 int age;
};
int main()
{
 //用默认构造函数初始化对象S1
 Student s1;
 s1.showVariable();
 //用初始化构造函数初始化对象S2
 Student s2(1002, 18);
 s2.showVariable();
 //Student& temp = s2;
 Student s3(s2);//将对象s2复制给s3。注意复制和赋值的概念不同。
 s3.showVariable();
 return 0;
}


转换构造函数

转换构造函数

转换构造函数用于将其他类型的变量,隐式转换为本类对象。下面的转换构造函数,将int类型的r转换为Student类型的对象,对象的age为r,num为1004.

 Student(int r)
 {
     int num=1004int age= r;
 }


转换构造函数可以用在哪里?
假如重载了+号运算符,使得两个Student类的对象可以相加,其结果为两个对象的成员变量age之和。
s1+s2 其值就是18+18=36。
那么 s1+19 呢? 因为我们定义了 转换构造函数,那么 s1+19,首选调用+号运算符,发现20不是Student类的对象,而是int类型。然后调用转换构造函数,将19变为Student(1004,19)。现在便可以进行加法运算,其值是18(s1.age)+19=37.

这种情况下,s4定义了,但未被初始化,为什么呢?

原因在于

//转换类型的构造函数
 Student(int r){
  int num = 1004;
  int age = r;
 }

应该改成

//转换类型的构造函数
 Student(int r){
  num = 1004;
  age = r;
 }

这里面是一个作用域的问题。


(4) 成员函数
     成员函数必须在类内部声明,可以在类内部定义,也可以在类外部定义。如果在类内部定义,就默认是内联函数。


3 类定义补充
3.1 可使用类型别名来简化类
     除了定义数据和函数成员之外,类还可以定义自己的局部类型名字。
     使用类型别名有很多好处,它让复杂的类型名字变得简单明了、易于理解和使用,还有助于程序员清楚地知道使用该类型的真实目的。
class People
public: 
     typedef std::string phonenum; //电话号码类型
 
     phonenum phonePub; //公开号码
private:      
     phonenum phonePri;//私人号码
}; 
 
3.2 成员函数可被重载
     可以有多个重载成员函数,个数不限。

重载:

在实际开发中,有时候我们需要实现几个功能类似的函数,只是有些细节不同。例如希望交换两个变量的值,这两个变量有多种类型,可以是 int、float、char、bool 等,我们需要通过参数把变量的地址传入函数内部。在C语言中,程序员往往需要分别设计出三个不同名的函数,其函数原型与下面类似:

但在C++中,这完全没有必要。C++ 允许多个函数拥有相同的名字,只要它们的参数列表不同就可以,这就是函数的重载(Function Overloading)。借助重载,一个函数名可以有多种用途。

参数列表又叫参数签名,包括参数的类型、参数的个数和参数的顺序,只要有一个不同就叫做参数列表不同。通过本例可以发现,重载就是在一个作用范围内(同一个类、同一个命名空间等)有多个名称相同但参数不同的函数。重载的结果是让一个函数名拥有了多种用途,使得命名更加方便(在中大型项目中,给变量、函数、类起名字是一件让人苦恼的问题),调用更加灵活。

在使用重载函数时,同名函数的功能应当相同或相近,不要用同一函数名去实现完全不相干的功能,虽然程序也能运行,但可读性不好,使人觉得莫名其妙

注意,参数列表不同包括参数的个数不同、类型不同或顺序不同,仅仅参数名称不同是不可以的。函数返回值也不能作为重载的依据。

函数的重载的规则:

  • 函数名称必须相同。
  • 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)。
  • 函数的返回类型可以相同也可以不相同。
  • 仅仅返回类型不同不足以成为函数的重载。

#include <iostream>
using namespace std;

//交换 int 变量的值
void Swap(int* a, int* b){  //这里用Swap是为了和标准函数swap区分
 int temp = *a;
 *a = *b;
 *b = temp;
}

//交换 float 变量的值
void Swap(float* a, float* b){
     float temp = *a;
     *a = *b;
    *b = temp; 
}

//交换 char 变量的值
void Swap(char* a, char* b){
 float temp = *a;
 *a = *b;
 *b = temp;
}

//交换 bool 变量的值
void Swap(bool* a, bool* b){
 bool temp = *a;
 *a = *b;
 *b = temp;
}

int main(){
 //交换 int 变量的值
 int n1 = 100, n2 = 200;
 Swap(&n1, &n2);
 cout << n1 << ", " << n2 << endl;

 //交换 float 变量的值
 float f1 = 12.5, f2 = 6.3;
 Swap(&f1, &f2);
 cout << f1 << ", " << f2 << endl;

 return 0;
}

C++ 是如何做到函数重载的?

C++代码在编译时会根据参数列表对函数进行重命名,例如void Swap(int a, int b)会被重命名为_Swap_int_intvoid Swap(float x, float y)会被重命名为_Swap_float_float。当发生函数调用时,编译器会根据传入的实参去逐个匹配,以选择对应的函数,如果匹配失败,编译器就会报错,这叫做重载决议(Overload Resolution

不同的编译器有不同的重命名方式,这里仅仅举例说明,实际情况可能并非如此。

从这个角度讲,函数重载仅仅是语法层面的,本质上它们还是不同的函数,占用不同的内存,入口地址也不一样


3.3 内联函数
     有三种:
(1)直接在类内部定义。
(2)在类内部声明,加上inline关键字,在类外部定义。
(3)在类内部声明,在类外部定义,同时加上inline关键字。
注意:此种情况下,内联函数的定义通常应该放在类定义的同一头文件中,而不是在源文件中。这是为了保证内联函数的定义在调用该函数的每个源文件中是可见的。
-----------------------------未完,待续,2017.7.6 


1.函数调用原理
"编译过程的最终产品是可执行程序--由一组机器语言指令组成。运行程序时,操作系统将这些指令载入计算机内存中,因此每条指令都有特定的内存地址。计算机随后将逐步执行这些指令。有时(如有循环和分支语句时),将跳过一些指令,向前或向后跳到特定地址。常规函数调用也使程序跳到另一个地址(函数的地址),并在函数结束时返回。下面更详细地介绍这一过程的典型实现。执行到函数调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈(为此保留的内存块),跳到标记函数起点的内存单元,执行函数代码(也许还需将返回值放入寄存器中),然后跳回到地址被保存的指令处(这与阅读文章时停下来看脚注,并在阅读完脚注后返回到以前阅读的地方类似)。来回跳跃并记录跳跃位置意味着以前使用函数时,需要一定的开销。"
2.内联函数
内联函数提供了另一种选择。编译器将使用相应的函数代码替换函数调用。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。
3.内联函数的使用
在函数声明前加上关键字inline;
在函数定义前加上关键字inline。
示例如下:

4.内联函数与宏定义的区别
C语言使用预处理器语句#define来提供宏。如下例所示:
#define SQUARE(X) X*X
宏定义时通过文本替换来实现的--X是参数的符号标记。
a = square(5.0);->a=5.0*5.0;
b = square(4.5+7.5);->b=4.5+7.5*4.5+7.5
d = square(c++);->d=c++*c++
可以看出,对于b,需要使用括号才能正常运算。
#define SQUARE(X) ((X)*(X))
对于c,却仍递增了两次。
因此,宏定义和内联函数存在本质的区别,转换的时候应考虑是否转换后功能是否正常

5.什么时候使用内联函数

如果执行函数代码的时间比处理函数调用机制的时间长,则节省的时间占比很小。若代码执行时间很短,则内联函数就可以节省函数调用的时间。


3.4 访问限制
public,private,protected 为属性/方法限制的关键字。

1.类的一个特征就是封装,public和private作用就是实现这一目的。所以:

用户代码(类外)可以访问public成员而不能访问private成员;private成员只能由类成员(类内)和友元访问。

2.类的另一个特征就是继承,protected的作用就是实现这一目的。所以:

protected成员可以被派生类对象访问,不能被用户代码(类外)访问。

#include <iostream>
#include <assert.h>

using namespace std;
class A{
public:
 int a;
 A(){  //构造函数
  a1 = 1;
  a2 = 2;
  a3 = 3;
  a = 4;
 }
 void fun(){
  cout << a << endl;
  cout << a1 << endl;
  cout << a2 << endl;
  cout << a3 << endl;
 }
public:
 int a1;
protected:
 int a2;
private:
 int a3;
};

int main(){
 A itma;
 itma.a = 10;   //正确
 itma.a1 = 20;  //正确
 //itma.a2 = 30; //错误,类外不能访问protected成员
 //itma.a3 = 40; //错误,类外不能访问private成员
 itma.fun();
 return 0;
}

继承中的特点:

先记住:不管是否继承,上面的规则永远适用!

有public, protected, private三种继承方式,它们相应地改变了基类成员的访问属性。

1.public继承:基类public成员,protected成员,private成员的访问属性在派生类中分别变成:public, protected, private

2.protected继承:基类public成员,protected成员,private成员的访问属性在派生类中分别变成:protected, protected, private

3.private继承:基类public成员,protected成员,private成员的访问属性在派生类中分别变成:private, private, private

但无论哪种继承方式,上面两点都没有改变:

1.private成员只能被本类成员(类内)和友元访问,不能被派生类访问

2.protected成员可以被派生类访问

再来看看以下代码:

1.public继承

#include<iostream>
#include<assert.h>
using namespace std;

class A{    //基类
public:
 int a;
 A(){   //默认构造函数
  a1= 1;
     a2= 2;
  a3= 3;
  a= 4;
 }
 void fun(){
  cout<< a << endl;    //正确
  cout<< a1 << endl;   //正确
  cout<< a2 << endl;   //正确
  cout<< a3 << endl;   //正确
 }
public:
   int a1;
protected:
    int a2;
private:
     int a3;
};

class B: public A{   //public 继承 B派生类
public:
   int a;
     B(int i){  //另外一种构造函数,初始化构造函数
          A();
    a= i;
 }
 void fun(){
       cout<< a << endl;       //正确,public成员
        cout<< a1 << endl;       //正确,基类的public成员,在派生类中仍是public成员。
       cout<< a2 << endl;       //正确,基类的protected成员,在派生类中仍是protected可以被派生类访问。
       //cout<< a3 << endl;       //错误,基类的private成员不能被派生类访问。
 }
};

int main(){
    B b(10);
 cout<< b.a << endl;
 cout<< b.a1 << endl;   //正确
 //cout<< b.a2 << endl;   //错误,类外不能访问protected成员
 //cout<< b.a3 << endl;   //错误,类外不能访问private成员
 system("pause");
 return 0;

}

2.protected继承:

3.private继承:


3.5 类的数据成员中不能使用 auto、extern和register等进行修饰, 也不能在定义时进行初始化

 如 int xPos = 0; //错;

例外:
          静态常量整型(包括char,bool)数据成员可以直接在类的定义体中进行初始化,例如:
          static const int ia= 30; 

关于类中的静态成员变量,

在类中只能声明,不能定义

注意在类的内部只是声明,不是定义

类中的静态变量是属于类的,不属于某个对象!不能在定义对象时对变量初始化!就时不能用构造函数来初始化!

而且使用时应加上类名,而不是对象。例如: 

#include <iostream>
using namespace std;
class A{
public:
 static int x;
 static int y;
};
int A::x = 1;
int A::y = 2;//这样初始化!
int main(){
 cout << A::x << endl;
 cout << A::y << endl;//同样只能这样使用!
}

下面是对static的一点讲解 
=============================================== 
  静态数据成员的使用方法和注意事项如下: 
  1、静态数据成员在定义或说明时前面加关键字static。 
  2、静态成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式如下: 
    <数据类型><类名>::<静态数据成员名>=<值> 
  这表明: 
  (1) 初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆。 
  (2) 初始化时不加该成员的访问权限控制符private,public等。 
  (3) 初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员。 
  3、静态数据成员是静态存储的,它是静态生存期,必须对它进行初始化。 
  4、引用静态数据成员时,采用如下格式: 
   <类名>::<静态成员名> 
  如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员。


还有,只有静态常量整型数据成员才可以在类中初始化

类中的静态成员变量使用时,需要在类外声明;
但是不能在*.h文件中,而只能在*.cpp文件中声明;

然后在其它函数中可以直接使用。

比如:*.cpp文件中:

========================a.h====================================================
class CMyLibpurple
{
public:
CMyLibpurple(void);
public:
~CMyLibpurple(void);
public:
static PurpleConversation *LocalConv;
}
========================a.h====================================================

 

========================a.cpp====================================================
PurpleConversation *CMyLibpurple::LocalConv = NULL; 
CMyLibpurple::CMyLibpurple(void)
{
//LocalConv =NULL; 不能在构造函数中初始化,因为静态变量不属于哪个对象,而是属于类。
}

static void
null_write_conv(PurpleConversation *conv, const char *who, const char *alias,
     const char *message, PurpleMessageFlags flags, time_t mtime)
{
CMyLibpurple::LocalConv = conv; 
}


4 类声明与类定义

4.1 类声明(declare)
class Screen;
      在声明之后,定义之前,只知道Screen是一个类名,但不知道包含哪些成员。只能以有限方式使用它,不能定义该类型的对象,只能用于定义指向该类型的指针或引用,声明(不是定义)使用该类型作为形参类型或返回类型的函数。
void Test1(Screen& a){};
void Test1(Screen* a){};

4.2 类定义(define)
     在创建类的对象之前,必须完整的定义该类,而不只是声明类。所以,类不能具有自身类型的数据成员,但可以包含指向本类的指针或引用。

class LinkScreen
{

public:
          Screen window;
          LinkScreen* next;
          LinkScreen* prev;
};
 //注意,分号不能丢
     因为在类定义之后可以接一个对象定义列表,可类比内置类型,定义必须以分号结束:

class LinkScreen{ /* ... */ };

class LinkScreen{ /* ... */ } scr1,scr2; 


5 类对象
     定义类对象时,将为其分配存储空间。
     Sales_item item; //编译器分配了足以容纳一个 Sales_item 对象的存储空间。item 指的就是那个存储空间。


6 隐含的 this 指针 
     成员函数具有一个附加的隐含形参,即 this指针,它由编译器隐含地定义。成员函数的函数体可以显式使用 this 指针。


6.1 何时使用 this 指针
     当我们需要将一个对象作为整体引用而不是引用对象的一个成员时。最常见的情况是在这样的函数中使用 this:该函数返回对调用该函数的对象的引用。
class Screen 
{
...
public:
      Screen& set(char);  //返回对调用该函数的对象的引用
};
Screen& Screen::set(char c) 

{
      contents[cursor] = c;
      return *this;
}


7 类作用域     每个类都定义了自己的作用域和唯一的类型。

     类的作用域包括:类的内部(花括号之内), 定义在类外部的成员函数的参数表(小括号之内)和函数体(花括号之内)。
class Screen 
//类的内部
...
}; 
//类的外部

char Screen::get(index r, index c) const
{
     index row = r * width;      // compute the row location
     return contents[row + c];   // offset by c to fetch specified character

 注意:成员函数的返回类型不一定在类作用域中。可通过 类名::来判断是否是类的作用域,::之前不属于类的作用域,::之后属于类的作用域。例如

Screen:: 之前的返回类型就不在类的作用域,Screen:: 之后的函数名开始到函数体都是类的作用域。
class Screen 
public: 
     typedef std::string::size_type index; 
     index get_cursor() const; 
}; 
Screen::index Screen::get_cursor() const   //注意:index前面的Screen不能少
     return cursor; 
     该函数的返回类型是 index,这是在 Screen 类内部定义的一个类型名。在类作用域之外使用,必须用完全限定的类型名 Screen::index 来指定所需要的 index 是在类 Screen 中定义的名字。


二 构造函数

 构造函数是特殊的成员函数,用来保证每个对象的数据成员具有合适的初始值。
     构造函数名字与类名相同,不能指定返回类型(也不能定义返回类型为void),可以有0-n个形参。
     在创建类的对象时,编译器就运行一个构造函数。

1 构造函数可以重载
     可以为一个类声明的构造函数的数量没有限制,只要每个构造函数的形参表是唯一的。
class Sales_item;
{
public: 
     Sales_item(const std::string&); 
     Sales_item(std::istream&); 
     Sales_item(); //默认构造函数
}; 

2 构造函数自动执行 
     只要创建该类型的一个对象,编译器就运行一个构造函数:
Sales_item item1("0-201-54848-8");
Sales_item *p = new Sales_item(); 

     第一种情况下,运行接受一个 string 实参的构造函数,来初始化变量item1。
     第二种情况下,动态分配一个新的 Sales_item 对象,通过运行默认构造函数初始化该对象。


3 析构函数
     构造函数的用途之一是自动获取资源;与之相对的是,析构函数的用途之一是回收资源。除此之外,析构函数可以执行任意类设计者希望在该类对象的使用完毕之后执行的操作。

(1) 何时调用析构函数

  • 撤销(销毁)类对象时会自动调用析构函数。
  • 变量(类对象)在超出作用域时应该自动撤销(销毁)。
  • 动态分配的对象(new A)只有在指向该对象的指针被删除时才撤销(销毁)。
  • 撤销(销毁)一个容器(不管是标准库容器还是内置数组)时,也会运行容器中的类类型元素的析构函数(容器中的元素总是从后往前撤销)。

设计一个类时,如何写析构函数?
析构函数如果我们不写的话,C++ 会帮我们自动的合成一个,就是说:C++ 会自动的帮我们写一个析构函数。很多时候,自动生成的析构函数可以很好的工作,但是一些重要的事迹,就必须我们自己去写析构函数。
析构函数和构造函数是一对。构造函数用于创建对象,而析构函数是用来撤销对象。简单的说:一个对象出生的时候,使用构造函数,死掉的时候,使用析构函数。

下面我们来做一个例子,看看:

#include <iostream>
#include <string>
using namespace std;
class NoName{
public:
 NoName() :pstring(new std::string), i(0), d(0){};
private:
 std::string* pstring;
 int i;
 double d;
};
int main(){
 return 0;
}

像上面这个 NoName 类这样的设计,类里面有一个成员变量是指针(std::string *pstring) ,那么在构造函数里我们使用new 创建了对象,并使用 pstring 来操作这个对象。那么在这个情况下,我们就必须设计一个析构函数。

析构函数是这样编写的:(可以在类的里面声明,定义写在类的外面,)

#include <iostream>
#include <string>
using namespace std;
class NoName{
public:
 NoName() :pstring(new std::string), i(0), d(0){
  cout << "构造函数被调用了" << endl;
 };
 ~NoName();
private:
 std::string* pstring;
 int i;
 double d;
};
NoName::~NoName(){
 cout << "析构函数被调用了" << endl;
}
int main(){
 NoName miao;
 return 0;
}

析构函数是这样写的: ~NoName() ,它与构造函数唯一的区别就是,前面都加了一个 ~ 符号。 析构函数都是没有参数的,也就是说:析构函数永远只能写一个。
按照 C++ 的要求,只要有 new 就要有相应的 delete 。这个 new 是在构造函数里 new 的,就是出生的时候。所以在死掉的时候,就是调用析构函数时,我们必须对指针进行 delete 操作。

在定义 miao 对象的时候,调用了 NoName 类的构造函数,在main() 函数执行完的时候,就是执行完return 0; 这句话后,main() 函数的执行域结束,所以就要杀掉 a 对象,所以这个时候会调用NoName 类的析构函数。

那么,如果我们在 main() 函数中使用 new 关键字来创建一个 NoName 类的实例化对象,会出现什么样的运行效果呢?
main() 函数中的代码如下:

int main(){
 NoName miao;
 NoName *p = new NoName;
 return 0;
}

这里使用 new 创建的对象,就必须要使用 delete 来释放它。 牢牢的记住:newdelete 是一对。正确的代码是下面这个样子的:

int main(){
 NoName miao;
 NoName *p = new NoName; //记住new和delete一定要成对出现,释放内存
 delete p;
 return 0;
}

构造函数 和 析构函数 各有各的用途,在构造函数中,我们来获取资源;在析构函数中,我们来释放资源。释放了之后,这些资源就会被回收,可以被重新利用。
比如说,我们在构造函数里打开文件,在析构函数里关闭打开的文件。这是一个比较好的做法。
在构造函数里,我们去连接数据库的连接,在析构函数里关闭数据库的连接。
在构造函数里动态的分配内存,那么在析构函数里把动态分配的内存回收。

构造函数 和 析构函数 之间的操作是向对应的。
如果我们不写析构函数,C++ 会帮我们写一个析构函数。C++帮我们写的这个析构函数只能做一些很简单的工作,它不会帮助我们去打开文件、连接数据库、分配内存这些操作,相应的回收,它也不会给我们写。所以需要我们自己手动的写。(如果要做这些操作,我们必须自己写。)

如果我们自己写了析构函数,记住三个原则:
如果你写了析构函数,就必须同时写赋值构造函数 和 赋值操作符。你不可能只写一个。

赋值构造函数:

在赋值的时候,不是讲指针赋值过来,而是将指针对应指向的字符串赋值过来,这是最关键的一步。

因为我们写了析构函数,就必须要将赋值构造函数写上:

除了要写 赋值构造函数,还要写赋值操作符。

#include <iostream>
#include <string>
using namespace std;
class NoName{
public:
 NoName() :pstring(new std::string), i(0), d(0){
  cout << "构造函数被调用了" << endl;
 };
 NoName(const NoName & other);   //赋值构造函数
 ~NoName();

 NoName& operator =(const NoName &rhs); //赋值操作符

private:
 std::string* pstring;
 int i;
 double d;
};
NoName::~NoName(){
 cout << "析构函数被调用了" << endl;
}
NoName::NoName(const NoName & other){
 pstring = new std::string;
 *pstring = *(other.pstring);
 i = other.i;
 d = other.d;
}

NoName& NoName::operator=(const NoName &rhs){
 pstring = new std::string;
 *pstring = *(rhs.pstring);
 i = rhs.i;
 d = rhs.d;
 return *this;
}

int main(){
 NoName miao;
 NoName *p = new NoName; //记住new和delete一定要成对出现,释放内存
 delete p;
 return 0;
}

只要你写了析构函数,就必须要写 赋值构造函数 和 赋值运算符,这就是著名的 三法则 (rule of three

总结:

在设计一个类的时候,如果我们一个构造函数都没有写,那么 C++ 会帮我们写一个构造函数。只要我们写了一个构造函数,那么 C++ 就不会再帮我们写构造函数了。

构造函数可以重载,可以写很多个,析构函数不能重载,只能写一个。如果我们没有写析构函数,C++会自动帮我们写一个析构函数。那么在工作的时候,我们写的析构函数会被调用,调用完成之后,C++会执行它自动生成的析构函数。

如果我们写的类是一个没有那么复杂的类,我们可以不需要写析构函数。如果一个类只要有这些情况:打开文件、动态分配内存、连接数据库。简单的说:就是只要构造函数里面有了new 这个关键词,我们就需要自己手动编写析构函数。

那么如果我们写了析构函数,就必须要注意三法则:同时编写:析构函数、拷贝构造函数、赋值运算符重载。-----为什么呢?

c++构造函数、拷贝构造函数、析构函数、赋值运算符重载

http://blog.csdn.net/qq_35420908/article/details/52841475    (还需细看)



四 友元

百度百科上对友元函数是这样定义的:友元函数是指某些虽然不是类成员却能够访问类的所有成员的函数。。类授予它的友元特别的访问权。通常同一个开发者会出于技术和非技术的原因,控制类的友元和成员函数(否则当你想更新你的类时,还要征得其它部分的拥有者的同意)。

为什么会有友元函数?

结合着类的特性和类中一般成员函数,我们可以这样理解:类具有封装和信息隐藏的特性。只有类的成员函数才能访问类的私有成员,程序中的其他函数是无法访问私有成员的。非成员函数可以访问类中的公有成员,但是如果将数据成员都定义为公有的,这又破坏了隐藏的特性。另外,应该看到在某些情况下,特别是在对某些成员函数多次调用时,由于参数传递,类型检查和安全性检查等都需要时间开销,而影响程序的运行效率。

为了解决上述问题,提出一种使用友元的方案。友元是一种定义在类外部的普通函数,但它需要在类体内进行说明,为了与该类的成员函数加以区别,在说明时前面加以关键字friend。友元不是成员函数,但是它可以访问类中的私有成员。友元的作用在于提高程序的运行效率,但是,它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。

友元函数的特点是能够访问类中的私有成员的非成员函数。友元函数从语法上看,它与普通函数一样,即在定义上和调用上与普通函数一样。下面举一例子说明友元函数的应用。

1. 友元函数

#include <iostream>
using namespace std;

class Point{
public:
 Point(double xx, double yy){ x = xx; y = yy; } //初始化构造函数
 void Getxy(); //成员函数声明
 friend double Distance(Point &a, Point &b); //友元函数声明,形参是引用(别名)
private:
 double x, y;
};

void Point::Getxy(){      //类的作用域
 cout << x << "and" << y << endl;
}

double Distance(Point &a, Point &b){   //友元函数可以直接访问私有成员
 double dx = a.x - b.x;
 double dy = a.y - b.y;
 return sqrt(dx*dx + dy*dy);
}

void main(){
 Point p1(3.0, 4.0), p2(6.0, 8.0);  //类定义,利用初始化构造函数
 p1.Getxy();
 p2.Getxy();
 double d = Distance(p1, p2);
 cout << "Distance is" << d << endl;
}

说明:在该程序中的Point类中说明了一个友元函数Distance(),它在说明时前边加friend关键字,标识它不是成员函数,而是友元函数。它的定义方法与普通函数定义一样,而不同于成员函数的定义,因为它不需要指出所属的类。但是,它可以引用类中的私有成员,函数体中 a.x,b.x,a.y,b.y都是类的私有成员,它们是通过对象引用的。在调用友元函数时,也是同普通函数的调用一样,不要像成员函数那样调用。

本例中,p1.Getxy()和p2.Getxy()这是成员函数的调用,要用对象来表示。而Distance(p1, p2)是友元函数的调用,它直接调用,不需要对象表示,它的参数是对象。


2.友元类

友元除了前面讲过的函数以外,友元还可以是类,即一个类可以作另一个类的友元。当一个类作为另一个类的友元时,这就意味着这个类的所有成员函数都是另一个类的友元函数。

使用友元类时注意:

(1) 友元关系不能被继承。

(2) 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。

(3) 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明

总结起来:

(1)友元关系不可以继承,但对已有的方法来说访问权限不改变。

(2)如果改写基类的方法则访问权限改变

(3)友元关系不具有传递性

若类B是类A的友元,类C是B的友元,类C不一定是类A的友元。

#include <iostream>
using namespace std;

class Cobj{
public:
 Cobj() :mX(0), mY(0){}; //默认构造函数
 friend class CFriend;  //声明友元类
private:
 void PrintData() const{
  cout << "mX=" << mX << endl;
  cout << "mY=" << mY << endl;
 }
 int mX;
 int mY;
};

class CFriend{
public:
 CFriend(int x, int y){
  mObj.mX = x;  //直接调用类CObj的私有数据成员,初始化构造函数
  mObj.mY = y;
 }
 void ShowData()const{
  mObj.PrintData(); //直接调用类CObj的私有成员函数
 }
private:
 Cobj mObj;     //声明对象
};

int main(){
 CFriend one(3, 4);  //定义对象
 one.ShowData();
 return 0;
}


C++虚函数与纯虚函数的区别

什么是虚函数?

那些被virtual关键字修饰的成员函数,就是虚函数。虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异而采用不同的策略。

虚函数声明如下:virtual ReturnType FunctionName(Parameter);

虚函数必须实现,如果不实现,编译器将报错,错误提示为:

error LNK****: unresolved external symbol "public: virtual void __thiscall

ClassName::virtualFunctionName(void)"


为什么要用纯虚函数?

在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。为了解决这个问题,方便使用类的多态性,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。

在什么情况下使用纯虚函数(pure vitrual function)?

1,当想在基类中抽象出一个方法,且该基类只做能被继承,而不能被实例化;

2,这个方法必须在派生类(derived class)中被实现;

如果满足以上两点,可以考虑将该方法申明为pure virtual function.

我们来举个例子,我们先定义一个形状的类(Cshape),但凡是形状我们都要求其能显示自己。所以我们定义了一个类如下:

class CShape
{
    virtual void Show() {};
};

但没有CShape这种形状,因此我们不想让CShape这个类被实例化,我们首先想到的是将Show函数的定义(实现)部分删除如下:
class CShape
{
    virtual void Show();
};

当我们使用下面的语句实例化一个CShape时:
CShape cs;  //这是我们不允许的,但仅用上面的代码是可以通过编译(但link时失败)。
怎么样避免一个CShape被实例化,且在编译时就被发现?

答案是:使用pure virtual funcion.
我们再次修改CShape类如下:
class CShape
{
public:
    virtual void Show() =0;
};
这时在实例化CShape时就会有以下报错信息:
error C2259: 'CShape' : cannot instantiate abstract class due to following members:
warning C4259: 'void __thiscall CShape::Show(void)' : pure virtual function was not defined


我们再来看看被继承的情况,我们需要一个CPoint2D的类,它继承自CShape.他必须实现基类(CShape)中的Show()方法。

其实使用最初的本意是让每一个派生自CShape的类,都要实现Show()方法,但时常我们可能在一个派生类中忘记了实现Show(),为了避免这种情况,pure virtual funcion发挥作用了。


我们看以下代码:

class CPoint2D:public CShape
{
public:
 CPoint2D()
 {
  printf("CPoint2D ctor is invoked\n");
 };
 void Msg()
 {
  printf("CPoint2D.Msg() is invoked\n");
 };
 
};
 
当我们实例化CPoint2D时,在编译时(at the compiling)也会出现如下的报错:
error C2259: 'CShape' : cannot instantiate abstract class due to following members:
warning C4259: 'void __thiscall CShape::Show(void)' : pure virtual function was not defined
   如上,我们预防了在派生类中忘记实现基类方法。


首先:强调一个概念
定义一个函数为虚函数,不代表函数为不被实现的函数。
定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
定义一个函数为纯虚函数,才代表函数没有被实现。

定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。

#include <iostream>
using namespace std;

class A{
public:
 virtual void foo(){     //虚函数
  cout << "A::foo() is called" << endl;
 }
};
 
class B :public A{    //继承
public:
 void foo(){
  cout << "B::foo is called" << endl;
 }
};

int main(){
 A *a = new B();  //声明一个指针变量a
 a->foo();   // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B的!
 return 0;
}

这个例子是虚函数的一个典型应用,通过这个例子,也许你就对虚函数有了一些概念。它虚就虚在所谓“推迟联编”或者“动态联编”上,一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为“虚”函数。
    虚函数只能借助于指针或者引用来达到多态的效果。

纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”
 virtual void funtion1()=0

纯虚函数最显著的特征是:它们必须在继承类中重新声明函数(不要后面的=0,否则该派生类也不能实例化),而且它们在抽象类中往往没有定义。


抽象类的介绍
抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层。
(1)抽象类的定义:  称带有纯虚函数的类为抽象类。
(2)抽象类的作用:
抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些语义,也可以再将这些语义传给自己的子类。
(3)使用抽象类时注意:

•   抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。
•   抽象类是不能定义对象的。

总结:
1、纯虚函数声明如下: virtual void funtion1()=0; 纯虚函数一定没有定义,纯虚函数用来规范派生类的行为,即接口。包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。
2、虚函数声明如下:virtual ReturnType FunctionName(Parameter);虚函数必须实现,如果不实现,编译器将报错,错误提示为:
error LNK****: unresolved external symbol "public: virtual void __thiscall ClassName::virtualFunctionName(void)"
3、对于虚函数来说,父类和子类都有各自的版本。由多态方式调用的时候动态绑定。
4、实现了纯虚函数的子类,该纯虚函数在子类中就编程了虚函数,子类的子类即孙子类可以覆盖该虚函数,由多态方式调用的时候动态绑定。
5、虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。
6、在有动态分配堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚的。
7、友元不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚拟函数。但可以通过让友元函数调用虚拟成员函数来解决友元的虚拟问题。
8、析构函数应当是虚函数,将调用相应对象类型的析构函数,因此,如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数。

有纯虚函数的类是抽象类,不能生成对象,只能派生。他派生的类的纯虚函数没有被改写,那么,它的派生类还是个抽象类。
定义纯虚函数就是为了让基类不可实例化化
因为实例化这样的抽象数据结构本身并没有意义。
或者给出实现也没有意义
实际上我个人认为纯虚函数的引入,是出于两个目的
1、为了安全,因为避免任何需要明确但是因为不小心而导致的未知的结果,提醒子类去做应做的实现。
2、为了效率,不是程序执行的效率,而是为了编码的效率。


C++的重载(overload)与重写(override)

C++的重载(overload)与重写(override)

成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual关键字可有可无。

重写是指派生类函数重写基类函数,是C++的多态的表现,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual关键字。

示例中,函数Base::f(int)与Base::f(float)相互重载,而Base::g(void)被Derived::g(void)重写。

#include <iostream>
using namespace std;

class Base{
public:
 void f(int x){   //函数重载,overload
  cout << "Base::f(int)" << x << endl;
 }
 void f(float x){
  cout << "Base::f(float)" << x << endl;
 }
 virtual void g(){   //虚函数
  cout << "Base::g()" << endl;
 }
};

class Derived :public Base{   //pubic继承
public:
 virtual void g(){        //override
  cout << "Derived::g()" << endl;
 }
};

int main(){
 Derived d;
 Base *pb = &d;
 pb->f(42);   //overload
 pb->f(3.14f);
 pb->g();
}


C++的多态性

C++编程语言是一款应用广泛,支持多种程序设计的计算机编程语言。它的继承、重载、多态等特性为其自身镀上了一层层神秘的色彩,这也是为什么C++精彩的原因,如今,众多语言模仿C++的特性,更说明了这样的性质的独特之处,我们今天就会为大家详细介绍其中C++多态性的一些基本知识,以方便大家在学习过程中对此能够有一个充分的掌握。

下面先上一个复试题目:

#include <iostream>
using namespace std;

class A{
public:
 void foo(){
  printf("1\n");
 }
 virtual void fun(){    //虚函数
  printf("2\n");
 }
};

class B :public A{
public:
 void foo(){
  printf("3\n");
 }
 void fun(){          //重写基类的虚函数
  printf("4\n");
 }
};

int main(void){
 A a;
 B b;
 A *p = &a;
 p->foo();
 p->fun();
 p = &b;
 p->foo();
 p->fun();
 return 0;
}

答案为:1 2 1 4

当然,对于大牛来说,这中小菜一碟的事情肯定会成为笑柄,但从这个很小的例子,可以看出多态的很重要的性质。没有疑问,第一个p->foo()和p->fuu()都很好理解,本身是基类指针,指向的又是基类对象,调用的都是基类本身的函数,因此输出结果就是1、2。第二个输出结果就是1、4。p->foo()和p->fuu()则是基类指针指向子类对象,正式体现多态的用法,p->foo()由于指针是个基类指针,指向是一个固定偏移量的函数,因此此时指向的就只能是基类的foo()函数的代码了,因此输出的结果还是1。而p->fun()指针是基类指针,指向的fun是一个虚函数,由于每个虚函数都有一个虚函数列表,此时p调用fun()并不是直接调用函数,而是通过虚函数列表找到相应的函数的地址,因此根据指向的对象不同,函数地址也将不同,这里将找到对应的子类的fun()函数的地址,因此输出的结果也会是子类的结果4。

我们来总结一下:这个题目中可以看出多态的哪些特性呢?

多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。多态(polymorphisn),字面意思多种形状。

C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。(这里我觉得要补充,重写的话可以有两种,直接重写成员函数和重写虚函数,只有重写了虚函数的才能算作是体现了C++多态性)而重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。编译器会根据这些函数的不同列表,将同名的函数的名称做修饰,从而生成一些不同名称的预处理函数,来实现同名函数调用时的重载问题。但这并没有体现多态性。

多态与非多态的实质区别就是函数地址是早绑定还是晚绑定。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的,就是说地址是早绑定的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定。

那么多态的作用是什么呢,封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。

最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。因为没有多态性,函数调用的地址将是一定的,而固定的地址将始终调用到同一个函数,这就无法实现一个接口,多种方法的目的了。


C++中的static关键字的总结

C++的static有两种用法:面向过程程序设计中的static和面向对象程序设计中的static。前者应用于普通变量和函数,不涉及类;后者主要说明static在类中的作用。

1.面向过程设计中的static

1.1静态全局变量

在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。我们先举一个静态全局变量的例子,如下:

#include <iostream>
using namespace std;

void fn();
static int n; //定义静态全局变量
void main()
{
 n = 20;
 cout<<"n:"<<n<<endl;
 fn();
}

void fn()
{
 n++;
 cout<<n<<endl;
}

静态全局变量有以下特点:
• 该变量在全局数据区分配内存;
• 未经初始化的静态全局变量会被程序自动初始化为0(自动变量的值是随机的,除非它被显式初始化);
• 静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的;

静态变量都在全局数据区分配内存,包括后面将要提到的静态局部变量。对于一个完整的程序,在内存中的分布情况如下图: 
代码区
全局数据区
堆区
栈区
一般程序的由new产生的动态数据存放在堆区,函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。细心的读者可能会发现,Example 1中的代码中将 “static int n; //定义静态全局变量”改为“int n; //定义全局变量”。程序照样正常运行。的确,定义全局变量就可以实现变量在文件中的共享,但定义静态全局变量还有以下好处:
• 静态全局变量不能被其它文件所用;
• 其它文件中可以定义相同名字的变量,不会发生冲突;

1.2.静态局部变量

在局部变量前,加上关键字static,该变量就被定义成为一个静态局部变量。 我们先举一个静态局部变量的例子,如下:
//Example 3

#include <iostream>
using namespace std;

void fn();
void main()
{
 fn();
 fn();
 fn();
}
void fn()
{
 static int n = 10;  //静态局部变量
 cout << n << endl;
 n++;
}

答案:10 11 12

通常,在函数体内定义了一个变量,每当程序运行到该语句时都会给该局部变量分配栈内存。但随着程序退出函数体,系统就会收回栈内存,局部变量也相应失效。但有时候我们需要在两次调用之间对变量的值进行保存。通常的想法是定义一个全局变量来实现。但这样一来,变量已经不再属于函数本身了,不再仅受函数的控制,给程序的维护带来不便。
静态局部变量正好可以解决这个问题。静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下一次调用,直到下次赋新值。
静态局部变量有以下特点:
• 该变量在全局数据区分配内存;
• 静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
• 静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
• 它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束;


1.3静态函数

在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。

#include <iostream.h>
static void fn();//声明静态函数
void main()
{
   fn();
}
void fn()//定义静态函数
{
   int n=10;
   cout<<n<<endl;
}
定义静态函数的好处:
• 静态函数不能被其它文件所用;
• 其它文件中可以定义相同名字的函数,不会发生冲突;

二、面向对象的static关键字(类中的static关键字)

2.1静态数据成员

在类内数据成员的声明前加上关键字static,该数据成员就是类内的静态数据成员。先举一个静态数据成员的例子。

#include <iostream>
using namespace std;

class Myclass
{
public:
 Myclass(int a, int b, int c); //初始化构造函数声明
 void GetSum();         //成员函数
private:
 int a, b, c;
 static int Sum;//声明静态数据成员
};
int Myclass::Sum = 0;//定义并初始化静态数据成员, 通过类名引用

Myclass::Myclass(int a, int b, int c)  //初始化构造函数定义
{
 this->a = a;   //this指针,指向对象本身
 this->b = b;
 this->c = c;
 Sum += a + b + c;
}

void Myclass::GetSum()
{
 cout << "Sum=" << Sum << endl;
}

void main()
{
 Myclass M(1, 2, 3);
 M.GetSum();
 Myclass N(4, 5, 6);
 N.GetSum();
 M.GetSum();

}

答案: 6 21 21

可以看出,静态数据成员有以下特点:
• 对于非静态数据成员,每个类对象都有自己的拷贝。而静态数据成员被当作是类的成员。无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问。也就是说,静态数据成员是该类的所有对象所共有的。对该类的多个对象来说,静态数据成员只分配一次内存,供所有对象共用。所以,静态数据成员的值对每个对象都是一样的,它的值可以更新


2.2静态成员函数

与静态数据成员一样,我们也可以创建一个静态成员函数,它为类的全部服务而不是为某一个类的具体对象服务。静态成员函数与静态数据成员一样,都是类的内部实现,属于类定义的一部分。普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。通常情况下,this是缺省的。如函数fn()实际上是this->fn()。但是与普通函数相比,静态成员函数由于不是与任何的对象相联系,因此它不具有this指针。从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。下面举个静态成员函数的例子。

#include <iostream>
using namespace std;

class Myclass
{
public:
 Myclass(int a, int b, int c);
 static void GetSum(); // 声明静态成员函数
private:
 int a, b, c;
 static int Sum;//声明静态数据成员
};
int Myclass::Sum = 0;//定义并初始化静态数据成员

Myclass::Myclass(int a, int b, int c)
{
 this->a = a;
 this->b = b;
 this->c = c;
 Sum += a + b + c; //非静态成员函数可以访问静态数据成员
}

void Myclass::GetSum() //静态成员函数的实现
{
 // cout<<a<<endl; //错误代码,a是非静态数据成员
 cout << "Sum=" << Sum << endl;
}

void main()
{
 Myclass M(1, 2, 3);
 M.GetSum();
 Myclass N(4, 5, 6);
 N.GetSum();
 Myclass::GetSum();
}


C++多继承

C++是支持多重继承的,但一定要慎用,因为很容易出现各种各样的问题。

上面算是一段最简单的多重继承代码了,编译运行是没有错误的。平时绝大部分时候,我们都只使用单继承,所为单继承是针对多重继承而言的,即一个类只有一个直接父类。其实有单继承,肯定自然而然的会想到让一个类去继承多个类。这也是符合现实的,比如自然界绝大部分生物都是两性繁殖,即每一个孩子都是继承了父母两个人的基因的。很幸运,C++是支持多重继承的(java通过继承和实现接口的结合,也能实现类似的多重继承)。



面向对象更重要的是思想,很多C系程序员一直都在用类来写过程式程序。



















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值