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;
}
keywordstatic告诉编译器该变量不是自己主动类型,而是静态类型.变量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';
}
结果例如以下:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3Vvb2w=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
相同能够在全部函数之外声明变量,由于他不属于不论什么的函数或者块之内,所以不是自己主动类型,并且其内存也必须为静态类型的,但对于这样的变量不须要加statickeyword.例如以下:你也许会想到全局变量,也能够这么称呼,可是这不是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()调用不论什么函数前进行初始化.
静态数据成员
keywordstatic有很多用途.在累的某成员声明之前使用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
C++基础学习教程(八)的更多相关文章
- salesforce零基础学习(八十)使用autoComplete 输入内容自动联想结果以及去重实现
项目中,我们有时候会需要实现自动联想功能,比如我们想输入用户或者联系人名称,去联想出系统中有的相关的用户和联系人,当点击以后获取相关的邮箱或者其他信息等等.这种情况下可以使用jquery ui中的au ...
- salesforce零基础学习(八十二)审批邮件获取最终审批人和审批意见
项目中,审批操作无处不在.配置审批流时,我们有时候会用到queue,related user设置当前步骤的审批人,审批人可以一个或者多个.当审批人有多个时,邮件中获取当前记录的审批人和审批意见就不能随 ...
- salesforce零基础学习(八十七)Apex 中Picklist类型通过Control 字段值获取Dependent List 值
注:本篇解决方案内容实现转自:http://mysalesforceescapade.blogspot.com/2015/03/getting-dependent-picklist-values-fr ...
- salesforce零基础学习(八十九)使用 input type=file 以及RemoteAction方式上传附件
在classic环境中,salesforce提供了<apex:inputFile>标签用来实现附件的上传以及内容获取.salesforce 零基础学习(二十四)解析csv格式内容中有类似的 ...
- javascript基础学习(八)
javascript之日期对象 学习要点: 日期对象 将日期对象转换为字符串 将日期对象中的日期和时间转换为字符串 日期对象中的日期 日期对象中的时间 设置日期对象中的日期 设置日期对象中的时间 与毫 ...
- Java基础学习笔记八 Java基础语法之接口和多态
接口 接口概念 接口是功能的集合,同样可看做是一种数据类型,是比抽象类更为抽象的”类”.接口只描述所应该具备的方法,并没有具体实现,具体的实现由接口的实现类(相当于接口的子类)来完成.这样将功能的定义 ...
- C++基础学习教程(一)
開始自己的C++复习进阶之路. 声明: 这次写的博文纯当是一个回想复习的教程.一些非常基础的知识将不再出现.或者一掠而过,这次的主要风格就是演示样例代码非常多~~~ 全部代码在Ubuntu 14.04 ...
- spring boot基础学习教程
Spring boot 标签(空格分隔): springboot HelloWorld 什么是spring boot Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新 ...
- webpack从0到1超详细超基础学习教程
概念 自己是一个一听到webpack就头大,看着一堆不知道那是什么玩意的东西总觉得自己做好前端就行了,但是在使用vue-cli的时候总觉得要改其中的一些东西进行项目初始化的时候能够更好使用!所以想要根 ...
随机推荐
- 阿斯钢iojeg9uhw8uhy平
http://www.huihui.cn/share/8424421 http://www.huihui.cn/share/8424375 http://www.huihui.cn/share/842 ...
- Google Ads Encryption Key
aes | floyd's Google Ads Encryption Key
- wp实例开发精品文章源码推荐
WP8 启动媒体应用 这个示例演示了如何选择正确的msAudioCategory类别的音像(AV)流来配置它作为一个音频播放流.具体地说,这个示例执行以下操作:启动一个媒体应用与“媒体 ...
- Lucene.Net 2.3.1开发介绍 —— 三、索引(三)
原文:Lucene.Net 2.3.1开发介绍 -- 三.索引(三) 3.Field配置所产生的效果 索引数据,简单的代码,只要两个方法就搞定了,而在索引过程中用到的一些类里最简单,作用也不小的就是F ...
- 【ASP.NET Web API教程】5.2 发送HTML表单数据:URL编码的表单数据
原文:[ASP.NET Web API教程]5.2 发送HTML表单数据:URL编码的表单数据 注:本文是[ASP.NET Web API系列教程]的一部分,如果您是第一次看本系列教程,请先看前面的内 ...
- [Android学习笔记]try-catch
private boolean test() { boolean result = true; String str = null; try { Log.d("test",&quo ...
- Delphi事件的广播2
上篇文章写了将事件分离成类的方法来实现事件的广播,这次将参考观察者模式来实现事件的广播.模式中主要有这两个角色: 发布者:发布者保存着一张观察者的列表,以便在必要的时候调用观察者的方法. 观察者:观察 ...
- Delphi读写UTF-8、Unicode格式文本文件
// UTF-8文件写入函数procedure SaveUTFFile(const FileName: string; S: string; WriteHeader: Boolean = True); ...
- HDU 1535 Invitation Cards (POJ 1511)
两次SPFA. 求 来 和 回 的最短路之和. 用Dijkstra+邻接矩阵确实好写+方便交换.可是这个有1000000个点.矩阵开不了. d1[]为 1~N 的最短路. 将全部边的 邻点 交换. d ...
- gant
http://gant.github.io/ http://gant.codehaus.org/