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! ”其实看起来更像地址。

  • 相关阅读:
    蓝桥网试题 java 基础练习 特殊的数字
    蓝桥网试题 java 基础练习 杨辉三角形
    蓝桥网试题 java 基础练习 查找整数
    蓝桥网试题 java 基础练习 数列特征
    蓝桥网试题 java 基础练习 字母图形
    蓝桥网试题 java 基础练习 01字串
    蓝桥网试题 java 基础练习 回文数
    蓝桥网试题 java 基础练习 特殊回文数
    Using text search in Web page with Sikuli
    each of which 用法
  • 原文地址:https://www.cnblogs.com/shaonianpi/p/9971951.html
Copyright © 2011-2022 走看看