一些概念:类和结构、名称空间、复制构造函数、作用域和链接性 自动变量和堆栈

1、类和结构

      类描述看上去很像是包含成员函数以及public和private可见性标签的结构声明。实际上,C++对结构进行了扩展,使之具有与类相同的特性。他们之间唯一的区别是,结构的默认访问类型是public,而类为private。C++程序员通常使用类来实现类描述,而把结构限制为只表示纯粹的数据对象或没有私有部分的类。

class World
{
    float mass;  //private by default
    char name[20];  //private by default
public:
    void tellall(void);
    ...
};

      类的命名规则中,一种常见但不通用的约定——将类名首字母大写。

      一般来说,访问私有类成员的唯一方法是使用类方法。C++使用友元函数来避开这种限制。要让函数成为友元,需要在类声明中声明该函数,并在声明前加上关键字friend。

类的成员函数的两个特征:

  • 定义成员函数时,使用作用域解析符(::)来标识函数所属的类。
  • 类方法可以访问类的private组件。即,类的成员函数可以访问私有数据成员。
#include <iostream>
#include<cstring>

using namespace std;

class Stock
{
private:
    char company[30];
    int shares;
    double share_val;
    double total_val;
    void set_tot(){total_val=shares*share_val;}
public:
    void acquire(const char * co,int n,double pr);
    void buy(int num,double price);
    void sell(int num,double price);
    void update(double price);
    void show();
};

void Stock::acquire(const char *co, int n, double pr)
{
    std::strncpy(company,co,29);
    company[29]='\0';
    if(n<0)
    {
        std::cerr<<"Number of shares can't be negative. "
                <<company<<" shares set to 0.\n";
        shares=0;
    }
    else
        shares=n;
    share_val=pr;
    set_tot();
}

void Stock::buy(int num, double price)
{
    if(num<0)
    {
        std::cerr<<"Number of shares purchased can't be negative. "
                <<"Transaction is avorted.\n";
    }
    else
    {
        shares+=num;
        share_val=price;
        set_tot();
    }
}

void Stock::sell(int num, double price)
{
    using std::cerr;
    if(num<0)
    {
        cerr<<"Number of shares sold can't be negative. "
           <<"Transaction is aborted.\n";
    }
    else if(num>shares)
    {
        cerr<<"You can't sell more than you have! "
           <<"Transaction is aborted.\n";
    }
    else
    {
        shares-=num;
        share_val=price;
        set_tot();
    }
}

void Stock::update(double price)
{
    share_val=price;
    set_tot();
}

void Stock::show()
{
    using std::cout;
    using std::endl;
    cout<<"Company: "<<company
       <<" Shares: "<<shares<<endl
      <<" Share price: $"<<share_val
     <<" Total Worth: $"<<total_val<<endl;
}

int main(int argc, char *argv[])
{
    using std::cout;
    using std::ios_base;
    Stock stock1;
    stock1.acquire("NanoSmart",20,12.50);
    cout.setf(ios_base::fixed);
    cout.precision(2);
    cout.setf(ios_base::showpoint);
    stock1.show();
    stock1.buy(15,18.25);
    stock1.show();
    stock1.sell(400,20.00);
    stock1.show();
    return 0;
}

格式化命令:

        cout.setf(ios_base::fixed);//use fixed decimal point format

        cout.precision(2);//two places to right of decimal

        cout.setf(ios_base::showpoint);//show trailing zeros

2、名称空间

名称空间允许定义一个可在其中声明标识符的命名区域。这样做的目的是减少名称冲突,尤其当程序非常大,并使用多个厂商的代码时。可以通过使用作用域解析操作符、using声明或using编译指令,来使名称空间中的标识符可用。

//file1.cpp
#include<iostream>
using namespace std;
void other();
void another();
int x=10;
int y;
int main()
{
    cout<<x<<endl;
    {
        int x=4;
        cout<<x<<endl;
        cout<<y<<endl;
    }
    other();
    another();
    return 0;
}

void other()
{
    int y=1;
    cout<<"Other: "<<x<<", "<<y<<endl;
}

//file2.cpp
#include<iostream>
using namespace std;
extern int x;
namespace 
{
int y=-4;
}
void another()
{
    cout<<"another(): "<<x<<", "<<y<<endl;
}

3、复制构造函数  P386

复制构造函数用于将一个对象复制到新创建的对象中,用于初始化过程中。类的复制构造函数原型通常如下:

Class_nae(const Class_name  &);  //接收一个指向类对象的常量引用作为参数。

    (1)何时调用复制构造函数?

       a、新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。

      例如,假设motto时一个Stringbad对象,则下面4种声明都将调用复制构造函数:

StringBad ditto(motto);  //calls StringBad(const StringBad &)

StringBad metoo=moto;  //calls StringBad(const StringBad &)
 
StringBad also=String(motto);  //calls StringBad(const StringBad &)

StringBad * pStringBad=new StringBad(motto);  //calls StringBad(const StringBad &)



其中,中间的2种声明会使用复制构造函数直接创建metoo和also,也可能使用复制构造函数生成一个临时对象,然后将临时对象的内容赋给metoo和also。

        b、当函数按值传递对象或者函数返回对象时,都将调用复制构造函数。默认的复制构造函数将会复制原始变量的地址,它将指向原始变量的地址。

        由于按值传递对象将调用复制构造函数,因此应该按引用传递对象。这样可以节省调用构造函数的时间以及存储新对象的空间。按引用传递对象时,将会进行深度复制,此时复制构造函数将复制字符串并将副本的地址赋给str成员,而不仅仅是复制字符串地址。

     (2)复制构造函数的功能

        默认的复制构造函数将逐个复制非静态成员,复制的是成员的值。静态函数不受影响,因为静态函数属于整个类,而不是各个对象。

StringBad sailor=sports; 

等同于:

StringBad sailor;

sailor.str=sports.str;

sailor.len=sports.len;

        这里复制的并不是字符串,而是一个指向字符串的指针。也就是说,将sports初始化为sailor后,得到的两个指向同一个字符串的指针。

//StringBad.h
#include<iostream>
class StringBad
{
private:
    char * str; //pointer to string
    int len;    //length of string
    static int num_strings; //number of objects
    //enum{num_strings2=0};
    //static const int num_strings3=0;
public:
    StringBad(const char * s);
    StringBad();
    ~StringBad();
    friend std::ostream & operator<<(std::ostream & os,
                                     const StringBad & st);
};


//StringBad.cpp
#include<cstring>
#include"strngbad.h"
using std::cout;

int StringBad::num_strings=0;

StringBad::StringBad(const char *s)
{
    len=std::strlen(s);
    str=new char[len+1];
    std::strcpy(str,s);
    num_strings++;
    cout<<num_strings<<": \""<<str
       <<"\" object created\n";
    //str=s;

}

StringBad::StringBad()
{
    len=4;
    str=new char[4];
    std::strcpy(str,"C++");
    num_strings++;
    cout<<num_strings<<": \""<<str
       <<"\"default object created\n";
}

StringBad::~StringBad()
{
    cout<<"\""<<str<<"\" objet deleted, ";
    --num_strings;
    cout<<num_strings<<" left\n";
    delete [] str;
}

std::ostream & operator<<(std::ostream & os,const StringBad & st)
{
    os<<st.str;
    return os;
}


//useStringBad.cpp
#include<iostream>
#include "strngbad.h"

using std::cout;

void callme1(StringBad &);
void callme2(StringBad &);

int main()
{
    using std::endl;
    StringBad headline1("Celery Stalks at Midnight");
    StringBad headline2("Lettuce Prey");
    StringBad sports("Spinach Leaves Bowl for Dollars");
    cout<<"headline1: "<<headline1<<endl;
    cout<<"headline2: "<<headline2<<endl;
    cout<<"sports: "<<sports<<endl;
    callme1(headline1);
    cout<<"headline1: "<<headline1<<endl;
    callme2(headline2);
    cout<<"headline2: "<<headline2<<endl;
    cout<<"Initialize one object to another: \n";
    StringBad sailor=sports;
    cout<<"sallor: "<<sailor<<endl;
    cout<<"Assign one object to another: \n";
    StringBad knot;
    knot=headline1;
    cout<<"knot: "<<knot<<endl;
    cout<<"End of main()\n";
    return 0;
}

void callme1(StringBad & rsb)
{
    cout<<"String passed by reference: \n";
    cout<<" \""<<rsb<<"\"\n";
}

void callme2(StringBad & sb)
{
    cout<<"String passed by value: \n";
    cout<<" \""<<sb<<"\"\n";
}

     不允许再次释放已经释放过的内存,当再次尝试释放已经释放的内存时,程序会根据编译器的差异报出不同的不可预料的错误。

      其中,默认的复制构造函数不说明其行为,因此它不创建过程,也不增加计数器num_strings的的值。但析构函数更新了计数,并且在任何对象过期时都将被调用,而不管对象时如何被创建的

       该程序应提供一个队计数进行更新的显示复制构造函数:

String::String(const String & s)
{
   num_string++;
   ...//import stuff to go here
}

        如果类中包含这样的静态数据成员,即其值将在新对象被创建时发生变化,则应该提供一个显示复制构造函数来处理计数问题。

        如果类中包含了使用new初始化的指针成员,应当定义一个复制构造函数,以复制指向的数据,而不是指针,这杯称为深度复制。复制的另一种形式(成员复制或浅复制)只是复制指针值。浅复制仅浅浅地复制指针信息,而不会复制指针引用的结构。

String::String(const String & st)
{
    num_strings++;
    len=st.len;
    str=new char [len+1];
    std::strcpy(str,st.str);
}




//检查自我赋值的情况,释放成员指针以前指向的内存,复制数据而不仅仅是数据的地址,并返回一个指向调用对象的引用。
String & String::operator=(const String & st)
{
    if(this==&st)
        return *this;
    delete [] str;
    len=st.len;
    str=new char[len+1];
    std::strcpy(str,st.str);
    return *this;
}

4、作用域和链接性

        C++存储方案决定了变量保留在内存中的时间(存储持续性)以及程序的哪一部分可以访问它(作用域和链接性)。自动变量实在代码块(如函数体或函数体中的代码块)中定义的变量,仅当程序执行到包含定义的代码块时,它们才存在,并且可见。自动变量可以通过使用存储类型说明符auto或register或者根本不适用说明符(与使用auto相同)来声明。register说明符提示编译器,该变量的使用频率很高。

        静态变量在整个程序执行期间都存在。对于在函数外面定义的变量,其所属文件中位于该变量的定义后面的所有函数都可以使用它(文件作用域),并可在程序的其他文件中使用(外部链接性)。另一个文件要使用这种变量,必须使用extern关键字来声明它。对于文件间共享的变量,应在一个文件中包含其定义声明(不使用extern),并在其他文件中包含引用声明(使用extern)。在函数的外面使用关键字static定义变量的作用域为整个文件,但是不能用于其他文件(内部链接性)。在代码中使用关键字static定义变量被限制在该代码块内(局部作用域、无链接性),但在整个程序执行期间,它都一只存在并且保持原值。

       在默认情况下,C++函数的链接性为外部,因此可在文件间共享;但使用关键字static限定的函数的链接性为内部的,被限制在定义它的文件中。

5、自动变量和堆栈   P269

正确理解自动变量在函数中占用内存和释放内存。

    函数在被调用时,将函数中的自动变量自动添加到堆栈;然后在函数开始执行后,将函数中的形参和刚添加到堆栈上的值关联起来;当函数结束后,栈顶指针被重置为函数被调用前的值,从而释放自动变量使用的内存。

    进入堆栈中的局部变量遵循先进后出原则。

猜你喜欢

转载自blog.csdn.net/qq_35820102/article/details/83089054