首页 > 代码库 > C++基础学习教程(八)

C++基础学习教程(八)

转载请注明出处:http://blog.csdn.net/suool/article/details/38300117

引入

在进行下一步的学习之前,我们需要厘清几个概念.

RAII

首先介绍一个编程习语,”RAII”(ResourceAcquisition Is Initialization,资源获取即为初始化),他描述了利用构造函数\析构函数,并在函数返回时自动析构的机制.简言之,RAII意为构造函数获取一种资源;打开一个文件,一个网络连接,或仅仅是从某I/O流中复制一些标志.这种获取是对象初始化的一部分,而析构函数则释放该资源:关闭文件,断开网络连接,或者恢复I/O流中所有被修改的标志.

只要定义了某RAII类的对象,就可以使用该类,编译器会完成余下的相关工作.RAII类的构造函数通过他所需要的所有参数来获取资源.当某个RAII对象锁所在的函数返回的时,该对象会自动析构并释放资源,That is it.

有时候甚至不需要等到函数返回,在一条复合语句中定义了一个RAII对象,则该语句结束,控制流离开复合语句时,该对象即被析构.

下面举例:代码要实现的是:


/** @file RAII.cpp */
/** The color Class */
class color
{
public:
  color() : red_(0), green_(0), blue_(0) {}
  color(int r, int g, int b) : red_(r), green_(g), blue_(b) {}
  int red() const { return red_; }
  int green() const { return green_; }
  int blue() const { return blue_; }
  /// Because red(), green(), and blue() are supposed to be in the range [0,255],
  /// it should be possible to add them together in a single long integer.
  /// TODO: handle errors if any color component is out of range
  long int combined() const { return ((red() * 256L + green()) * 256) + blue(); }
private:
  int red_, green_, blue_;
};

inline bool operator==(color const& a, color const& b)
{
  return a.combined() == b.combined();
}

inline bool operator!=(color const& a, color const& b)
{
  return not (a == b);
}

inline bool order_color(color const& a, color const& b)
{
  return a.combined() < b.combined();
}

/// Write a color in HTML format: \#RRGGBB.
std::ostream& operator<<(std::ostream& out, color const& c)
{
  std::ostringstream tmp;
  // The hex manipulator tells a stream to write or read in hexadecimal (base 16).
  tmp << '#' << std::hex << std::setw(6) << std::setfill('0') << c.combined();
  out << tmp.str();
  return out;
}

class ioflags
{
public:
  /// Save the formatting flags from @p stream.
  ioflags(std::basic_ios<char>& stream) : stream_(stream), flags_(stream.flags()) {}
  /// Restore the formatting flags.
  ~ioflags() { stream_.flags(flags_); }
private:
  std::basic_ios<char>& stream_;
  std::ios_base::fmtflags flags_;
};

std::istream& operator>>(std::istream& in, color& c)
{
  ioflags flags(in);

  char hash;
  if (not (in >> hash))
    return in;
  if (hash != '#')
  {
    // malformed color: no leading # character
    in.unget();                 // return the character to the input stream
    in.setstate(in.failbit);    // set the failure state
    return in;
  }
  // Read the color number, which is hexadecimal: RRGGBB.
  int combined;
  in >> std::hex >> std::noskipws;
  if (not (in >> combined))
    return in;
  // Extract the R, G, and B bytes.
  int red, green, blue;
  blue = combined % 256;
  combined = combined / 256;
  green = combined % 256;
  combined = combined / 256;
  red = combined % 256;

  // Assign to c only after successfully reading all the color components.
  c = color(red, green, blue);

  return in;
}

其中上面的这段代码的ioflags就是RAII对象:



它包含的内容有:

  • l  std::basic_ios<char>是所有的如istream和ostream的示例.ioflags对于输入流和输出流都可以工作.
  • l  std::ios_base::fmtflags类型是所有格式化标志的类型.
  • l  无参数函数flags()返回当前全部格式化的标准.
  • l  单参数成员函数flags()将所有的格式化标志都设置给该参数.

Ioflags的使用方法是在一个函数或者复合语句中定义一个ioflags的类型变量,并将一个流对象作为唯一的参数传递给ioflags的构造函数.则该函数可以任意修改流的标志.本例中,输入操作符函数使用hex操作子将输入进制修改为十六进制.格式化标志存储了输入进制.操作符函数还关闭了skipws标志.该标志关闭则表示输入操作符不再允许井号(#)和颜色值之间有任何的空白符.

当输入函数返回时,ioflags对象被析构,析构函数恢复原先的格式化标志.如果不适用RAII技术,则>>操作符函数就需要在所有的四个返回点手动恢复标志.

声明和定义

下一个厘清的概念是函数声明和定义,这个在之前讲过



如下面的代码:

/** @file def_cl.cpp */
/** Declarations and Definitions of Member Functions */
class rational
{
public:
  rational();
  rational(int num);
  rational(int num, int den);
  void assign(int num, int den);
  int numerator() const;
  int denominator() const;
  rational& operator=(int num);
private:
  void reduce();
  int numerator_;
  int denominator_;
};

rational::rational()
: numerator_(0), denominator_(1)
{}

rational::rational(int num)
: numerator_(num), denominator_(1)
{}

rational::rational(int num, int den)
: numerator_(num), denominator_(den)
{
  reduce();
}

void rational::assign(int num, int den)
{
  numerator_ = num;
  denominator_ = den;
  reduce();
}

void rational::reduce()
{
  assert(denominator_ != 0);
  if (denominator_ < 0)
  {
    denominator_ = -denominator_;
    numerator_ = -numerator_;
  }
  int div(gcd(numerator_, denominator_));
  numerator_ = numerator_ / div;
  denominator_ = denominator_ / div;
}

int rational::numerator()
const
{
  return numerator_;
}

int rational::denominator()
const
{
  return denominator_;
}

rational& rational::operator=(int num)
{
  numerator_ = num;
  denominator_ = 1;
  return *this;
}

因为每个函数名字都由类名字开始,所以构造函数完整的名字是rational::rational,成员函数的名字形式都如rational::numerator, rational::operator =等到.C++把这种完整形式的名字称为限定名.

内联函数

在前面的讲解中我们说过一个函数内联函数”inline”,它提示编译器在函数的调用点以空间换时间.也可以将inline适用于成员函数上.事实上,如果一个函数仅仅返回一个数据成员而不作其它的事情话,则内联函数会提高速度,同时增加了程序的大小.



/** @file inline.cpp */
/** The rational Class with inline Member Functions */
class rational
{
public:
  rational(int num) : numerator_(num), denominator_(1) {}
  inline rational(int num, int den);
  void assign(int num, int den);
  int numerator() const                   { return numerator_; }
  int denominator() const                 { return denominator_; }
  rational& operator=(int num);
private:
  void reduce();
  int numerator_;
  int denominator_;
};

inline rational::rational(int num, int den)
: numerator_(num), denominator_(den)
{
  reduce();
}

void rational::assign(int num, int den)
{
  numerator_ = num;
  denominator_ = den;
  reduce();
}

void rational::reduce()
{
  assert(denominator_ != 0);
  if (denominator_ < 0)
  {
    denominator_ = -denominator_;
    numerator_ = -numerator_;
  }
  int div(gcd(numerator_, denominator_));
  numerator_ = numerator_ / div;
  denominator_ = denominator_ / div;
}

rational& rational::operator=(int num)
{
  numerator_ = num;
  denominator_ = 1;
  return *this;
}


静态变量

下面要说的是静态变量.

局部变量是自动类型的,即当进入一个函数或者局部块的时再分配内存\构造对象,而当函数返回或者控制离开块的时则析构对象并释放内存.因为所有的自动类型变量都是在栈上分配的,所以不必关心内存的分配和释放,这些工作由主机平台的正常的函数调用指令来完成.

同时main()函数也一样,在其中定义的变量也是自动类型的变量,在栈上分配空间.自动类型变量的行为遵循RAII等原则,这极大简化了一般的编程任务,但是不是所有的,有时候需要一个变量生命周期贯穿各个函数调用..

比如:如果一个函数要为一些对象生成唯一的身份编号,号码从1开始,并依次递增计数器.那么该函数就需要记录计数器的值,在函数返回之后也需如此.如下的代码片段:

/** Generating Unique Identification Numbers */
int generate_id()
{
  static int counter(0);
  ++counter;
  return counter;
}

关键字static告诉编译器该变量不是自动类型,而是静态类型.变量counter会在generate_id()被首次调用的时候进行初始化,其内存既不是自动类型,也不分配在程序栈上.所有的静态变量会分配在一个长久保留的地方.因而当generate_id()返回的时候,counter将保持原有值而不会丢失.

编写一个程序并多次调用generate_id(),观察是否在每次调用的时候生成新的值.代码如下:


/** Calling generate_id to Demonstrate Static Variables */
#include <iostream>
#include <ostream>

int generate_id()
{
  static int counter(0);
  ++counter;
  return counter;
}

int main()
{
  for (int i = 0; i != 10; ++i)
    std::cout << generate_id() << '\n';
}

结果如下:



同样可以在所有函数之外声明变量,因为他不属于任何的函数或者块之内,所以不是自动类型,而且其内存也必须为静态类型的,但对于这种变量不需要加static关键字.如下:你或许会想到全局变量,也可以这么称呼,但是这不是C++的标准术语.

/** Declaring counter Outside of the generate_id Function */
#include <iostream>
#include <ostream>

int counter;

int generate_id()
{
  ++counter;
  return counter;
}

int main()
{
  for (int i = 0; i != 10; ++i)
    std::cout << generate_id() << '\n';
}

和自动类型不一样的是,对于没有初始化序列的静态变量,无论是否为内置类型,均会被填充为零,如果类型为类且该类有自定义的构造函数,则会调用该类的默认构造函数进行初始化.使用C++静态变量的一个困难在于,程序难以控制他们的初始化时间.c++标准提供两个保证:

  • l  静态对象按照源文件中出现的次序依次进行初始化
  • l  静态对象会被在main()使用之前进行初始化,或者在main()调用任何函数前进行初始化.

静态数据成员

关键字static有许多用途.在累的某成员声明之前使用static则将其声明为一个静态数据成员.静态数据成员不属于任何的该类的对象,而是独立于他们的存在.该类的所有的对象共享一份该数据成员实例.

因为静态数据成员不是对象的一部分,因此不要将它写在构造函数的初始化器的类表中,正确的方法是像对待普通的全局变量那样来初始化静态数据成员,且勿忘记在成员名之前加上类名.下面一个示例:


/** Using Static Data Members for an ID Generator */
#include <iostream>
#include <ostream>

class generate_id
{
public:
  generate_id() : counter_(0) {}
  long next();
private:
  short counter_;
  static short prefix_;
  // The counter rolls over at a fairly low value (32,767), to ensure the code
  // is completely portable to all systems. Real code can use a higher value
  // before rolling over, but that involves C++ features that the book has not
  // yet covered.
  static long int const max_counter_ = 32767;
};

// Switch to random-number as the initial prefix for production code.
// short generate_id::prefix_(static_cast<short>(std::rand()));
short generate_id::prefix_(1);
long const generate_id::max_counter_;

long generate_id::next()
{
  if (counter_ == max_counter_)
    counter_ = 0;
  else
    ++counter_;
  return prefix_ * (max_counter_ + 1) + counter_;
}

int main()
{
  generate_id gen;           // Create an ID generator
  for (int i = 0; i != 10; ++i)
    std::cout << gen.next() << '\n';
}

内联函数

内联函数,即是inline函数,通常的做法是在一个头文件中声明函数,然后在另一个源文件中定义这些函数并链接到程序中.大多数成员函数的都与类分开.

但内联函数的规则和普通函数不同.调用内联函数的源文件还需要该函数的定义.而在每个使用内联函数的源文件总,该内联函数的定义不能多于一个,且在整个程序中,该函数的定义也必须相同.

写在头文件的函数的规则:头文件包含了非内联函数的声明和内联函数的定义.分开的源文件包仅定义非内联函数.

内联函数的缺点:

  • l  增加编译时间
  • l  增加重编译

因此在实际的编程中,把函数声明和定义分离开更加明智.

一份定义规则:编译器有一个规则就是,允许每个源文件中有一份类\函数\对象的定义.另一个规则是函数或全局对象的定义,可以在多个源文件中定义某个类,只要该定义在所有的源文件中相同即可.同时上面讲到的可以在多个源文件中定义内联函数,而每个源文件仅能有一份该内联函数的定义,且该内联函数在程序中的每个定义必须相同.

这些规则被称为ODR规则(One-Defination Rule).

编译器要求在每个源文件总必须遵守ODR,但是多个源文件的错误只能靠自己检查和注意,因为编译器不会检查多文件的ODR违例.



NEXT

一些算法\迭代器\异常基础知识.

转载请注明出处:http://blog.csdn.net/suool/article/details/38300117