zoukankan      html  css  js  c++  java
  • c++入门之详细探讨类的一些行为

    之前我们讨论过类成员的组成,尤其是成员函数,我们知道了定义一个类的时候,我们往往定义了:构造函数,析构函数,其他函数,以及友元函数(友元函数不是必须的)。

    同时,我们知道了这样一个事情:在定义一个对象的时候:构造函数会被调用;对象被销毁的时候:会调用析构函数。友元函数提供给我们访问成员的另一种方式。

    但这其中,会产生更多的细节。通过一些具体的细节,我们来感受一些类中的行为:

    类声明如下:

     1 # include "iostream"
     2 # ifndef STRNGBAD_H_
     3 # define STRNGBAD_H_
     4 class StringBad
     5 {
     6 private:
     7     char *str;
     8     int len;
     9     static int num_strings; //= 0;//可见除了const 量之外,类内部成员是不能在内部赋初值的
    10 public:
    11     StringBad(const char * s);
    12     StringBad();
    13     ~StringBad();
    14 
    15     friend std::ostream & operator<<(std::ostream & os, const StringBad & st);
    16 };
    17 # endif

    上述这个类声明中:

    和通常一样:包含了构造函数,析构函数,友元函数;但这里需要强调的是 静态成员变量:num_strings。当我们使用StringBad类生成A,B,C....诸多类对象的时候,A.str,B.str,C.str....这些变量本质上地址是不同的。但是A.num_strings,B.num_strings,A.num_strings,地址是相同的。即:静态成员是属于类,而不是属于对象的。

    我们看看这个类的定义:

     1 # include "cstring"
     2 # include "strngbad.h"
     3 
     4 using  std::cout;
     5 
     6 int StringBad::num_strings = 0;
     7 
     8 StringBad::StringBad(const char*s)
     9 {
    10     len = std::strlen(s);
    11     str = new char[len + 1];
    12     std::strcpy(str, s);
    13     num_strings++;
    14     cout << num_strings << ": "" << str << "" object created
    ";
    15 }
    16 
    17 StringBad::StringBad()
    18 {
    19     len = 4;
    20     str = new char[4];
    21     std::strcpy(str, "C++");
    22     num_strings++;
    23     cout << num_strings << ": "" << str << "" default created
    ";
    24 }
    25 
    26 StringBad::~StringBad()
    27 {
    28     cout << """ << str << "" object created. ";
    29     --num_strings;
    30     cout << num_strings << "left
    ";
    31     delete[] str;
    32 }
    33 
    34 std::ostream & operator<<(std::ostream & os, const StringBad & st)
    35 {
    36     os << st.str;//注意这里打印的是 str,如果不加str呢
    37     return os;
    38 }

    关于这个类定义:我们注意这么几点:第6行进行了静态变量的初始化。注意,对num_strings 进行初始化的时候,StringBad::num_strings表明num_strings成员属于StringBad

    问题:可以在类声明中进行初始化吗?

    答:不可以,类声明只是说明需要为什么样的类型分配多少空间,但并不真正的分配空间,因此不能在声明中初始化(const除外)。

    从这个类定义文件可以看出:无论是描述类成员函数,还是类成员变量.我们都要在前面描述其作用域!

    new 和delete在动态内存分配中是十分重要的。但是要注意:在构造函数中使用了new[],在析构函数中必有delete[].

    继续看调用文件:

     1 # include "iostream"
     2 using std::cout;
     3 # include "strngbad.h"
     4 
     5 void callme1(StringBad &);
     6 void callme2(StringBad);
     7 
     8 int main()
     9 {
    10     using std::endl;
    11     {
    12         cout << "Starting an inner block.
    ";
    13         StringBad headline1("hello world!");
    14         StringBad headline2("learning forever!");
    15         StringBad sports("i love trvaling!");
    16         cout << "headline1" << headline1 << endl;
    17         cout << "headline12"<< headline2 << endl;
    18         cout << "sports" << sports << endl;
    19         callme1(headline1);
    20         cout << "headline1" << headline1 << endl;
    21        callme2(headline2);
    22         cout << "headline2" << headline2 << endl;
    23         cout << "Initialize one object to another:
    ";
    24         StringBad sailor = sports;
    25         cout << "sailor: " << sailor << endl;
    26     }
    27     cout << "End of main()
    ";
    28     system("pause");
    29     return 0;
    30 }
    31 
    32 void callme1(StringBad & rsb)
    33 {
    34     cout << "String passed by reference:
    ";
    35     cout << "     
    " << rsb << ""
    ";
    36 }
    37 
    38 void callme2(StringBad  sb)
    39 {
    40     cout << "String passed by rvalue:
    ";
    41     cout << "     
    " << sb << ""
    ";
    42 }

    在此,给出第19行代码和第21行代码的一些细节:

    第19行代码,传递参数的行为是引用传递,21行为:值传递。

    再次强调:值传递 和 值返回 函数的两个本质特征:1. 进行值传递的时候,复制实参的副本,使得形参为实参的副本;2. 值返回的时候,复制返回值的副本(并销毁该变量(因为栈中的变量生存周期为函数调用期)),对副本进行后续操作。

    因此第21行的代码:在进行按值传递的时候,首先创建一个对象的副本。但在创建对象副本的时候,首先会调用构造函数,但是这个构造函数是谁呢?是之前类定义中的构造函数吗?如果是,显然这个创建副本的形式应该为:()或者(cha*),但问题是:这里是吗???显然不是,那是什么呢???其实为:B=A,即将已知的对象A赋值给临时对象B,这就如同第24行的代码。那么这样的初始化方式,调用哪个构造函数呢???其实,是一种叫做赋值构造函数,专门应对这种赋值初始化的操作。很明显,之前的声明和定义中并没有赋值构造函数。那么这个赋值构造函数只能由编译器产生。

    终于知道为何开始要求我们采用()或者{}的初始化方式,而不是"="的初始化方式。因为通常的我们可能并没有显示的定义自己的赋值构造函数,这个时候由编译器产生一个这样的函数,可能会存在一定的风险。但如果我们不可避免会采用到=或者值传递创建函数的形式,我们应该显示的定义自己的赋值构造函数,使得我们的程序风险更低。(实际上,通常的定义一个显示赋值构造函数更保险)

    总结:按值传递和按值返回对象的时候,都将调用赋值构造函数!2构造函数中创建一个显示赋值构造函数通常更保险

    我们在此再次声明:“hello World!”表示一个地址而不是字符串。当我们定义:p = “hello Word!”,假如我们定义:q = p,那么:是否是将字符串P复制了一遍,并存到了q的地址呢?并不是,而是:p,q都指向了“hello world!”,而这个时候,“hello world! ”其实看起来更像地址。

  • 相关阅读:
    Golang 接口(interface)
    Golang 结构体(struct)
    Golang fmt包介绍
    Golang的函数(func)
    Golang数据类型 (map)
    Golang 指针(pointer)
    Golang数据类型 切片(slice)
    操作系统学习笔记(五) 页面置换算法
    Python 元组、列表
    操作系统学习笔记(四) 存储模型和虚拟内存
  • 原文地址:https://www.cnblogs.com/shaonianpi/p/9971951.html
Copyright © 2011-2022 走看看