Add New Notes
83
Zim/Programme/C++/0长数组.txt
Normal file
@@ -0,0 +1,83 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-04-21T16:33:24+08:00
|
||||
|
||||
====== 0长数组 ======
|
||||
Created Thursday 21 April 2011
|
||||
|
||||
0长度的数组:在标准c里面,是不允许0长度的数组的,gcc允许。的确带来了一些方便吧。最主要的应用是表示可变长度的内容时。
|
||||
|
||||
|
||||
例如:
|
||||
struct line
|
||||
{
|
||||
int length;
|
||||
char contents[0];
|
||||
};
|
||||
struct line * x = (struct line*)malloc(sizeof(struct line) + content_length);
|
||||
x->length = content_length;
|
||||
在标准c里面,contents长度至少为1才行,这样写malloc的时候长度计算就要复杂一点。
|
||||
|
||||
realtang 前辈例子中的可变长度似乎最好直接定义为指针再单独分配空间:
|
||||
代码:
|
||||
|
||||
struct line { int length; char* contents; } ...
|
||||
struct line * x = (struct line*)malloc(sizeof(struct line));
|
||||
x->length = content_length;
|
||||
x->contents = (char *)malloc(x->length);
|
||||
|
||||
下面是典型的用法
|
||||
代码:
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
int main() {
|
||||
struct line {
|
||||
int length;
|
||||
char content[0];
|
||||
};
|
||||
char *hello = "Hello";
|
||||
struct line * x = (struct line*)malloc(sizeof(struct line) + strlen(hello) + 1);
|
||||
x->length = strlen(hello) + 1;
|
||||
strcpy(x->content,hello);
|
||||
printf("%s\n",x->content);
|
||||
return 0;
|
||||
}
|
||||
|
||||
数组为0的例子,能想到比较而且需要的例子是通信的领域。 一个数据包括头部(header)和数据(payload), 头部一般有一字段指定数据长度。 但有时并没有数据要发送,为了让对端知道自己还是正常的,通信没有中断,可以发一个空包,在处理时可以用0大小的数组来表示空包。
|
||||
一般如果不支持0大小数组的系统,__在结构体定义的时候, 可以定义一个最小的数组__(如数组大小为1),不过如果要发的是空包时, 这个大小为1的数组已经浪费了一些内存,在大容量的嵌入式系统的,这些浪费内存的相当宝贵。 至于数组越界,那是程序员要注意的事情,根数组大小是不是0没有什么关系。
|
||||
|
||||
以下的结构理论上也可以,但处理上已经不方便了, header 和 payload已经分离了。发送数据得根据payload或length的值调用两次memcpy 把要发送的数据复制到发送缓冲区。
|
||||
代码:
|
||||
|
||||
struct {
|
||||
unsigned int length;
|
||||
char* payload;
|
||||
}
|
||||
|
||||
http://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html
|
||||
6.17 Arrays of Length Zero
|
||||
|
||||
Zero-length arrays are allowed in GNU C. They are very useful as the last element of a structure which is really a header for a variable-length object:
|
||||
|
||||
struct line {
|
||||
int length;
|
||||
char contents[0];
|
||||
};
|
||||
|
||||
struct line *thisline = (struct line *)
|
||||
malloc (sizeof (struct line) + this_length);
|
||||
thisline->length = this_length;
|
||||
|
||||
In ISO C90, you would have to give contents a length of 1, which means either you waste space or complicate the argument to malloc.
|
||||
|
||||
In ISO C99, you would use a flexible array member, which is slightly different in syntax and semantics:
|
||||
|
||||
Flexible array members are written as contents[] without the 0.
|
||||
Flexible array members have incomplete type, and so the sizeof operator may not be applied. As a quirk of the original implementation of zero-length arrays, sizeof evaluates to zero.
|
||||
Flexible array members may only appear as the last member of a struct that is otherwise non-empty.
|
||||
A structure containing a flexible array member, or a union containing such a structure (possibly recursively), may not be a member of a structure or an element of an array. (However, these uses are permitted by GCC as extensions.)
|
||||
|
||||
|
||||
|
||||
171
Zim/Programme/C++/C++_Standard_Library.txt
Normal file
@@ -0,0 +1,171 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-07T10:36:07+08:00
|
||||
|
||||
====== C++ Standard Library ======
|
||||
Created Tuesday 07 February 2012
|
||||
http://en.wikipedia.org/wiki/C%2B%2B_standard_library
|
||||
|
||||
In C++, the C++ Standard Library is a collection of __classes and functions,__ which are written in **the core language **and part of the C++ ISO Standard itself.[1] The C++ Standard Library provides** several generic containers**, functions to utilise and manipulate these containers, **function objects**, **generic strings** and **streams** (including interactive and file I/O), support for some language features, and everyday functions for tasks such as finding the square root of a number. The C++ Standard Library also__ incorporates 18 headers of the ISO C90 C standard library ending with ".h"__, but their use is deprecated.[2] All other headers in the C++ Standard Library **do not end in ".h"**. Features of the C++ Standard Library are declared within __the std namespace__.
|
||||
|
||||
The C++ Standard Library is based upon conventions introduced by the __Standard Template Library (STL)__. Although the C++ Standard Library and the STL share many features, neither is a strict superset of the other. In particular, the C++ Standard Library has also been influenced[3] by the work of Alexander Stepanov and Meng Lee.[4]
|
||||
|
||||
The C++ Standard Library underwent ISO standardization as part of the C++ ISO Standardization effort, and is undergoing further work[5] regarding standardization of expanded functionality.
|
||||
|
||||
===== C++ Standard Library =====
|
||||
|
||||
fstream
|
||||
iomanip
|
||||
ios
|
||||
iostream
|
||||
sstream
|
||||
string
|
||||
|
||||
===== Standard Template Library =====
|
||||
|
||||
algorithm
|
||||
bitset
|
||||
functional
|
||||
iterator
|
||||
Sequence containers
|
||||
Associative containers
|
||||
Unordered associative containers
|
||||
Container adaptors
|
||||
|
||||
===== C standard library =====
|
||||
|
||||
Data types
|
||||
Character classification
|
||||
Strings
|
||||
Mathematics
|
||||
File input/output
|
||||
Date/time
|
||||
Localization
|
||||
Memory allocation
|
||||
Process control
|
||||
Signals
|
||||
|
||||
===== Miscellaneous headers: =====
|
||||
|
||||
<assert.h>
|
||||
<errno.h>
|
||||
<iso646.h>
|
||||
<setjmp.h>
|
||||
<stdarg.h>
|
||||
|
||||
===== Contents =====
|
||||
1 Standard headers
|
||||
1.1 Containers
|
||||
1.2 General
|
||||
1.3 Strings
|
||||
1.4 Streams and Input/Output
|
||||
1.5 Numerics
|
||||
1.6 Language support
|
||||
1.7 C standard library
|
||||
2 See also
|
||||
3 References
|
||||
4 External links
|
||||
|
||||
===== Standard headers =====
|
||||
The following files contain the declarations of the C++ Standard Library.
|
||||
|
||||
==== Containers ====
|
||||
<array>
|
||||
New in C++11 and TR1. Provides the container class template __std::array__, a container for **a fixed sized array**.
|
||||
<bitset>
|
||||
Provides the specialized container class __std::bitset__, a bit array.
|
||||
<deque>
|
||||
Provides the container class template __std::deque__, a double-ended queue.
|
||||
<forward_list>
|
||||
New in C++11 and TR1. Provides the container class template __std::forward_list__, a singly linked list.
|
||||
<list>
|
||||
Provides the container class template __std::list__, a doubly linked list.
|
||||
<map>
|
||||
Provides the container **class templates** __std::map__ and __std::multimap__, sorted associative array and multimap.
|
||||
<queue>
|
||||
Provides the container** adapter class** __std::queue__, a single-ended queue.
|
||||
<set>
|
||||
Provides the container class templates __std::set__ and __std::multiset__, **sorted** associative containers or sets.
|
||||
<stack>
|
||||
Provides the container adapter class__ std::stack__, a stack.
|
||||
<unordered_map>
|
||||
New in C++11 and TR1. Provides the container class template __std::unordered_map __and __std::unordered_multimap__, hash tables.
|
||||
<unordered_set>
|
||||
New in C++11 and TR1. Provides the container class template__ std::unordered_set__ and __std::unordered_multiset__.
|
||||
<vector>
|
||||
Provides the container class template __std::vector__, **a dynamic array**.
|
||||
|
||||
==== General ====
|
||||
<algorithm>
|
||||
Provides definitions of many __container algorithms__.
|
||||
<functional>
|
||||
Provides several function objects, designed for use with the standard algorithms.
|
||||
<iterator>
|
||||
Provides classes and templates for working with iterators.
|
||||
<locale>
|
||||
Provides classes and templates for working with locales.
|
||||
<memory>
|
||||
Provides facilities for memory management in C++, including the class template __std::auto_ptr__.
|
||||
<stdexcept>
|
||||
Contains standard exception classes such as std::logic_error and std::runtime_error, both derived from std::exception.
|
||||
<tuple>
|
||||
New in C++11 and TR1. Provides a class template__ std::tuple__, a tuple.
|
||||
<utility>
|
||||
Provides the template class std::pair, for working with pairs (two-member tuples) of objects.
|
||||
|
||||
==== Strings ====
|
||||
<string>
|
||||
Provides the C++ standard string classes and templates.
|
||||
|
||||
==== Streams and Input/Output ====
|
||||
<fstream>
|
||||
Provides facilities for file-based input and output. See fstream.
|
||||
<iomanip>
|
||||
Provides facilities to __manipulate output formatting__, such as the base used when formatting integers and the precision of floating point values.
|
||||
<ios>
|
||||
Provides several types and functions basic to the operation of iostreams.
|
||||
<iosfwd>
|
||||
Provides forward declarations of several I/O-related class templates.
|
||||
<iostream>
|
||||
Provides C++ input and output fundamentals. See iostream.
|
||||
<istream>
|
||||
Provides the template class std::istream and other supporting classes for input.
|
||||
<ostream>
|
||||
Provides the template class std::ostream and other supporting classes for output.
|
||||
__<sstream>__
|
||||
Provides the template class std::sstream and other supporting classes for string manipulation.
|
||||
<streambuf>
|
||||
Provides reading and writing functionality to/from certain types of **character sequences**, such as external files or strings.
|
||||
|
||||
==== Numerics ====
|
||||
<complex>
|
||||
Provides class template std::complex and associated functions for working with complex numbers.
|
||||
|
||||
<numeric>
|
||||
Provides algorithms for numerical processing
|
||||
<valarray>
|
||||
Provides the template class std::valarray, an array class optimized for numeric processing.
|
||||
|
||||
==== Language support ====
|
||||
<exception>
|
||||
Provides several types and functions related to __exception handling__, including std::exception, the base class of all exceptions thrown by the Standard Library.
|
||||
<limits>
|
||||
Provides the template class std::numeric_limits, used for describing properties of fundamental numeric types.
|
||||
<new>
|
||||
Provides __operators new and delete__ and other functions and types composing the fundamentals of C++ memory management.
|
||||
<typeinfo>
|
||||
Provides facilities for working with C++ run-time type information.
|
||||
|
||||
===== C standard library =====
|
||||
Main article: C Standard Library
|
||||
|
||||
Each header from the C Standard Library is included in the C++ Standard Library under a different name, __generated by removing the .h, and adding a 'c' at the start;__ for example, 'time.h' becomes 'ctime'. The only difference between these headers and the traditional C Standard Library headers is that where possible the functions should be placed into the std:: namespace (although few compilers actually do this). In ISO C, functions in the standard library are allowed to be implemented by macros, which is not allowed by ISO C++.
|
||||
|
||||
===== See also =====
|
||||
|
||||
Apache C++ Standard Library
|
||||
Boost C++ Libraries
|
||||
C POSIX library
|
||||
C standard library
|
||||
Standard library
|
||||
Technical Report 1
|
||||
99
Zim/Programme/C++/C++_Standard_Template_Library.txt
Normal file
@@ -0,0 +1,99 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-07T10:55:30+08:00
|
||||
|
||||
====== C++ Standard Template Library ======
|
||||
Created Tuesday 07 February 2012
|
||||
http://en.wikipedia.org/wiki/Standard_Template_Library
|
||||
|
||||
The Standard Template Library (STL) is a C++ software library which heavily influenced many parts of the** C++ Standard Library**. It provides four components called __algorithms, containers, functors, and iterators__.
|
||||
|
||||
The STL provides a ready-made set of common classes for C++, such as containers and associative arrays, that can be used with any built-in type and with any user-defined type that __supports some elementary operations__ (such as copying and assignment). STL __algorithms are independent of containers__, which significantly reduces the complexity of the library.
|
||||
|
||||
The STL achieves its results through the use of templates. This approach provides __compile-time polymorphism__ that is often more efficient than traditional run-time polymorphism. Modern C++ compilers are tuned to minimize any abstraction penalty arising from heavy use of the STL.
|
||||
|
||||
The STL was created as the first library of __generic algorithms and data structures__ for C++, with four ideas in mind: generic programming, abstractness without loss of efficiency, the Von Neumann computation model,[1] and value semantics.
|
||||
|
||||
===== Contents =====
|
||||
|
||||
1 Composition
|
||||
1.1 Containers
|
||||
1.2 Iterators
|
||||
1.3 Algorithms
|
||||
1.4 Functors
|
||||
2 History
|
||||
3 Criticisms
|
||||
3.1 Quality of implementation
|
||||
3.2 Other issues
|
||||
4 Implementations
|
||||
5 See also
|
||||
6 Notes
|
||||
7 References
|
||||
8 External links
|
||||
|
||||
===== Composition =====
|
||||
|
||||
==== Containers ====
|
||||
The STL contains __sequence containers and associative containers__. The standard sequence containers include__ vector, deque, and list__. The standard associative containers are __set, multiset, map, and multimap__. There are also container adaptors __queue, priority_queue, and stack__, that are containers with specific interface, using other containers as implementation.
|
||||
|
||||
Container Description
|
||||
Simple Containers
|
||||
__pair__ The pair container is a simple associative container consisting of __a 2-tuple __of data elements or objects, called 'first' and 'second', in that fixed order. The STL 'pair' can be **assigned, copied and compared**. The array of objects allocated in a map or hash_map (described below) are of __type 'pair' by default__, where all the 'first' elements act as the **unique keys**, each associated with their 'second'** value objects**.
|
||||
|
||||
Sequences (Arrays/Linked Lists): ordered collections
|
||||
__vector __ **a dynamic array**, like C array (i.e., capable of __random access__) with the ability to__ resize itself automatically__ when inserting or erasing an object. Inserting and removing an element to/from back of the vector at the end takes amortized constant time. Inserting and erasing at the beginning or in the middle is linear in time. A specialization for __type bool__ exists, which optimizes for space by storing bool values as bits.
|
||||
__list__ **a doubly linked list**; elements are __not stored in contiguous memory__. Opposite performance from a vector. Slow lookup and access (linear time), but once a position has been found, quick insertion and deletion (constant time).
|
||||
__deque__ (double-ended queue) __a vector__ with insertion/erase at the beginning or end in amortized constant time, however lacking some guarantees on iterator validity after altering the deque.
|
||||
|
||||
Container adaptors
|
||||
__queue__ Provides FIFO queue interface in terms of push/pop/front/back operations. Any sequence supporting operations front(), back(), push_back(), and pop_front() can be used to instantiate queue (e.g. list and deque).
|
||||
__priority_queue__ Provides priority queue interface in terms of push/pop/top operations (the element with the highest priority is on top). Any random-access sequence supporting operations front(), push_back(), and pop_back() can be used to instantiate priority_queue (e.g. vector and deque). Elements should additionally __support comparison__ (to determine which element has a higher priority and should be popped first).
|
||||
__stack__ Provides LIFO stack interface in terms of push/pop/top operations (the last-inserted element is on top). Any sequence supporting operations back(), push_back(), and pop_back() can be used to instantiate stack (e.g. vector, list, and deque).
|
||||
|
||||
Associative containers: **unordered** collections
|
||||
__set __ a mathematical set; inserting/erasing elements in a set does not invalidate iterators pointing in the set. Provides set operations union, intersection, difference, symmetric difference and test of inclusion. Type of data must __implement comparison operator <__ or custom comparator function must be specified; such comparison operator or comparator function must guarantee strict weak ordering, otherwise behavior is undefined. Typically implemented using a self-balancing binary search tree.
|
||||
__multiset__ same as a set, but allows duplicate elements.
|
||||
__map __ **an associative array**; allows mapping from one data item (a key) to another (a value). Type of key must implement comparison operator < or custom comparator function must be specified; such comparison operator or comparator function must guarantee strict weak ordering, otherwise behavior is undefined. Typically implemented using a self-balancing binary search tree.
|
||||
__multimap __ same as a map, but allows duplicate keys.
|
||||
__hash_set__
|
||||
__hash_multiset__
|
||||
__hash_map__
|
||||
__hash_multimap __ similar to a set, multiset, map, or multimap, respectively, but __implemented using a hash table__; keys are not ordered, but a hash function must exist for the key type. These containers are not part of the C++ Standard Library, but are included in SGI's STL extensions, and are included in common libraries such as the GNU C++ Library in the ____gnu_cxx namespace__. These are scheduled to be added to the C++ standard as part of TR1, with the slightly different names of** unordered_set, unordered_multiset, unordered_map and unordered_multimap**.
|
||||
Other types of containers
|
||||
__bitset __ stores series of bits similar to __a fixed-sized vector of bools__. Implements bitwise operations and lacks iterators. Not a Sequence.
|
||||
__valarray__ another C-like array like vector, but is designed for high speed numerics at the expense of some programming ease and general purpose use. It has many features that make it ideally suited for use with vector processors in traditional vector supercomputers and SIMD units in consumer-level scalar processors, and also ease vector mathematics programming even in scalar computers.
|
||||
|
||||
===== Iterators =====
|
||||
The STL implements five different types of iterators. These are __input iterators__ (that can only be used to read a sequence of values), __output iterators__ (that can only be used to write a sequence of values), __forward iterators __(that can be read, written to, and move forward), __bidirectional iterators__ (that are like forward iterators, but can also move backwards) and __random access iterators__ (that can move freely any number of steps in one operation).
|
||||
|
||||
It is possible to have bidirectional iterators act like random access iterators, as moving forward ten steps could be done by simply moving forward a step at a time a total of ten times. However, having distinct random access iterators__ offers efficiency advantages__. For example, a vector would have a random access iterator, but a list only a bidirectional iterator.
|
||||
|
||||
Iterators are the major feature that__ allow the generality of the STL__. For example, an algorithm to reverse a sequence can be implemented using bidirectional iterators, and then the same implementation can be used on lists, vectors and deques. User-created containers only have to provide an iterator that implements one of the five **standard iterator interfaces**, and __all the algorithms provided in the STL can be used on the container__.
|
||||
|
||||
This generality also comes at a price at times. For example, performing a search on an associative container such as a map or set can be much slower using iterators than by __calling member functions__ offered by the container itself. This is because an associative container's methods can take advantage of knowledge of the internal structure, which is opaque to algorithms using iterators.
|
||||
|
||||
===== Algorithms =====
|
||||
A large number of algorithms to perform operations such as searching and sorting are provided in the STL, each implemented to require a certain level of iterator (and therefore will work on any container that provides an interface by iterators).
|
||||
|
||||
===== Functors =====
|
||||
The STL includes classes that __overload the function operator (operator()). Classes that do this are called functors or function objects__. They are useful for **keeping and retrieving state information** in functions passed into other functions. Regular function pointers can also be used as functors.
|
||||
|
||||
A particularly common type of functor is the __predicate__. For example, algorithms like **find_if** take a **unary** predicate that operates on the elements of a sequence. Algorithms like sort, partial_sort, nth_element and all sorted containers use a binary predicate that must provide a strict weak ordering, that is, it must behave like a membership test on a transitive, irreflexive and antisymmetric binary relation. If none is supplied, these algorithms and containers use less by default, which in turn calls the less-than-operator <.
|
||||
|
||||
===== History =====
|
||||
|
||||
The architecture of STL is largely the creation of Alexander Stepanov. In 1979 he began working out his initial ideas of __generic programming__ and exploring their potential for revolutionizing software development. Although David Musser had developed and advocated some aspects of generic programming already by year 1971, it was limited to a rather specialized area of software development (computer algebra).
|
||||
|
||||
Stepanov recognized the full potential for generic programming and persuaded his then-colleagues at General Electric Research and Development (including, primarily, David Musser and Deepak Kapur) that __generic programming should be pursued as a comprehensive basis for software development__. At the time there was no real support in any programming language for generic programming.
|
||||
|
||||
The first major language to provide such support was __Ada__, with its generic units feature. By 1987 Stepanov and Musser had developed and published an Ada library for list processing that embodied the results of much of their research on generic programming. However, Ada had not achieved much acceptance outside the defense industry and C++ seemed more likely to become widely used and provide good support for generic programming even though the language was relatively immature. Another reason for turning to C++, which Stepanov recognized early on, was the C/C++ model of computation that allows very flexible access to storage via pointers, which is crucial to achieving generality without losing efficiency.
|
||||
|
||||
Much research and experimentation were needed, not just to develop individual components, but to develop an overall architecture for a component library based on generic programming. First at AT&T Bell Laboratories and later at Hewlett-Packard Research Labs (HP), Stepanov experimented with many architectural and algorithm formulations, first in C and later in C++. Musser collaborated in this research and in 1992 Meng Lee joined Stepanov's project at HP and became a major contributor.
|
||||
|
||||
This work undoubtedly would have continued for some time being just a research project or at best would have resulted in an HP proprietary library, if Andrew Koenig of Bell Labs had not become aware of the work and asked Stepanov to present the main ideas at a November 1993 meeting of the ANSI/ISO committee for C++ standardization. The committee's response was overwhelmingly favorable and led to a request from Koenig for a formal proposal in time for the March 1994 meeting. Despite the tremendous time pressure, Alex and Meng were able to produce a draft proposal that received preliminary approval at that meeting.
|
||||
|
||||
The committee had several requests for changes and extensions (some of them major), and a small group of committee members met with Stepanov and Lee to help work out the details. The requirements for the most significant extension (associative containers) had to be shown to be consistent by fully implementing them, a task Stepanov delegated to Musser. It would have been quite easy for the whole enterprise to spin out of control at this point, but again Stepanov and Lee met the challenge and produced a proposal that received final approval at the July 1994 ANSI/ISO committee meeting. (Additional details of this history can be found in Stevens.) Subsequently, the Stepanov and Lee document 17 was incorporated into the ANSI/ISO C++ draft standard (1, parts of clauses 17 through 27). It also influenced other parts of the C++ Standard Library, such as the string facilities, and some of the previously adopted standards in those areas were revised accordingly.
|
||||
|
||||
In spite of STL's success with the committee, there remained the question of how STL would make its way into actual availability and use. With the STL requirements part of the publicly available draft standard, compiler vendors and independent software library vendors could of course develop their own implementations and market them as separate products or as selling points for their other wares. One of the first edition's authors, Atul Saini, was among the first to recognize the commercial potential and began exploring it as a line of business for his company, Modena Software Incorporated, even before STL had been fully accepted by the committee.
|
||||
|
||||
The prospects for early widespread dissemination of STL were considerably improved with Hewlett-Packard's decision to make its implementation freely available on the Internet in August 1994. This implementation, developed by Stepanov, Lee, and Musser during the standardization process, became the basis of many implementations offered by compiler and library vendors today.
|
||||
377
Zim/Programme/C++/C++中前置声明的应用与陷阱.txt
Normal file
@@ -0,0 +1,377 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-17T18:31:04+08:00
|
||||
|
||||
====== C++中前置声明的应用与陷阱 ======
|
||||
Created Friday 17 February 2012
|
||||
|
||||
http://blog.csdn.net/yunyun1886358/article/details/5672574
|
||||
|
||||
===== 前置声明的使用 =====
|
||||
|
||||
有一定C++开发经验的朋友可能会遇到这样的场景:两个类A与B是__强耦合关系__,类A要引用B的对象,类B也要引用类A的对象。好的,不难,我的第一直觉让我写出这样的代码:
|
||||
// A.h
|
||||
#include "B.h"
|
||||
class A
|
||||
{
|
||||
__B b; //编译器在编译A时,必须要知道B类的定义。否则无法为A分配内存空间。__
|
||||
public:
|
||||
A(void);
|
||||
virtual ~A(void);
|
||||
};
|
||||
|
||||
//A.cpp
|
||||
** #include "A.h" **
|
||||
A::A(void)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
A::~A(void)
|
||||
{
|
||||
}
|
||||
|
||||
// B.h
|
||||
#include "A.h"
|
||||
class B
|
||||
{
|
||||
A a;
|
||||
public:
|
||||
B(void);
|
||||
~B(void);
|
||||
};
|
||||
|
||||
// B.cpp
|
||||
#include "B.h"
|
||||
B::B(void)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
B::~B(void)
|
||||
{
|
||||
}
|
||||
|
||||
好的,完成,编译一下A.cpp,不通过。再编译B.cpp,还是不通过。编译器都被搞晕了,编译器去编译A.h,发现包含了B.h,就去编译B.h。编译B.h的时候发现包含了A.h,但是A.h已经编译过了(其实没有编译完成,可能编译器做了记录,A.h已经被编译了,这样可以避免陷入死循环。编译出错总比死循环强点),就没有再次编译A.h就继续编译。后面发现用到了A的定义,这下好了,A的定义并没有编译完成,所以找不到A的定义,就编译出错了。提示信息如下:
|
||||
|
||||
1>d:/vs2010/test/test/a.h(5): error C2146: syntax error : missing ';' before identifier 'b'
|
||||
1>d:/vs2010/test/test/a.h(5): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
|
||||
1>d:/vs2010/test/test/a.h(5): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
|
||||
|
||||
那怎么办?有办法,C++为我们提供了前置声明。前置声明是什么?前置声明就是我在声明一个类(CHouse)的时候,用到了__另外一个类的定义__(CBed),但是CBed还没有定义呢,而且我还__先不需要CBed的定义__,**只要知道CBed是一个类就够了**。那好,我就先声明类CBed,告诉编译器CBed是一个类(不用包含CBed的头文件):
|
||||
|
||||
|
||||
class CBed;
|
||||
|
||||
然后在CHouse中用到CBed的,都用__CBed的指针类型代替__(因为指针类型固定大小的,但是CBed的大小只用知道了CBed定义才能确定)。等到__要实现CHouse定义的时候__,就必须要知道CBed的定义了,那是再包含CBed的头文件就行了。
|
||||
|
||||
前置声明有时候很有用,比如说两个类相互依赖的时候要。还有前置声明可以__减少头文件的包含层次__,减少出错可能。上面说的例子。
|
||||
|
||||
|
||||
// House.h
|
||||
__ class CBed; __// 盖房子时:现在先不买,肯定要买床的
|
||||
class CHouse
|
||||
{
|
||||
__CBed*__ bed; // 我先给床留个位置
|
||||
public:
|
||||
CHouse(void);
|
||||
virtual ~CHouse(void);
|
||||
void GoToBed();
|
||||
};
|
||||
|
||||
// House.cpp
|
||||
|
||||
#include "House.h"
|
||||
#include "Bed.h" // 等房子开始装修了,要买床了
|
||||
CHouse::CHouse(void)
|
||||
{
|
||||
bed = new CBed(); // 把床放进房子
|
||||
}
|
||||
|
||||
CHouse::~CHouse(void)
|
||||
{
|
||||
}
|
||||
|
||||
void CHouse::GoToBed()
|
||||
{
|
||||
bed->Sleep();
|
||||
}
|
||||
|
||||
// Bed.h
|
||||
class CBed
|
||||
{
|
||||
|
||||
public:
|
||||
CBed(void);
|
||||
~CBed(void);
|
||||
void Sleep();
|
||||
};
|
||||
|
||||
// Bed.cpp
|
||||
#include "Bed.h"
|
||||
|
||||
CBed::CBed(void)
|
||||
{
|
||||
}
|
||||
|
||||
CBed::~CBed(void)
|
||||
{
|
||||
}
|
||||
|
||||
void CBed::Sleep()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
前置声明中的陷阱,注意这里有陷阱:
|
||||
|
||||
1、CBed* bed;__必须用指针或引用__
|
||||
|
||||
|
||||
|
||||
// House.h
|
||||
class CBed; // 盖房子时:现在先不买,肯定要买床的
|
||||
class CHouse
|
||||
{
|
||||
CBed& bed; // 我先给床留个位置
|
||||
// __CBed bed; // 编译出错 __
|
||||
public:
|
||||
CHouse(void);
|
||||
CHouse(CBed& bedTmp);
|
||||
virtual ~CHouse(void);
|
||||
void GoToBed();
|
||||
};
|
||||
|
||||
// House.cpp
|
||||
#include "Bed.h" // 等房子开始装修了,要买床了
|
||||
#include "House.h"
|
||||
|
||||
CHouse::CHouse(void)
|
||||
: bed(*new CBed())
|
||||
{
|
||||
CBed* bedTmp = new CBed(); // 把床放进房子
|
||||
bed = *bedTmp;
|
||||
}
|
||||
|
||||
CHouse::CHouse(CBed& bedTmp)
|
||||
: bed(bedTmp)
|
||||
{
|
||||
}
|
||||
|
||||
CHouse::~CHouse(void)
|
||||
{
|
||||
delete &bed;
|
||||
}
|
||||
|
||||
void CHouse::GoToBed()
|
||||
{
|
||||
bed.Sleep();
|
||||
}
|
||||
|
||||
2、__不能在CHouse的声明中使用CBed的方法__
|
||||
|
||||
bed->Sleep的左边必须指向类/结构/联合/泛型类型
|
||||
|
||||
class CBed; // 盖房子时:现在先不买,肯定要买床的
|
||||
class CHouse
|
||||
{
|
||||
CBed* bed; // 我先给床留个位置
|
||||
// CBed bed; // 编译出错
|
||||
public:
|
||||
CHouse(void);
|
||||
virtual ~CHouse(void);
|
||||
void GoToBed()
|
||||
{
|
||||
bed->Sleep(); // __编译出错,床都没买,怎么能睡 __
|
||||
}
|
||||
};
|
||||
|
||||
__3、在CBed定义之前调用CBed的析构函数__
|
||||
|
||||
[c-sharp] view plaincopy
|
||||
|
||||
// House.h
|
||||
class CBed; // 盖房子时:现在先不买,肯定要买床的
|
||||
class CHouse
|
||||
{
|
||||
CBed* bed; // 我先给床留个位置
|
||||
// CBed bed; // 编译出错
|
||||
public:
|
||||
CHouse(void);
|
||||
virtual ~CHouse(void);
|
||||
void GoToBed();
|
||||
void RemoveBed()
|
||||
{
|
||||
**delete bed**; // 我不需要床了,我要把床拆掉。还没买怎么拆?
|
||||
}
|
||||
};
|
||||
|
||||
// House.cpp
|
||||
#include "Bed.h"
|
||||
#include "House.h" // 等房子开始装修了,要买床了
|
||||
|
||||
CHouse::CHouse(void)
|
||||
{
|
||||
bed = new CBed(); // 把床放进房子
|
||||
}
|
||||
|
||||
CHouse::~CHouse(void)
|
||||
{
|
||||
int i = 1;
|
||||
}
|
||||
|
||||
void CHouse::GoToBed()
|
||||
{
|
||||
bed->Sleep();
|
||||
}
|
||||
|
||||
// Bed.h
|
||||
class CBed
|
||||
{
|
||||
int* num;
|
||||
public:
|
||||
CBed(void);
|
||||
~CBed(void);
|
||||
void Sleep();
|
||||
};
|
||||
|
||||
// Bed.cpp
|
||||
#include "Bed.h"
|
||||
|
||||
CBed::CBed(void)
|
||||
{
|
||||
num = new int(1);
|
||||
}
|
||||
|
||||
CBed::~CBed(void)
|
||||
{
|
||||
delete num; // 调用不到
|
||||
}
|
||||
|
||||
void CBed::Sleep()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//main.cpp
|
||||
#include "House.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
CHouse house;
|
||||
house.RemoveBed();
|
||||
}
|
||||
|
||||
===== 前置声明解决两个类的互相依赖 =====
|
||||
|
||||
接下来,给出开篇第一个问题的答案:
|
||||
|
||||
// A.h
|
||||
class B;
|
||||
class A
|
||||
{
|
||||
B* b;
|
||||
public:
|
||||
A(void);
|
||||
virtual ~A(void);
|
||||
};
|
||||
|
||||
//A.cpp
|
||||
__#include "B.h" __
|
||||
#include "A.h"
|
||||
A::A(void)
|
||||
{
|
||||
b = new B;
|
||||
}
|
||||
|
||||
|
||||
A::~A(void)
|
||||
{
|
||||
}
|
||||
|
||||
// B.h
|
||||
** class A; **
|
||||
class B
|
||||
{
|
||||
A* a;
|
||||
public:
|
||||
B(void);
|
||||
~B(void);
|
||||
};
|
||||
|
||||
// B.cpp
|
||||
#include "A.h"
|
||||
#include "B.h"
|
||||
B::B(void)
|
||||
{
|
||||
a = New A;
|
||||
}
|
||||
|
||||
|
||||
B::~B(void)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
===== 前置声明在友元类方法中的应用 =====
|
||||
|
||||
《C++ Primer 4Edition》在类的友元一章节中说到,如果在一个类A的声明中将另一个类B的**成员函数**声明为友元函数F,那么__类A必须事先知道类B的定义__;类B的成员函数F声明如果使用类A作为形参,那么也必须知道类A的定义,那么两个类就互相依赖了。要解决这个问题必须使用类的前置声明。例如:
|
||||
|
||||
// House.h
|
||||
#include "Bed.h"
|
||||
class CHouse
|
||||
{
|
||||
friend void CBed::Sleep(CHouse&);
|
||||
public:
|
||||
CHouse(void);
|
||||
virtual ~CHouse(void);
|
||||
void GoToBed();
|
||||
void RemoveBed()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// House.cpp
|
||||
#include "House.h"
|
||||
|
||||
CHouse::CHouse(void)
|
||||
{
|
||||
}
|
||||
|
||||
CHouse::~CHouse(void)
|
||||
{
|
||||
int i = 1;
|
||||
}
|
||||
|
||||
void CHouse::GoToBed()
|
||||
{
|
||||
}
|
||||
|
||||
// Bed.h
|
||||
class CHouse;
|
||||
class CBed
|
||||
{
|
||||
int* num;
|
||||
public:
|
||||
CBed(void);
|
||||
~CBed(void);
|
||||
void Sleep(CHouse&);
|
||||
};
|
||||
|
||||
// Bed.cpp
|
||||
#include "House.h"
|
||||
CBed::CBed(void)
|
||||
{
|
||||
num = new int(1);
|
||||
}
|
||||
|
||||
CBed::~CBed(void)
|
||||
{
|
||||
delete num;
|
||||
}
|
||||
|
||||
void CBed::Sleep(CHouse& h)
|
||||
{
|
||||
|
||||
}
|
||||
89
Zim/Programme/C++/C++中变量的作用域与生命周期.txt
Normal file
@@ -0,0 +1,89 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-17T14:00:53+08:00
|
||||
|
||||
====== C++中变量的作用域与生命周期 ======
|
||||
Created Friday 17 February 2012
|
||||
|
||||
http://blog.csdn.net/yunyun1886358/article/details/5632087
|
||||
|
||||
今天在论坛上看到有朋友发帖问道:既然静态全局变量与全局变量都存储在全局数据区,为什么作用域却不一样呢?也许答案非常简单:C++就是这么规定的,静态全局变量与全局变量的__唯一区别就是作用域__不同。
|
||||
|
||||
对一个C++变量来说,有两个属性非常重要:__作用域和生命周期__,它们从两个不同的维度描述了一个变量--__时间和空间__。顾名思义,作用域就是__一个变量可以被引用的范围__,如:全局作用域、**文件作用域**、局部作用域;
|
||||
|
||||
而生命周期就是这个变量可以被引用的**时间段**。__不同生命周期的变量,在程序内存中的分布位置是不一样__的。一个程序的内存分为代码区、**全局数据区、堆区、栈区**,不同的内存区域,对应不同的生命周期。
|
||||
|
||||
有很多方法来指定一个变量的作用域和生命周期。最常见的,如:{ }、static修饰符等。下面按照作用域与生命周期来对变量做一个分类:
|
||||
|
||||
|
||||
===== 全局变量 =====
|
||||
|
||||
作用域:全局作用域(全局变量只需在一个源文件中__定义__,就可以作用于**所有的源文件**。)
|
||||
生命周期:程序运行期一直存在
|
||||
引用方法:**其他**文件中要使用必须用__extern 关键字声明__要引用的全局变量。
|
||||
内存分布:全局数据区
|
||||
注意:如果在两个文件中都定义了相同名字的全局变量,连接出错:变量重定义
|
||||
例子:
|
||||
//**defime.cpp**
|
||||
int g_iValue = 1;
|
||||
|
||||
//**main.cpp**
|
||||
__extern __int g_iValue; //声明而非定义
|
||||
|
||||
int main()
|
||||
{
|
||||
cout << g_iValue;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
===== 全局静态变量 =====
|
||||
|
||||
作用域:__文件作用域__(只在被定义的文件中可见。)
|
||||
生命周期:程序运行期一直存在
|
||||
内存分布:全局数据区
|
||||
定义方法:static关键字,__const 关键字__
|
||||
注意:只要文件不互相包含,在两个不同的文件中是可以定义完全相同的两个静态变量的,它们是两个完全不同的变量
|
||||
例子:
|
||||
|
||||
__const__ int iValue_1;
|
||||
static const int iValue_2;
|
||||
static int iValue_3;
|
||||
|
||||
int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
===== 静态局部变量 =====
|
||||
|
||||
作用域:__局部作用域__(只在局部作用域中可见)
|
||||
生命周期:程序运行期一直存在
|
||||
内存分布:全局数据区
|
||||
定义方法:局部作用域用中用**static定义**
|
||||
注意:**只被初始化一次**,__多线程中需加锁保护__
|
||||
例子:
|
||||
void function()
|
||||
{
|
||||
static int iREFCounter = 0;
|
||||
}
|
||||
|
||||
|
||||
===== 局部变量 =====
|
||||
|
||||
作用域:局部作用域(只在局部作用域中可见)
|
||||
生命周期:__程序运行出局部作用域即被销毁__
|
||||
内存分布:栈区
|
||||
注意:auto指示符标示
|
||||
|
||||
还有一点要说明,掌握static关键字的使用很关键。以下是引用别人的一些经验之谈:
|
||||
|
||||
===== Tips: =====
|
||||
|
||||
* 若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以__降低模块间的耦合度__;
|
||||
* 若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;
|
||||
* 设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑__重入问题__,因为他们都放在静态数据存储区,全局可见;
|
||||
* 如果我们需要一个可重入的函数,那么,我们一定要避免函数中使用static变量(这样的函数被称为:带“内部存储器”功能的的函数)
|
||||
* 函数中必须要使用static变量情况:比如当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。
|
||||
182
Zim/Programme/C++/C++中的全局namespace.txt
Normal file
@@ -0,0 +1,182 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-17T13:49:24+08:00
|
||||
|
||||
====== C++中的全局namespace ======
|
||||
Created Friday 17 February 2012
|
||||
|
||||
http://www.360doc.com/content/10/1027/10/4107890_64374919.shtml
|
||||
|
||||
我们应该知道__传统的C++只有一个全局的namespace__,但是由于现在的程序的规模越来越大,程序的分工越来越细,全局作用域变得**越来越拥挤**,每个人都可能使用相同的名字来实现不同的库,于是程序员在合并程序的时候就会可能出现名字的冲突。namespace的引入解决了这个问题。namespace允许像类,对象,函数聚集在一个名字下。
|
||||
|
||||
本质上讲__namespace是对全局作用域的细分__。
|
||||
我想大家都见过这样的程序吧:
|
||||
hello_world.c
|
||||
#include
|
||||
using namespace std;
|
||||
int main()
|
||||
{
|
||||
printf("hello world !");
|
||||
return 0;
|
||||
}
|
||||
我想很多人对namespace的了解也就这么多了但是namespace远不止如此,让我们再多了解一下namespace
|
||||
namespace的格式基本格式是
|
||||
namespace identifier
|
||||
{
|
||||
entities;
|
||||
}
|
||||
举个例子,
|
||||
namespace exp
|
||||
{
|
||||
int a,b;
|
||||
}
|
||||
有点类似于类,但完全是两种不同的类型。
|
||||
为了__在namespace外__使用namespace内的变量我们使用__::操作符__,如下
|
||||
exp::a
|
||||
exp::b
|
||||
__使用namespace可以有效的避免重定义的问题__
|
||||
|
||||
#include
|
||||
using namespace std;
|
||||
** namespace first**
|
||||
** {**
|
||||
** int var = 5;**
|
||||
** }**
|
||||
** namespace second**
|
||||
** {**
|
||||
** double var = 3.1416;**
|
||||
** }**
|
||||
int main () {
|
||||
cout << first::var << endl;
|
||||
cout << second::var << endl;
|
||||
return 0;
|
||||
}
|
||||
结果是
|
||||
5
|
||||
3.1416
|
||||
两个全局变量都是名字都是var,但是他们不在同一个namespace中所以没有冲突。
|
||||
|
||||
__关键字using可以帮助从namespace中引入名字到当前的声明区域__
|
||||
|
||||
#include
|
||||
using namespace std;
|
||||
namespace first
|
||||
{
|
||||
int x = 5;
|
||||
int y = 10;
|
||||
}
|
||||
namespace second
|
||||
{
|
||||
double x = 3.1416;
|
||||
double y = 2.7183;
|
||||
}
|
||||
|
||||
int main () {
|
||||
using __first::x__;
|
||||
using second::y;
|
||||
cout <<__ x __<< endl;
|
||||
cout << y << endl;
|
||||
cout << first::y << endl;
|
||||
cout << second::x << endl;
|
||||
return 0;
|
||||
}
|
||||
输出是
|
||||
5
|
||||
2.7183
|
||||
10
|
||||
3.1416
|
||||
就如我们所指定的第一个x是first::x,y是second.y
|
||||
|
||||
using也可以__导入整个的namespace__
|
||||
#include
|
||||
using namespace std;
|
||||
namespace first
|
||||
{
|
||||
int x = 5;
|
||||
int y = 10;
|
||||
}
|
||||
namespace second
|
||||
{
|
||||
double x = 3.1416;
|
||||
double y = 2.7183;
|
||||
}
|
||||
|
||||
int main () {
|
||||
using namespace __first;__
|
||||
cout << x << endl;
|
||||
cout << y << endl;
|
||||
cout << second::x << endl;
|
||||
cout << second::y << endl;
|
||||
return 0;
|
||||
}
|
||||
输出是
|
||||
5
|
||||
10
|
||||
3.1416
|
||||
2.7183
|
||||
|
||||
|
||||
正如我们所预见的导入的整个的first的namespace,前一对x,y的值就是first中的x,y的值。这里我们不能在“using namespace first:”下加一句“using namespace second:”,为什么呢?
|
||||
这样做无异于直接完全的忽视namespace first和namespace second,会出现__重复定义__的结果,所以前面的hello_world.c中的using指令的使用一定程度上存在问题的,只是因为我们就用了一个namspace,一旦引入了新的namespace这种做法很可能会出现重复定义的问题。
|
||||
|
||||
__在头文件中,我们通常坚持使用显式的限定__,并且仅__将using指令局限在很小的作用域中__,这样他们的效用就会受到限制并且易于使用。类似的例子有
|
||||
#include
|
||||
using namespace std;
|
||||
namespace first
|
||||
{
|
||||
int x = 5;
|
||||
}
|
||||
namespace second
|
||||
{
|
||||
double x = 3.1416;
|
||||
}
|
||||
int main () {
|
||||
{
|
||||
using namespace first;
|
||||
cout << x << endl;
|
||||
}
|
||||
{
|
||||
using namespace second;
|
||||
cout << x << endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
输出是
|
||||
5
|
||||
3.1416
|
||||
可以看到两个不同的namespace都被限制在了不同作用域中了,他们之间就没有冲突。
|
||||
|
||||
namespace也支持嵌套
|
||||
|
||||
#include
|
||||
namespace first
|
||||
{
|
||||
int a=10;
|
||||
int b=20;
|
||||
namespace second
|
||||
{
|
||||
double a=1.02;
|
||||
double b=5.002;
|
||||
void hello();
|
||||
}
|
||||
void second::hello()
|
||||
{
|
||||
std::cout <<"hello world"<
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
using namespace **first**;
|
||||
std::cout<<
|
||||
__second::__hello();
|
||||
}
|
||||
输出是1.02 hello world在namespace first中嵌套了namespace second,__seond并不能直接使用,需要first来间接的使用__。
|
||||
|
||||
**namespace可以使用别名**,在对一些名字比较长的namespace使用别名的话,是一件很惬意的事。但是与using相同,最好避免在头文件使用namespace的别名(f比first更容易产生冲突)。
|
||||
namespace f = first;
|
||||
|
||||
最后,namespace提供了单独的作用域,它类似于静态全局声明的使用,可以使用__未命名的namespace__定义来实现:namespace { int count = 0;} //这里的count是唯一的
|
||||
//在程序的其它部分中count是有效的
|
||||
void chg_cnt (int i) { count = i;}
|
||||
|
||||
108
Zim/Programme/C++/C++中的继承和多态.txt
Normal file
@@ -0,0 +1,108 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T10:11:49+08:00
|
||||
|
||||
====== C++中的继承和多态 ======
|
||||
Created Saturday 06 August 2011
|
||||
|
||||
派生类的构造函数:
|
||||
1.在派生类构造函数中,只要基类不是仅使用无参的默认构造函数,都要__显式的给出基类名称参数表__;
|
||||
2.基类没有定义构造函数,派生类也可以不定义,使用默认构造函数;
|
||||
3.基类有带参构造函数,派生类__必须__定义构造函数。
|
||||
|
||||
派生类与基类:
|
||||
1.派生类对象可以赋值给基类的对象,其实是把派生类对象中从对应基类中__继承__来的成员赋值给基类对象。反过来不行,因为派生类的新成员无值可赋;
|
||||
2.可以讲一个派生类对象的地址赋给其基类的指针变量,但是只能通过这个指针访问派生类中由基类继承来的成员,不能访问新成员;
|
||||
__注意__:如果派生类重载了基类中的操作,则将派生类对像赋值给基类对像后基类对像不能使用派生类重载后的操作而实际使用的还是基类的操作,除非该操作是一个虚函数。
|
||||
|
||||
多态性:
|
||||
__编译时多态__,通过函数的重载和运算符的重载来实现的;
|
||||
有别于编译时多态(重载)——__运行时的多态__:
|
||||
1.运行多态性:通过继承关系和虚函数(必须带virtual)来实现。即派生类对虚函数覆盖(参数表返回类型完全相同),无法在执行之前确定该调用哪一个函数;
|
||||
2.未加关键字virtual函数,在派生类被覆盖是同名覆盖(这是编译时多态的情况),不能体现运行时的多态性;
|
||||
|
||||
1 #include<iostream>
|
||||
|
||||
2 #include<string>
|
||||
3 using namespace std;
|
||||
4
|
||||
5 class Student
|
||||
6 {
|
||||
7 protected:
|
||||
8 string name;
|
||||
9 string curseName;
|
||||
10 int hours;
|
||||
11 int credit;
|
||||
12
|
||||
13 public:
|
||||
14 Student (string name) {this->name = name ; curseName = "#" ; credit = 0 ; hours = 0; }
|
||||
15
|
||||
16 /*
|
||||
17 有别于编译时多态——重载:
|
||||
18 1.运行多态性:通过继承关系和虚函数来实现。即派生类对虚函数覆盖(参数表返回类型完全相同),无法在执行之前确定该调用哪一个函数;
|
||||
19 2.未加关键字virtual函数,在派生类被覆盖是同名覆盖,不能体现运行时的多态性;
|
||||
20 */
|
||||
21 __virtual __void calcu() { credit = hours/16; }
|
||||
22 void setCourse( string str , int cHours)
|
||||
23 {
|
||||
24 curseName = str;
|
||||
25 hours = cHours;
|
||||
26 }
|
||||
27
|
||||
28 string getName() { return name; }
|
||||
29 string getCurseName() { return curseName; }
|
||||
30 int getHours() { return hours; }
|
||||
31 int getCredit() { return credit; }
|
||||
32 };
|
||||
33
|
||||
34 class GStudent : public Student
|
||||
35 {
|
||||
36 public:
|
||||
37 /*
|
||||
38 1.在派生类构造函数中,只要基类不是仅使用无参的默认构造函数,都要显示的给出基类名称参数表;
|
||||
39 2.基类没有定义构造函数,派生类也可以不定义,使用默认构造函数;
|
||||
40 3.基类有带参构造函数,派生类必须定义构造函数。
|
||||
41 */
|
||||
42 GStudent( string name ) : __Student(name)__ {} //通过“初始式”来达到初始化数据成员的目的。
|
||||
43 void calcu() { credit = hours/20;}
|
||||
44 };
|
||||
45
|
||||
46 int main()
|
||||
47 {
|
||||
48 Student s("本科生");
|
||||
49 GStudent g("研究生");
|
||||
50
|
||||
51 s.setCourse("本科课程",160);
|
||||
52 s.calcu();
|
||||
53 cout<<s.getName()<<"_"<<s.getCurseName()<<"_学时"<<s.getHours()<<"_学分"<<s.getCredit()<<endl;
|
||||
54
|
||||
55 g.setCourse("研究生课程",160);
|
||||
56 //这里的学分由GStudent定义的calcu()计算,它屏蔽了基类的同名函数。与不定义为虚函数(同名覆盖)效果一样
|
||||
57 g.calcu();
|
||||
58 cout<<g.getName()<<"_"<<g.getCurseName()<<"_学时"<<g.getHours()<<"_学分"<<g.getCredit()<<endl;
|
||||
59
|
||||
60 Student *ps;
|
||||
61
|
||||
62 //将 Student对象赋给Student对象指针,没什么好说的,和Student对象使用完全一样
|
||||
63 s.setCourse("本科",160);
|
||||
64 ps = &s;
|
||||
65 ps->calcu();
|
||||
66 cout<<ps->getName()<<"_"<<ps->getCurseName()<<"_学时"<<ps->getHours()<<"_学分"<<ps->getCredit()<<endl;
|
||||
67
|
||||
68 /*
|
||||
69 将 GStudent对象赋给Student对象指针,__基类对象用公有派生类对象代替__(赋值兼容规则)
|
||||
70 1.派生类对象可以赋值给基类的对象,其实是把派生类对象中从对应基类中__继承(而非重载)__来的成员赋值给基类对象。反过来不行;
|
||||
71 2.可以讲一个派生类对象的地址赋给其基类的指针变量,但是只能通过这个指针访问派生类中由基类继承来的成员,不能访问新成员;
|
||||
72 3.情况2 可以__访问派生类中覆盖的虚函数__——运行时多态;
|
||||
73 4.但函数没有virtual时,按照情况2不能访问新成员,__访问的其实是基类中的成员__,去掉Student中virtual,看运行结果.
|
||||
74 ==============================================================='
|
||||
75 指针类型是指向基类的指针,但是指向了派生类对象g,按照复制兼容规则是准许的,不过只能使用基类的成员,实际上用了派生类中新定义的
|
||||
76 calcu(),这就是虚函数体现的运行时多态性。
|
||||
77 */
|
||||
78 g.setCourse("研究生",160);
|
||||
79 ps = &g;
|
||||
80 ps->calcu();
|
||||
81 cout<<ps->getName()<<"_"<<ps->getCurseName()<<"_学时"<<ps->getHours()<<"_学分"<<ps->getCredit()<<endl;
|
||||
82 }
|
||||
|
||||
|
||||
140
Zim/Programme/C++/C++之“友元类”学习笔记.txt
Normal file
@@ -0,0 +1,140 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T10:41:03+08:00
|
||||
|
||||
====== C++之“友元类”学习笔记 ======
|
||||
Created Saturday 06 August 2011
|
||||
|
||||
=======================什么是友元类=======================
|
||||
|
||||
当一个类B成为了另外一个类A的“朋友”时,那么类A的私有和保护的数据成员就可以被类B访问。我们就把类B叫做类A的友元。
|
||||
|
||||
=======================友元类能做什么=======================
|
||||
|
||||
友元类可以通过自己的方法来访问把它当做朋友的那个类的所有成员。但是我们应该注意的是,我们把类B设置成了类A的友元类,但是这并不会是类A成为类B的友元。说白了就是:__甲愿意把甲的秘密告诉乙,但是乙不见得愿意把乙自己的秘密告诉甲。__
|
||||
|
||||
=======================友元类的声明方法和其用法=======================
|
||||
|
||||
声明友元类的方法其实很简单,只要我们在类A的成员列表中写下如下语句:
|
||||
friend class B;
|
||||
|
||||
这样一来,类B就被声明成了类A的友元。注意,类B虽然是类A的友元,但是__两者之间不存在继承关系__。这也就是说,友元类和原来那个类之间并没有什么继承关系,也__不存在包含或者是被包含的关系__,友元类和我上一篇博文《谈谈:C++类的“包含”机制》中的包含是完全不一样的!
|
||||
|
||||
=======================友元类的一个具体例子=======================
|
||||
|
||||
在这里,我们引用一个我从网上收集到的例子来说明友元类的作用:假设我们要设计一个模拟电视机和遥控器的程序。大家都之道,遥控机类和电视机类是不相包含的,而且,**遥控器可以操作电视机,但是电视机无法操作遥控器,这就比较符合友元的特性了**。即我们把遥控器类说明成电视机类的友元。下面是这个例子的具体代码:
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
class TV
|
||||
{
|
||||
public:
|
||||
friend class Tele;
|
||||
TV():on_off(off),volume(20),channel(3),mode(tv){}
|
||||
private:
|
||||
enum{on,off};
|
||||
enum{tv,av};
|
||||
enum{minve,maxve=100};
|
||||
enum{mincl,maxcl=60};
|
||||
bool on_off;
|
||||
int volume;
|
||||
int channel;
|
||||
int mode;
|
||||
};
|
||||
class Tele
|
||||
{
|
||||
public:
|
||||
void OnOFF(TV&t){t.on_off=(t.on_off==t.on)?t.off:t.on;}
|
||||
void SetMode(TV&t){t.mode=(t.mode==t.tv)?t.av:t.tv;}
|
||||
bool VolumeUp(TV&t);
|
||||
bool VolumeDown(TV&t);
|
||||
bool ChannelUp(TV&t);
|
||||
bool ChannelDown(TV&t);
|
||||
void show(TV&t)const;
|
||||
};
|
||||
bool Tele::VolumeUp(TV&t)
|
||||
{
|
||||
if (t.volume<t.maxve)
|
||||
{
|
||||
t.volume++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool Tele::VolumeDown(TV&t)
|
||||
{
|
||||
if (t.volume>t.minve)
|
||||
{
|
||||
t.volume--;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool Tele::ChannelUp(TV&t)
|
||||
{
|
||||
if (t.channel<t.maxcl)
|
||||
{
|
||||
t.channel++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool Tele::ChannelDown(TV&t)
|
||||
{
|
||||
if (t.channel>t.mincl)
|
||||
{
|
||||
t.channel--;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void Tele::show(TV&t)const
|
||||
{
|
||||
if (t.on_off==t.on)
|
||||
{
|
||||
cout<<"电视现在"<<(t.on_off==t.on?"开启":"关闭")<<endl;
|
||||
cout<<"音量大小为:"<<t.volume<<endl;
|
||||
cout<<"信号接收模式为:"<<(t.mode==t.av?"AV":"TV")<<endl;
|
||||
cout<<"频道为:"<<t.channel<<endl;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
cout<<"电视现在"<<(t.on_off==t.on?"开启":"关闭")<<endl;
|
||||
}
|
||||
|
||||
}
|
||||
int main()
|
||||
{
|
||||
Tele t1;
|
||||
TV t2;
|
||||
t1.show(t2);
|
||||
t1.OnOFF(t2);
|
||||
t1.show(t2);
|
||||
cout<<"调大声音"<<endl;
|
||||
t1.VolumeUp(t2);
|
||||
cout<<"频道+1"<<endl;
|
||||
t1.ChannelUp(t2);
|
||||
cout<<"转换模式"<<endl;
|
||||
t1.SetMode(t2);
|
||||
t1.show(t2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
我们在程序的第6行定义了一个TV电视机类的友元类Tele。那么程序中就可以来调用TV类中的私有成员。下面,是该程序的输出:
|
||||
{{./2011080222454094.png}}
|
||||
好了,这就是友元类了。关于友元类,我反正是这样认为的,因为友元类有可能会破坏数据的安全性,我们还是少用为好啊!在这里我只是记录一下它的用法罢了,呵呵
|
||||
|
||||
注意:对像之间有包含关系时可以在类定义时将包含的对像的类的一个对像作为该类的一个成员属性,也可以像本例一样,将另外一个类作为本类的友元。然后通过将另外类的对像传递给本类的操作。
|
||||
|
||||
BIN
Zim/Programme/C++/C++之“友元类”学习笔记/2011080222454094.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
123
Zim/Programme/C++/C++对象模型.txt
Normal file
@@ -0,0 +1,123 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-10-28T17:16:25+08:00
|
||||
|
||||
====== C++对象模型 ======
|
||||
Created Friday 28 October 2011
|
||||
|
||||
http://www.sf.org.cn/Article/base/200509/349.html
|
||||
|
||||
|
||||
介绍
|
||||
当编译一个C++程序时,计算机的内存被分成了4个区域,一个包括程序的代码,一个包括所有的全局变量,一个是堆栈,还有一个是堆(heap),我们称堆是自由的内存区域,我们可以通过new和delete把对象放在这个区域。你可以在任何地方分配和释放自由存储区。但是要注意因为分配在堆中的对象没有作用域的限制,因此一旦new了它,必须delete它,否则程序将崩溃,这便是内存泄漏。(C#已经通过内存托管解决了这一令人头疼的问题)。C++通过new来分配内存,new的参数是一个表达式,该表达式返回需要分配的内存字节数,这是我以前掌握的关于new的知识。
|
||||
|
||||
正文
|
||||
|
||||
这一章主要是说Runtime Semantics执行期语义学。
|
||||
|
||||
这是我们平时写的程序片段:
|
||||
Matrix identity; //一个全局对象
|
||||
Main()
|
||||
{
|
||||
Matrix m1=identity;
|
||||
……
|
||||
return 0;
|
||||
}
|
||||
很常见的一个代码片段,雷神从来没有考虑过identity如何被构造,或者如何被销毁。因为它肯定在Matrix m1=identity之前就被构造出来了,并且在main函数结束前被销毁了。我们不用考虑这些问题,好象C++就应该这样。但这本书是研究C++底层机制的。既然我们在看这本书,说明我们希望了解C++的编译器又做了那些大量的工作,使得我们可以这样使用对象。
|
||||
|
||||
在C++程序中所有的全局对象都被放在data segment中,如果明确赋值,则对象以该值为初值,否则所配置到内存内容为0。也就是说,如果我们有以下定义
|
||||
Int v1=1024;
|
||||
Int v2;
|
||||
则v1和v2都被配置于data segment,v1值为1024,v2值为0。(雷神在VC6环境用MFC编程时中发现如果int v2;v2的值不为0,而是-8,不知为什么?编译器造成的?)。
|
||||
如果有一个全局对象,并且这个对象有构造函数和析构函数的话,它需要静态的初始化操作和内存释放工作,C++是一种跨平台的编程语言,因此它的编译器需要一种可以移植的静态初始化和内存释放的方法。下面便是它的策略。
|
||||
1、 为每一个需要静态初始化的档案产生一个_sit()函数,内带构造函数或内联的扩展。
|
||||
2、 为每一个需要静态的内存释放操作的文件中,产生一个_std()函数,内带析构函数或内联的扩展。
|
||||
3、 提供一个_main()函数,用来调用所有的_sti()函数,还有一个exit()函数调用所有的_std()函数。
|
||||
侯先生说:
|
||||
Sit可以理解成static initialization的缩写。
|
||||
Std可以理解成static deallocation的缩写。
|
||||
那么main函数会被编译器变成这样:
|
||||
Matrix identity; //一个全局对象
|
||||
Main()
|
||||
{
|
||||
_main();//对所有的全局对象做static initialization动作。
|
||||
Matrix m1=identity;
|
||||
……
|
||||
exit();//对所有的全局对象做static deallocation动作。
|
||||
}
|
||||
其中_main()会有一个对identity对象的静态初始化的_sti函数,象下面伪码这样:
|
||||
// matrix_c是文件名编码_identity表示静态对象,这样能够保证向执行文件提供唯一的识别符号
|
||||
_sti__matrix_c_identity()
|
||||
{
|
||||
identity.Matrix:: Matrix(); //这就是静态初始化
|
||||
}
|
||||
相应的在exit()函数也会有一个_std_matrix_c_identity(),来进行static deallocation动作。
|
||||
但是被静态初始化的对象有一些缺点,在使用异常时,对象不能被放置在try区段内。还有对象的相依顺序引出的复杂度,因此不建议使用需要静态初始化的全局对象。
|
||||
|
||||
局部静态对象在C++底层机制是如何构造和在内存中销毁的呢?
|
||||
1、 导入一个临时对象用来保护局部静态对象的初始化操作。
|
||||
2、 第一次处理时,临时对象为false,于是构造函数被调用,然后临时对象被改为true.
|
||||
3、 临时对象的true或者false便成为了判断对象是否被构造的标准。
|
||||
4、 根据判断的结果决定对象的析构函数是否执行。
|
||||
|
||||
如果一个类定义了构造函数或者析构函数,则当你定义了一个对象数组时,编译器会通过运行库将你的定义进行加工,例如:
|
||||
point knots[10]; //我们的定义
|
||||
vec_new(&knots,sizeof(point),10,&point::point,0); //编译器调用vec_new()操作。
|
||||
|
||||
下面给出vec_new()原型,不同的编译器会有差别。
|
||||
void * vec_new(
|
||||
void *array, //数组的起始地址
|
||||
size_t elem_size, //每个对象的大小
|
||||
int elem_count, //数组元素个数
|
||||
void(*constructor)(void*),
|
||||
void(*destructor)(void* ,char)
|
||||
)
|
||||
对于明显获得初值的元素,vec_new()不再有必要,例如:
|
||||
point knots[10]={
|
||||
Point(), //knots[0]
|
||||
Point(1.0,1.0,0.5), //knots[1]
|
||||
-1.0 //knots[2]
|
||||
};
|
||||
会被编译器转换成:
|
||||
//C++伪码
|
||||
Point::Point(&knots[0]);
|
||||
Point::Point(&knots[1],1.0,1.0,0.5);
|
||||
Point::Point(&knots[2],-1.0,0.0,0.0);
|
||||
vec_new(&knots,sizeof(point),10,&point::point,0); //剩下的元素,编译器调用vec_new()操作。
|
||||
怎么样,很神奇吧。
|
||||
|
||||
当编译一个C++程序时,计算机的内存被分成了4个区域,一个包括程序的代码,一个包括所有的全局变量,一个是堆栈,还有一个是堆(heap),我们称堆是自由的内存区域,我们可以通过new和delete把对象放在这个区域。你可以在任何地方分配和释放自由存储区。但是要注意因为分配在堆中的对象没有作用域的限制,因此一旦new了它,必须delete它,否则程序将崩溃,这便是内存泄漏。(C#已经通过内存托管解决了这一令人头疼的问题)。C++通过new来分配内存,new的参数是一个表达式,该表达式返回需要分配的内存字节数,这是我以前掌握的关于new的知识,下面看看通过这本书,使我们能够更进一步的了解到些什么。
|
||||
Point3d *origin=new Point3d; //我们new 了一个Point3d对象
|
||||
编译器开始工作,上面的一行代码被转换成为下面的伪码:
|
||||
Point3d * origin;
|
||||
If(origin=_new(sizeof(Point3d)))
|
||||
{
|
||||
try{
|
||||
origin=Point3d::Point3d(origin);
|
||||
}
|
||||
catch(…){
|
||||
_delete(origin);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
而delete origin;
|
||||
会被转换成(雷神将书上的代码改为exception handling情况):
|
||||
if(origin!=0){
|
||||
try{
|
||||
Point3d::~Point3d(origin);
|
||||
_delete(origin);
|
||||
catch(…){
|
||||
_delete(origin); //不知对否?
|
||||
throw;
|
||||
}
|
||||
}
|
||||
一般来说对于new的操作都直截了当,但语言要求每一次对new的调用都必须传回一个唯一的指针,解决这个问题的办法是,传回一个指针指向一个默认为size=1的内存区块,实际上是以标准的C的malloc()来完成。同样delete也是由标准C的free()来完成。原来如此。
|
||||
|
||||
最后这篇笔记再说说临时对象的问题。
|
||||
T operator+(const T&,const T&); //如果我们有一个函数
|
||||
T a,b,c; //以及三个对象:
|
||||
c=a+b;
|
||||
//可能会导致临时对象产生。用来放置a+b的返回值。然后再由 T的copy constructor把临时对象当作c的初值。也有可能直接由拷贝构造将a+b的值放到c中,这时便不需要临时对象。另外还有一种可能通过操作符的重载定义,经named return value优化也可以获得c对象。这三种方法结果一样,区别在于初始化的成本。对临时对象书上有很好的总结:
|
||||
在某些环境下,有processor产生的临时对象是有必要的,也是比较方便的,这样的临时对象由编译器决定。
|
||||
临时对象的销毁应该是对完整表达式求值过程的最后一个步骤。
|
||||
因为临时对象是根据执行期语义有条件的产生,因此它的生命规则就显得很复杂。C++标准要求凡含有表达式执行结果的临时对象,应该保留到对象的初始化操作完成为止。当然这样也会有例外,当一个临时对象被一个引用绑定时,对象将残留,直到被初始化的引用的生命结束,或者超出临时对象的作用域。
|
||||
770
Zim/Programme/C++/C++文件IO.txt
Normal file
@@ -0,0 +1,770 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-17T16:14:48+08:00
|
||||
|
||||
====== C++文件IO ======
|
||||
Created Friday 17 February 2012
|
||||
|
||||
http://blog.cfan.com.cn/html/49/302849-1729970.html
|
||||
|
||||
虽然C++的I/O方法形成了一个完整的系统,但文件I/O(特别是磁盘文件I/O)由于
|
||||
受到本身的限制和特性,因而被当作一种特殊情况专门讲述。因为最普通的文件是磁盘文
|
||||
件,而磁盘文件具有其它设备不具有的性能和特征。但要记住,__磁盘文件I/O只是一般I/O__
|
||||
__系统的一个特例__,且本章讨论的大多数材料也适用于与其它类型的设备相连的流。
|
||||
|
||||
===== 18.1 fstream.h 和文件类 =====
|
||||
要处理文件I/O,程序中必须包含首标文件fstream.h。它定义了的类包括**ifstream、of-**
|
||||
**stream和fstream**。这些类分别从istream和ostream派生而来。记注,istream和ostream是从
|
||||
ios派生来的,所以ifstream、ofstream和fstream也存取ios定义的所有运算。
|
||||
|
||||
===== 18.2 打开和关闭文件 =====
|
||||
在C++里,用户__通过把文件和流联系起来打开文件__。打开文件之前,必须首先获得一个
|
||||
流。流分为三类:**输入、输出和输入/输出**。要创建一个输入流,必须说明它为类ifstream;
|
||||
要创建一个输出流,必须说明它为类ofstream。执行输入和输出操作的流必须说明为类
|
||||
fstream。例如,下面的程序段创建了一个输入流、一个输出流和一个输入/输出流。
|
||||
|
||||
ifstream in; // input
|
||||
ofstream out; // output
|
||||
__fstream__ io;// input and output
|
||||
|
||||
一旦创建了一个流,__把流和文件联系起来__的一种方法就是使用函数**open()**。该函数是
|
||||
这三个类中每个类的成员。其原型为:
|
||||
|
||||
void open(__const char *__ filename, int mode, int access=filebuf::openprot);
|
||||
|
||||
其中,filename为文件名,它可以包含路径说明符。mode值决定文件打开的方式,它必须是
|
||||
下列值中的一个(或多个):
|
||||
* ios::app
|
||||
* ios::ate
|
||||
* ios::binary
|
||||
* ios::in
|
||||
* ios::nocreate
|
||||
* ios::noreplace
|
||||
* ios::out
|
||||
* ios::trunc
|
||||
|
||||
用户可以把两个或两个以上的值__或在__一起得到它们的复合值。下面看看这些值的含义。
|
||||
* 包含ios::app 导致把所有文件的输出添加在**文件尾**。它只能用于输出文件。包含ios::
|
||||
ate导致文件打开时定位于文件尾。虽然如此,在文件的任何位置都可以进行I/O操作。
|
||||
* 缺省时,文件以__文本方式打开__。使用ios::binary值可以使文件以二进制方式打开。文件
|
||||
以文本方式打开时,会产生不同的字符转换。如**回车/换行转换为换行**。但当文件以二进制方
|
||||
式打开时,__不发生字符转换__。任何文件,不管是包含格式化的文本还是包含原始的二进制数
|
||||
据,均可以文件方式或二进制方式打开,唯一不同的是是否进行字符转换。
|
||||
* ios::in值说明文件有输入能力,ios::out值说明文件有输出能力。用ifstream创建的流
|
||||
隐含为输入,用ofstream创建的流**隐含为输出**。在这些情况下,没有必要提供这些值。
|
||||
* 包含ios::nocreate导致函数open()在文件不存在时失败。ios::noreplace值导致函数
|
||||
open()在文件存在时失败。
|
||||
* ios::trunc值导致已存在的同名文件的内容被破坏且长度被截断为0。
|
||||
注:建议的ANSI C++标准规定方式参数类型为openmode,通常为整型。现在大多数工
|
||||
具简单地将方式参数定义为整型数。
|
||||
|
||||
access值决定**存取文件的方式**,其缺省值为filebuf::openprot,指定为通常文件(filebuf
|
||||
是由streambuf派生的类)。大多数时候允许access缺省。参阅编译程序手册,找出自己所用
|
||||
操作环境中该参数的其它选项。例如,文件共享选项一般定义为在网络环境下使用的access
|
||||
参数。
|
||||
|
||||
下面的程序段打开一个普通输出文件:
|
||||
ofstream out;
|
||||
out.open(“test”, ios::out);
|
||||
由于一般情况下使用的是mode缺省值,所以很少象上面这样调用open()。对于ifstream,
|
||||
mode的缺省值是ios::in;对于ofstream它是ios::out。所以,上面的语句通常表现如下:
|
||||
out.open("test"); // defaults to output and normal file
|
||||
|
||||
如下例所示,要打开一个**供输入和输出的流**,就必须指定mode的值为ios::in和ios::
|
||||
out(无缺省值):
|
||||
fstream mystream;
|
||||
mystream.open("test",__ios::in | ios::out__);
|
||||
|
||||
如果open()失败,则**mystream为0**。所以在使用一个文件之前,应使用如下语句进行测
|
||||
试,以__确保打开操作成功__。
|
||||
if(!mystream){
|
||||
cout<<"Cannot open file.\N";
|
||||
// handle error
|
||||
}
|
||||
|
||||
虽然使用函数open()打开一个文件完全合适,但在大多数情况下,由于ifstream、of-
|
||||
stream和fstream类包含**自动打开文件的构造函数**,所以没有必要调用open()。构造函数有
|
||||
和open()相同的参数和缺省值。最常见的打开文件的方法是:
|
||||
|
||||
ifstream mystream("myfile");// open file for input
|
||||
如前所述,如果由于某种原因不能打开文件,则关于流的变量的值是0。所以,不管用构造函
|
||||
数还是显式地调用open()打开文件,都要测试流的值以保证真正打开了文件。
|
||||
|
||||
要关闭一个文件,使用成员函数close()。例如,用下面的语句关闭关于流mystream的
|
||||
文件:
|
||||
mystream. close();
|
||||
函数close()不带任何参数且无返回值。
|
||||
|
||||
==== 18.3读和写文本文件 ====
|
||||
|
||||
从文本文件读或向文本文件写都非常容易,只要使用在处理控制台I/O时使用的运算
|
||||
符__<<和>>__,并用和文件相关的流代替cin和cout即可。例如,下面的程序建立了一个帐
|
||||
单,它包括每个项目的名称和开支情况。
|
||||
|
||||
#include<iostream.h>
|
||||
#include<fstream.h>
|
||||
main()
|
||||
{
|
||||
ofstream out("INVNTRY");// output, normal file
|
||||
if(!out){
|
||||
cout<<"Cannot open INVENTORY file.\n";
|
||||
return 1;
|
||||
}
|
||||
out<<"Radios" <<**39.95**<<endl;
|
||||
out<<"Toasters"<<19.95 <<endl;
|
||||
out<<"Mixers" <<24.80<< endl;
|
||||
out.close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
下面的程序读上面的程序中创建的帐目文件并在屏幕上显示其内容:
|
||||
#include<iostream.h>
|
||||
#include<fstream.h>
|
||||
main()
|
||||
{
|
||||
ifstream in("INVNTRY");// input
|
||||
if(!in){
|
||||
cout<<"Cannot open INVENTORY file.\n";
|
||||
return 1;
|
||||
}
|
||||
__char __item[20];
|
||||
float cost;
|
||||
|
||||
in>>item>>cost;
|
||||
cout<<item<<" "<<cost<<"\n";
|
||||
in>>item>>cost;
|
||||
cout<<item<<" "<<cost<<"\n";
|
||||
in>> item>>cost;
|
||||
cout<<item<<" "<<cost<<"\n";
|
||||
in.close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
从某种意义上讲,使用>>和<<读写文件类似于使用C的函数fprintf()和fscanf()。
|
||||
所有的信息都按屏幕显示的格式存入文件。
|
||||
|
||||
下面是磁盘文件I/O的又一个例子。该程序读取**从键盘输入的字符串**并写入磁盘。程序
|
||||
在用户输入空行时结束。使用该程序时,要在命令行中指定输出文件名。
|
||||
#include<iostream.h>
|
||||
#include<fstream.h>
|
||||
#include<stdio.h>
|
||||
main(int argc,char * argv[])
|
||||
{
|
||||
if(argc!=2){
|
||||
cout <<"Usage: output<filename>\n";
|
||||
return 1;
|
||||
}
|
||||
ofstream out(argv[C]);// output, normal file
|
||||
if(!out){
|
||||
cout<<"Cannot open output file.\n";
|
||||
return 1;
|
||||
}
|
||||
char str[80];
|
||||
cout<<"Write strings to disk, RETURN to stop.\n";
|
||||
do{
|
||||
cout <<":";
|
||||
**gets(str);**
|
||||
out<<str<<endl;
|
||||
}while(*str);
|
||||
out.close();
|
||||
return 0;
|
||||
}
|
||||
用运算符>>读文本文件时,将发生__某些字符转换__,如省略空格等。如果要防止任何字
|
||||
符转换,就必须使用C++的二进制I/O函数,我们将在下一节讨论它。输入时如果遇到文件尾,
|
||||
和文件关联的流则为0。
|
||||
|
||||
|
||||
===== 18.4二进制I/O =====
|
||||
向一个文件写和从一个文件读有两种方法,下面就介绍这两种方法。
|
||||
记住:如果要对一个文件进行二进制操作,则必须使用ios::binary方式说明符打开文
|
||||
件。虽然二进制文件函数可以对以文本方式打开的文件进行操作,但可能发生一些字符转
|
||||
换,字符转换会违背二进制文件操作的目的。
|
||||
|
||||
==== 18.4.1 put()和get() ====
|
||||
读写二进制文件的一种方法使用成员函数get()和put()。这些函数以__字节为单位__进行
|
||||
操作。也就是说,get()可以读一个字节数据,put()可以写一字节数据。函数get()有很多形
|
||||
式,但最常用的形式和与之伴随的put()如下所示:
|
||||
istream&get(char &ch);
|
||||
ostream&put(char ch);
|
||||
函数get()从相关的流读一个字符并放入ch,返回对流的引用。函数put()将*写入流
|
||||
并返回流的引用。
|
||||
|
||||
下面的程序在屏幕上显示任何文件的内容,它用到了函数get()。
|
||||
#include <iostream.h>
|
||||
#include<fstream.h>
|
||||
main(int argc, char * argv[])
|
||||
{
|
||||
__char__ ch;
|
||||
if(argc!=2)
|
||||
cout<<"Usage:PR<filename>\n";
|
||||
return 1;
|
||||
}
|
||||
ifstream in(argv[1]);
|
||||
if(!in){
|
||||
cout <<"Cannot open file.\n";
|
||||
return 1;
|
||||
}
|
||||
while(in){//in will be 0 when __eof__ is reached
|
||||
in.get(ch);
|
||||
cout<<ch;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
前面提到过,当到达文件尾时,和文件相关的流变为0。所以,当in到达文件尾时它是0,从而终止while循环。
|
||||
下面给出的这种编码方法使读和显示文件的循环的编码更短:
|
||||
while(in.get(ch))
|
||||
cout << ch;
|
||||
由于get()返回流in的引用,且在遇到文件尾时in为0,所以上述语句能正常工作。
|
||||
|
||||
下面的程序用put()向文件CHARS写入从0到255的所有字符。ASCII字符只占char所
|
||||
容纳的值的一半,其余值一般称为扩展字符值,包括一些外来语和数学符号(并非所有的系
|
||||
统都支持扩展字符集)。
|
||||
#include<iostream.h>
|
||||
#include <fstream. h>
|
||||
main()
|
||||
{
|
||||
int i;
|
||||
ofstream out("CHARS",ios::out | ios::binary);
|
||||
if(!out){
|
||||
cout << "Cannot open output file.\n";
|
||||
return 1;
|
||||
}
|
||||
// write all characters to disk
|
||||
for(i=0; i<256; i++)out.put(char(i));
|
||||
out.close();
|
||||
return 0;
|
||||
}
|
||||
检查一下文件CHARS的内容,看一看自己的计算机有什么扩展字符,是一件很有趣的
|
||||
事情。
|
||||
|
||||
==== 18.4.2 read()和write() ====
|
||||
第二种方法是使用C++的函数read()和write()读写__二进制数据块__,其原型为:
|
||||
istream &read(unsigned char *buf, int num);
|
||||
ostream &write(const unsigned char *buf, int num);
|
||||
函数read() 从相关的流读num个字节并放入buf所指的缓冲区。函数write()把buf所
|
||||
指的缓冲区中的num个字节写入相关的流。
|
||||
注:建议的ANSI C++标准用__streamsize__ 说明num参数的类型,streamsize是整数类型
|
||||
的typedef。如前面的原型所示,大多数C++编译程序简单地将num定义为整型。一般情况
|
||||
下,建议的ANSI C++标准用streamsize作为对象的类型,这些对象说明在输入/输出操作
|
||||
中传送的字节数。
|
||||
|
||||
下面的程序先把一个结构写入磁盘,然后读回:
|
||||
|
||||
#include<iostream.h>
|
||||
#include<fstream.h>
|
||||
#include<string.h>
|
||||
struct status{
|
||||
char name[80];
|
||||
float balance;
|
||||
unsigned long account_num;
|
||||
};
|
||||
main()
|
||||
{
|
||||
struct status acc;
|
||||
__strcpy__(acc.name,"Ralph Trantor");
|
||||
acc.balance=1123.23;
|
||||
acc.account_num=34235678;
|
||||
ofstream outbal("balance",ios::out | ios:: binary);
|
||||
if(!outbal){
|
||||
cout <<"Cannot open file.\n";
|
||||
return 1;
|
||||
}
|
||||
outbal.write(__(unsigned char*)__&acc, sizeof(struct status));
|
||||
outbal.close();
|
||||
// now, readback
|
||||
ifstream inbal("balancen",ios:: | ios:: binary);
|
||||
if(!inbal){
|
||||
cout <<"Cannot open file.\n";
|
||||
return 1;
|
||||
}
|
||||
inbal.read(__(unsigned char*)__&acc, sizeof(struct status));
|
||||
cout <<acc.name <<endl;
|
||||
cout<<"Account # ", <<acc.account_num;
|
||||
cout.__precision__(2);
|
||||
cout. __setf__(ios::fixed);
|
||||
cout<<ena<<" Balance :$" <<acc.balance;
|
||||
inbal.close();
|
||||
return 0;
|
||||
}
|
||||
可以看出,读写整个结构只需调用一次read()和write(),而不必分别读写结构中的每
|
||||
个域。如本例所示,缓冲区可以是任何类型的对象。
|
||||
|
||||
注:当对不是定义为字符数组的缓冲区进行操作时,在调用read()和write()中进行类
|
||||
型强制转换是必要的。由于C++有很强的类型检查能力,所以一种类型的指针不能自动转
|
||||
换成另一种类型的指针。如果还未读到num个字符就到达了文件尾,read()便终止,缓冲区包含了能获得的那
|
||||
些字符。用成员函数gcount()可以得到读取的字符的个数,其原型为:
|
||||
__ int gcount()__;
|
||||
它返回**最后一次**二进制输入操作的字符个数。
|
||||
下面的程序是使用read()和write()的另一个例子,它说明了gcount()的用法。
|
||||
#include <iostream.h>
|
||||
#include<fstream.h>
|
||||
main(void)
|
||||
{
|
||||
float fnum[4] ={99.75,-34.4, 1776.0,200.1};
|
||||
int i;
|
||||
ofstream out("numbers",ios::out | ios:: binary);
|
||||
if(!out){
|
||||
cout << "Cannot open file.";
|
||||
return 1;
|
||||
}
|
||||
out.write((unsigned char * )&fnum, sizeof(fnum);
|
||||
Out.close();
|
||||
for(i=0; i<4;i++)// clear array
|
||||
fnum[i]=0.0;
|
||||
ifstream in("numbers",ios::in | ios::binary);
|
||||
in. read((unsigned char*)&fnum, sizeof fnum);
|
||||
// see how many bytes have been read
|
||||
**cout << in. gcount()**<< "bytes read\n";
|
||||
for(i=0; i<4; i++) // show values read from file
|
||||
cout << fnum[i] <<" ";
|
||||
in.close();
|
||||
return 0;
|
||||
}
|
||||
上面的程序向磁盘写一个浮点值数组然后读回。调用read()之后,用gcount()确定刚才
|
||||
读回多少个字节。
|
||||
|
||||
==== 18.5另外的get()成员函数 ====
|
||||
除了前面所示的形式,还可以用几种不同的方法重载get()。最常见的两种重载形式为:
|
||||
istream&get(char*buf, int num, char delim=’\n’);
|
||||
int get();
|
||||
|
||||
第一种重载形式读取字符并放到buf所指的数组中,直到读满num-1个字符或遇到delim所指定的字符为止。
|
||||
get()使buf所指的数组__以空结束__。如果没指定delim参数,缺省的定界符就是换行符。__在输入流中碰到定界字符时,并不读取它__,而是继续留在流中直到下次读入
|
||||
操作。 get()的第二种重载形式返回流中的下一个字符,到达文件尾时返回EOF。这种形式的get()类似于C的函数getc()。
|
||||
|
||||
==== 18.6 getline()成员函数 ====
|
||||
执行输入的另一个成员函数是getline(),其原型为:
|
||||
istream&getline(char * buf, int num, char delim=’\n’);
|
||||
可以看出,这个函数实质上和get()的get(buf、num、delim)形式是一样的。它从输入流中读取字符到buf所指的数组中,直到读满num个字符或遇到delim所指定的字符为止。如果没指定delim,其缺省值就是换行符。buf所指的数组__以空结束__。get(buf、num、delim)和getline()的区别在于getline()__从输入流中读入并移去定界符__。
|
||||
|
||||
下面的程序介绍了函数getline(),它逐行读并显示一个文本文件。
|
||||
// Read and display a text file line by line.
|
||||
#include <iostream. h>
|
||||
#include<fstream.h>
|
||||
main(int argc, char * argv[])
|
||||
{
|
||||
if(argc!=2){
|
||||
cout <<"Usage: Display<filename>\n";
|
||||
return 1;
|
||||
}
|
||||
ifstream in(argv[1]);// input
|
||||
if(!in){
|
||||
cout <<"Cannot open input file.\n";
|
||||
return 1;}
|
||||
char str[255];
|
||||
while(in){
|
||||
in. getline(str, 255);// delim defaults to’\n’
|
||||
cout << str<<endl;
|
||||
}
|
||||
in.close();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
===== 18.7跟踪EOF =====
|
||||
使用成员函数eof()可以跟踪何时到达文件尾,其原型为:
|
||||
int eof();
|
||||
当到达文件尾时,eof()的返回值__不为0__,否则为0。
|
||||
注:建议的ANSI C++标准规定eof()返回值类型为布尔型。但目前大多数C++编译程
|
||||
序还不支持布尔数据类型。实际上,将eof()返回值说明为布尔类型还是整型无关紧要,因为
|
||||
在任何表达式中的布尔型可以自动转换为整型。
|
||||
|
||||
下面的程序以十六进制的ASCII形式显示一个文件的内容。
|
||||
/* Display contents of specified file
|
||||
in both ASCII and in hex.
|
||||
*/
|
||||
#include<iostream.h>
|
||||
#include<fstream.h>
|
||||
#include<ctype.h>
|
||||
#include <__iomanip.h__>
|
||||
#include<stdio.h>
|
||||
main(int argc,char * argv[])
|
||||
{
|
||||
if(argc!=2){
|
||||
cout << "Usage: Display <filename>\n";
|
||||
return 1;
|
||||
}
|
||||
ifstream in(argv[1], ios::in | ios::binary);
|
||||
if(!in){
|
||||
cout << "Cannot open input file.\n";
|
||||
return 1;
|
||||
}
|
||||
register int i, j;
|
||||
int count=0;
|
||||
char c[16];
|
||||
cout. __setf__(ios: : uppercase);
|
||||
while(!in. eof()){
|
||||
for(i=0; i<16&&!in.eof(); i++){
|
||||
in. get(c[i]);
|
||||
}
|
||||
if(i<16) i--;// get rid of eof
|
||||
for(j=0;j<i;j++)
|
||||
cout << setw(3) << __hex__ <<(int)c[j];
|
||||
for(;j<16;j++) cout<<" " ;
|
||||
|
||||
cout<<"\t";
|
||||
for(j=0;j<i;j++)
|
||||
if(**isprint**(c[j])) cout<<c[j];
|
||||
else __cout <<"."__;
|
||||
cout <<endl;
|
||||
count++;
|
||||
if(count==16){
|
||||
count=0;
|
||||
cout<<"Press __ENTER__ to continue:";
|
||||
cin.get();
|
||||
cout<<endl;
|
||||
}
|
||||
}
|
||||
in.close();
|
||||
return 0;
|
||||
}
|
||||
如果用这个程序显示它自己的话,则第一屏内容为:
|
||||
2F 2A 20 44 69 73 70 6C 61 79 20 63 6F 6E 74 65 / * Display conte
|
||||
6E 74 73 20 6F 66 20 73 70 65 63 69 66 69 65 64 nts of specified
|
||||
20 66 69 6C 65 D A 20 20 20 69 6E 20 62 6F 74 file. . in bot
|
||||
68 20 41 53 43 49 49 20 616E 64 20 69 6E 20 68 h ASCII and in h
|
||||
65 78 2E D A 2A 2F D A 23 69 6E 63 6C 75 64 ex. . */ . . # includ
|
||||
65 20 3C 69 6F 73 74 72 65 61 6D 2E 68 3E D A e <iostream. h>. .
|
||||
23 69 6E 63 6C 75 64 65 20 3C 66 73 74 72 65 61 # Include<fstrea
|
||||
6D 2E 68 3E D A 23 69 6E 63 6C 75 64 65 20 3C m. h>. . # include<
|
||||
63 74 79 70 65 2E 68 3E D A 23 69 6E 63 6C 75 ctype. h>. . # inclu
|
||||
64 65 20 3C 69 6F 6D 61 6E 69 70 2E 68 3E D A de <iomanip. h>. .
|
||||
23 69 6E 63 6C 75 64 65 20 3C 73 74 64 69 6F 2E # include<stdio.
|
||||
68 3E D A D A 6D 6l 69 6E 28 69 6E 74 20 61 h>. . . . main(int a
|
||||
72 67 63 2C 206368 61 72 20 2A 61 72 67 76 5B rsc, char * argv[
|
||||
5D 29 D A 780A20 20 69 66 28 61 72 67 63]).{.. if(argc
|
||||
21 3D 32 29 20 78DA20 20 20 20 63 6F 75 74 1=2){. . cout
|
||||
20 3C 3C 20 22 55 73 6167 65 3A 2044 69 73 70 <<"Usage: Disp
|
||||
Press ENTER to continue:
|
||||
|
||||
===== 18.8ignore()函数 =====
|
||||
使用成员函数ignore()可以从输入流中读取并丢弃字符,其原型为:
|
||||
istream &ignore(int num=1, int delim=EOF);
|
||||
它读取并丢弃字符直到忽略num个(缺省为一个)字符或遇到由delim指定的字符(缺省为EOF)为止。如果遇到了定界字符,__不从输入流中移去它__。
|
||||
下面的程序读文件TEST。它忽略字符直到遇到空格或读完10个字符,然后显示文件的 剩余部分。
|
||||
#include <iostream. h>
|
||||
#include<fstream.h>
|
||||
main()
|
||||
{
|
||||
ifstream in("test");
|
||||
if(!in){
|
||||
cout << "Cannot open file.\n";
|
||||
return 1;
|
||||
}
|
||||
/* Ignore up to 10 characters or until first
|
||||
space is found. */
|
||||
in.ignore(10,’’);
|
||||
char c;
|
||||
while(in){
|
||||
in.get(c);
|
||||
cout<<c;
|
||||
}
|
||||
in.close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
===== 18.9 peek()和putback() =====
|
||||
使用peek()可以从输入流中获取下一个字符,但不移去它,其原型为:
|
||||
int peek();
|
||||
它返回流中的下一个字符或者到达文件尾时返回EOF。
|
||||
使用putback()可以获得从一个流中读取的最后一个字符,其原型为:
|
||||
istream&putback(char c);
|
||||
其中,c为读取的最后一个字符。
|
||||
|
||||
===== 18.10 flush() =====
|
||||
在执行输出时,并不立即将数据写入和流相关的物理设备,而是把它们存储在内部缓冲
|
||||
区里,直到缓冲区满为止时才将内容写入磁盘。如果使用flush()就可以在缓冲区装满之前
|
||||
强行地将其内容写入磁盘,其原型为:
|
||||
ostream&flush();
|
||||
当在恶劣的环境下使用程序时(如在经常掉电的地方),调用flush()是很必要的。
|
||||
注:关闭文件或程序正常终止均清除所有的缓冲区。
|
||||
|
||||
===== 18.11随机存取 =====
|
||||
在C++的I/O系统中,用户使用函数seekg()和seekp()执行随机存取。它们的一般形
|
||||
式为:
|
||||
istream &seekg(streamoff offset, seek_dir origin);
|
||||
ostream &seekp(streamoff offset, seek_dir origin);
|
||||
其中,streamoff是在iostream.h中定义的一种类型,它能包含offset所能容纳的最大有效
|
||||
值。同时,seek_dir是具有下列值的一个枚举:
|
||||
ios: :beg
|
||||
ios::cur
|
||||
ios::end
|
||||
C++的I/O系统管理和文件相关的两类指针。一类是__取指针__(get pointer),它指出在文
|
||||
件中进行下次**输入**操作的位置;另一类是__送指针__(put pointer),它指出在文件中进行下次**输**
|
||||
**出**操作的位置。每进行一次输入和输出操作,相应的指针就自动顺序前进。使用函数seekg()
|
||||
和seekp()可以按非顺序方式存取文件。
|
||||
|
||||
函数seekg()把文件的当前取指针移动到距指定的origin offset个字节的位置。origin必
|
||||
须是下列值之一:
|
||||
ios::beg 文件首
|
||||
ios::cur 当前位置
|
||||
ios::end 文件尾
|
||||
函数seekp()把相关文件的当前送指针定位到距指定的origin offset个字节的位置。ori-
|
||||
gin也必须是上述值之一。
|
||||
|
||||
下面的程序介绍了函数seekp()。它可以__改变一个文件中的特定字符__。命令行参数为文
|
||||
件名、要改变的文件中的第几个字节和新的字符值。注意,文件打开为读/写操作。
|
||||
|
||||
#include<iostream.h>
|
||||
#include<fstream.h>
|
||||
#include<stdio.h>
|
||||
main(int argc, char * argv[])
|
||||
{
|
||||
if(argc!=4){
|
||||
cout<<"Usage:CHANGE<mename><byte> <char>\n"
|
||||
return 1;
|
||||
}
|
||||
fstream out(argv[1], **ios::in | ios::out** | ios: :binary);
|
||||
if(!out){
|
||||
cout << "Cannot open file.\n";
|
||||
return 1;
|
||||
|
||||
out.seekp(__atoi__(argv[2]), ios::beg);
|
||||
out. put(* argv[3]);
|
||||
out.close();
|
||||
return 0;}
|
||||
列如,用这个程序把文件TEST中的第12个字节换成Z,行命令为:
|
||||
change test 12 Z
|
||||
|
||||
下面的程序使用了seekg(),它显示指定文件从指定位置开始的内容。
|
||||
#include<iostream.h>
|
||||
#include<fstream.h>
|
||||
#include<stdlib.h>
|
||||
main(int argc,char * argv[]){
|
||||
Char ch;
|
||||
if(argc!=3){
|
||||
cout <<"Usage: SHOW <filename><starting location>\n";
|
||||
return 1;
|
||||
}
|
||||
ifstream in(argv[1], ios::in| ios::binary);
|
||||
if(!in) {
|
||||
cout <<"Cannot open file.\n";
|
||||
return 1;
|
||||
}
|
||||
in.seekg(atoi(argv[2], ios::beg);
|
||||
while(in, get(ch))
|
||||
cout << ch;
|
||||
return 0;
|
||||
}
|
||||
|
||||
下面的程序用seekp()和seekg()反转一个文件中的前(num>个字符:
|
||||
#include<iostream.h>
|
||||
#include<fstream.h>
|
||||
#include<stdlib.h>
|
||||
main(int argc, char*argv[])
|
||||
{
|
||||
if(argc!=3) {
|
||||
cout << "Usage: Reverse<filename> <num>\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
ifstream inout (argv[1],ios::in|ios::out | ios::binary);
|
||||
if(!inout){
|
||||
cout<<"Cannot open input file.\n";
|
||||
return 1;
|
||||
}
|
||||
long e,i,j;
|
||||
char c1,c2;
|
||||
e=atol(arg[2]);
|
||||
for(i=0,j=0;i<1; i++,j--){
|
||||
inout.seekg(i, ios::beg);
|
||||
inout.get(c1);
|
||||
inout.seekg(j, ios::beg);
|
||||
inout.get(c2);
|
||||
inout.seekp(i, ios::beg);
|
||||
inout.put(c2);
|
||||
inout.seekp(j , ios::beg);
|
||||
inout.put(c1);
|
||||
}
|
||||
inout.close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
使用这个程序的时侯,必须指定要反转的文件名和字符个数。例如,要反转文件TEST
|
||||
的头10个字符,行命令为:
|
||||
reverse test 10
|
||||
假设文件包含如下内容:
|
||||
This is a test.
|
||||
执行程序后,文件的内容变为:
|
||||
a si sihTtest.
|
||||
|
||||
===== 18.11.1得到当前文件的位置 =====
|
||||
使用下列函数可以确定每个文件指针的当前位置:
|
||||
streampos tellg();
|
||||
streampos tellp();
|
||||
其中,__streampos__是在iostream.h中定义的一种类型,它能容纳每个函数返回的最大值。
|
||||
可以用tellg()和tellp()返回值作为下面形式的seekg()和seekp()的变元:
|
||||
istream &seekg(streampos pos);
|
||||
ostream&seekp(streampos pos);
|
||||
这些函数提供用户保存当前文件的位置,转而执行其它文件操作,然后可__将文件重设到__
|
||||
__先前保存的位置__。
|
||||
|
||||
===== 18.12 I/O状态 =====
|
||||
C++I/O系统维护每次I/O操作结果的状态信息。I/O系统的当前状态放在一个整数
|
||||
中,其中有下列标志及编码:
|
||||
名称 含义
|
||||
eofbit 1:到达文件尾 0:否则
|
||||
failbit 1:非致命I/O错 0:否则
|
||||
badbit 1:致命I/O错 0:否则
|
||||
这些标志一一列举在ios中。定义在ios中的goodbit值为0。
|
||||
|
||||
获取I/O状态信息有两种方法。第一种是调用成员函数rdstate(),其原型为:
|
||||
int rdstate();
|
||||
它返回一个反映错误标志状态的整数。根据前面的标志表,可以料到在不出现错误时rdstate
|
||||
()返回值是0,否则就有标志位被置位。
|
||||
注:建议的ANSI C++标准将rdstate()的返回值类型说明为iostate,即一个整型值的
|
||||
typedef。当前,大多数C++编译程序将rdstate()的返回值类型说明为整型。
|
||||
|
||||
下面的程序介绍了rdstate(),它显示一个文本文件的内容。如果出现错误,则用check-
|
||||
status()报警。
|
||||
#include<iostream.h>
|
||||
#include<fstream.h>
|
||||
void checkstatus(ifstream&in);
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
if(argc!=2){
|
||||
cout << "Usage: Display <filename>\n";
|
||||
return 1;
|
||||
}
|
||||
ifstream in(argv[1]);
|
||||
if(!in){
|
||||
cout << "Cannot open input file.\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
char c;
|
||||
while(in.get(c)){
|
||||
cout<<c;
|
||||
__checkstatus(in)__;
|
||||
}
|
||||
CheCkstatus(in);//Check final status
|
||||
in.close();
|
||||
return 0;
|
||||
}
|
||||
void Checkstatus(ifstream&in)
|
||||
{
|
||||
int i;
|
||||
i=**in.rdstate**();
|
||||
if(i&__ios::eofbit__)
|
||||
cout<<"EOF encountered\n";
|
||||
else if(i&ios::failbit)
|
||||
cout<<" Non-Fatal I/O error\n";
|
||||
else if(i&ios::badbit)
|
||||
cout<<"Fatal I/O error\n";
|
||||
}
|
||||
该程序总要报一个“错误”。while循环结束后,还调用了一次checkstatus(),正如所期望
|
||||
的,程序报告遇到了EOF。以后读者会发现在编写程序时函数checkstatus的用途很大。
|
||||
|
||||
确定出错的另一种方法是使用下列函数中的一个或几个:
|
||||
int bad();
|
||||
int eof();
|
||||
int fail();
|
||||
int good();
|
||||
|
||||
前面已经讨论过eof(),这里不再赘述。badhit置位时函数bad()返回真。failbit置位时函
|
||||
数fail()返回真。无错误发生时,函数good()返回真,否则返回假。
|
||||
注:建议的ANSI C++标准规定,bad()、eof()、fail()和good()函数的返回值类型为布
|
||||
尔型。但现在大多数C++编译程序将它们的返回值类型说明为整型。从实际的观点看,这种
|
||||
差别是无关紧要的,因为在任何表达式中布尔类型自动转换为整型。
|
||||
|
||||
一旦有错误发生,在程序继续运行之前有必要__使用函数clear()清除错误标志__,其原型
|
||||
为:
|
||||
void clear(int flags=0);
|
||||
如果flags为0(缺省值),则清除所有的错误标志位(复位为0),否则将flags设置为所需
|
||||
的清除值。
|
||||
|
||||
|
||||
===== 18.13定制的I/O和文件 =====
|
||||
|
||||
在第十六章里,我们学习了如何重载关于自定义的类的插入和提取运算符。第十六章只
|
||||
介绍了控制台I/O.但是,由于所有c++的流都是一样的,所以无需做任何改变就可以用同
|
||||
样的重载插入函数向屏幕或文件输出。下面的例子再现了第十六章中那个电话簿的例子,不
|
||||
过这里把表存到了磁盘上。这个程序非常简单,它可以向表中添加名字或在屏幕上显示表的
|
||||
内容。作为一个练习,把程序做一些改进,使它能寻找指定的成员和删除不要的成员。
|
||||
#include<iostream.h>
|
||||
#include <fstream.h>
|
||||
#include<string.h>
|
||||
class phonebook{
|
||||
char name[80];
|
||||
char areacode[4];
|
||||
char prefix[4];
|
||||
char num[5];
|
||||
public:
|
||||
phonebook(){};
|
||||
phonebook(char * n,char * a,char * p, char * nm)
|
||||
{
|
||||
__strcpy__(name,n);
|
||||
strcpy(areacode,a);
|
||||
strcpy(prefix,p);
|
||||
strcpy(num , nm);
|
||||
}
|
||||
friend ostream&operator<<(ostream&stream,phonebook o);
|
||||
friend istream &Operator>>(ostream &stream, phonebook &o);
|
||||
};
|
||||
// Display name and phone number.
|
||||
__ostream&operator<<__(ostream &stream, phonebook o)
|
||||
{
|
||||
stream <<o.name <<" ";
|
||||
stream<<"(" <<o.areacode<<")";
|
||||
stream<<o.prefix<<"-";
|
||||
stream<<o.num<<"\n";
|
||||
return stream ;// must return stream
|
||||
}
|
||||
// Input name and telephone number.
|
||||
istream &operator>>(istream &stream, phonebook &o)
|
||||
{
|
||||
cout << "Enter name:";
|
||||
stream >> o. name;
|
||||
cout <<"Enter area code:";
|
||||
stream >> o. areacode;
|
||||
cout <<"Enter prefix :";
|
||||
stream >> o.prefix;
|
||||
cout <<"Enter number :";
|
||||
|
||||
stream>>o. num;
|
||||
cout<<"\n";
|
||||
return stream;
|
||||
}
|
||||
main()
|
||||
{
|
||||
phonebook a;
|
||||
char c;
|
||||
fstream pb("phone”, ios::in|ios::out | ios::app);
|
||||
if(!pb){
|
||||
cout << "Cannot open phonebook file.\n";
|
||||
return 1;
|
||||
}
|
||||
for (;;){
|
||||
char c;
|
||||
do{
|
||||
cout<<"1.Enter numbers\n";
|
||||
cout << "2. Display numbers\n";
|
||||
cout<<"3.Quit\n";
|
||||
cout <<"\nEnter a choice:";
|
||||
cin>>c;
|
||||
}while(c<’1’||c>’3’);
|
||||
switch(c){
|
||||
case’1’:
|
||||
cin>>a;
|
||||
cout<<"Entry is:";
|
||||
cout << a;//show on screen
|
||||
pb <<a;// write to disk
|
||||
break;
|
||||
case’2’:
|
||||
Char ch;
|
||||
pb.seekg(0, ios::beg);
|
||||
while(!pb.eof()){
|
||||
pb.get(ch);
|
||||
cout <<ch;
|
||||
}
|
||||
pb.clear();// reset eof
|
||||
cout<<end;
|
||||
break;
|
||||
case’3’:
|
||||
pb.close();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
注意,重载的运算符((不用做任何改变就可用于写磁盘文件,也可用于写屏幕。这是
|
||||
C++I/O方法的最重要也是最有用的特征之一。
|
||||
31
Zim/Programme/C++/C++的发展,特点和源程序构成.txt
Normal file
@@ -0,0 +1,31 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T13:09:54+08:00
|
||||
|
||||
====== C++的发展,特点和源程序构成 ======
|
||||
Created Saturday 06 August 2011
|
||||
|
||||
最近一段时间在学习C++,也借了几本相关的书籍。因为之前主要用C#写程序,大概写了也有两年了吧。所以在回过头来学习C++,还是挺快的。但是我觉得光看书是不行的,要写!!因此我想把我整个学习C++的过程用博文记录下来,就像那些大虾们写的系列文章一样,这样也可以和更多学习C++的朋友们交流心得,互相促进!唉,废话不说了,开始写的我的第一章C++初始之C++的发展,特点和源程序构成。
|
||||
|
||||
C++的发展,大家都知道C++是从C语言演变而来的,它扩充了C,又保持了与C的兼容。C++是美国贝尔实验室的Bjarne Stroustrup博士以及其同事与20世纪80年代初在C语言的基础上开发成功的。最初的C++被称为“带类的C”,1983年正式取名为C++。1985年有Bjarne Stroustrup博士编写的《C++程序设计语言》一书的出版,标志着C++1.0版本的诞生,此后贝尔实验室又推出了C++2.0,3.0,4.0版本。在1989年,C++的标准化工作开始了,直到1998年11月被国际化标准组织(ISO)批准为国际标准。
|
||||
|
||||
C++的特点:1.它是C的扩充,兼容C,这就使许多C代码在不经修改就可以为C++所用;2.C++保持C的简洁、高效和接近汇编语言的特点上,又比C更安全,可读性跟好,代码更为合理;3.与C比最大特点,那就是增加了面向对象的机制;因此,C++既可用于面向过程的结构化程序设计,也可以用于面向对象的程序设计。
|
||||
|
||||
接下来说说C++源程序的构成吧,下面是一段关于整数和的C++简单程序(开发工具vs2010):
|
||||
|
||||
|
||||
|
||||
1 #include "stdafx.h" //这是一条C++编译预处理命令,用来指示编译器在对程序进行预处理时,将文件stdafx的代码嵌入到程序中该指令所在的地方
|
||||
2 #include <iostream>//这个文件中声明了流对象Cout,Cin以及<<,>>的定义
|
||||
3
|
||||
4 using namespace std;//用了#include <iostream>就一定要用该命名空间指令
|
||||
5 int main()
|
||||
6 {
|
||||
7 int x,y,sum;
|
||||
8 cout<<"Please input two integers:"<<'\n';//提示用户键盘输入两个整数
|
||||
9 cin>>x;//输入变量x值
|
||||
10 cin>>y;//输入变量y值
|
||||
11 sum=x+y;
|
||||
12 cout<<"x+y="<<sum<<endl;//endl是输出操作符,其作用与“\n”相同
|
||||
13 return 0;//如果程序正常结束,向操作系统返回一个数值0
|
||||
14 }
|
||||
256
Zim/Programme/C++/C++的发展,特点和源程序构成/C++之类与对象(1).txt
Normal file
@@ -0,0 +1,256 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-07T09:21:54+08:00
|
||||
|
||||
====== C++之类与对象(1) ======
|
||||
Created Sunday 07 August 2011
|
||||
|
||||
下个阶段,我将讲解C++中面向对象的部分,也是C++对C语言改进的最重要的部分。以前C++也被叫做是"带类的C"。今天主要讲类的构成,成员函数以及对象的定义和使用。
|
||||
|
||||
1.其实这一节,对于用C#开发的人来说,简直就是驾轻就熟啊。C++类的构成,我想
|
||||
|
||||
===== 从C的结构体开始说起 =====
|
||||
。C中的结构体我想大家在熟悉不过了。
|
||||
|
||||
1 #include "stdafx.h"
|
||||
2 #include <iostream>
|
||||
3
|
||||
4 using namespace std;
|
||||
5
|
||||
6 struct Kid
|
||||
7 {
|
||||
8 int age;
|
||||
9 char *name;
|
||||
10 char *sex;
|
||||
11 };
|
||||
12
|
||||
13 int main()
|
||||
14 {
|
||||
15 Kid kid1;
|
||||
16 kid1.age=10;
|
||||
17 kid1.name="rookie_j";
|
||||
18 kid1.sex="男";
|
||||
19 cout<<"姓名:"<<kid1.name<<endl<<"年龄:"<<kid1.age<<endl<<"性别:"<<kid1.sex<<endl;
|
||||
20 return 0;
|
||||
21 }
|
||||
|
||||
==== 但是C中的结构体存在缺点: ====
|
||||
1.main函数中的任意赋值语句都可以访问结构体中的成员,但在现实生活中并不是什么数据都可以被随意访问的,因此C语言中的结构体的数据是__不安全的__;
|
||||
2.结构体中的__数据和对该数据的操作是分离的__,并不是一个被封装起来的整体,因此使程序难以重用,影响了软件生产效率;于是C++中引入了类的概念。
|
||||
|
||||
===== C++中类的一般格式为: =====
|
||||
|
||||
class Kid
|
||||
{
|
||||
private:
|
||||
int age; //私有成员
|
||||
char *name;
|
||||
char *sex;
|
||||
public: //公有成员
|
||||
void setKid(int age,char *name,char *sex);
|
||||
void showKid();
|
||||
};
|
||||
|
||||
C++中规定如果没有对类的成员加私有private,保护protected,或公有public,则__默认为私有的__。而对于C++的结构体来说,成员可以是私有的,保护的或公有的,但默认为公有的;还有要注意的是__不能在类的声明中给数据成员赋值__,比如:
|
||||
|
||||
class Kid
|
||||
|
||||
{
|
||||
private :
|
||||
int age=10;
|
||||
char *name="rookie_j";
|
||||
char *sex="男";
|
||||
};
|
||||
|
||||
一般情况下,一个类的数据成员应该声明为私有的,成员函数声明为公有的。这样,内部的数据隐藏在类中,在类的外部无法访问直接访问,使数据得到有效的保护。而__公有的成员函数就成为一种与类外部沟通的接口__。
|
||||
|
||||
===== 2.C++中的成员函数有两种,一种为普通的成员函数: =====
|
||||
|
||||
1 class Kid
|
||||
2 {
|
||||
3 private:
|
||||
4 int age;
|
||||
5 char *name;
|
||||
6 char *sex;
|
||||
7 public:
|
||||
8 void setKid(int age,char *name,char *sex);
|
||||
9 void showKid();
|
||||
10
|
||||
11 };
|
||||
12
|
||||
13
|
||||
14 void Kid::setKid(int age,char *name,char *sex)
|
||||
15 {
|
||||
16 Kid::age=age;
|
||||
17 Kid::name=name;
|
||||
18 Kid::sex=sex;
|
||||
19 }
|
||||
20
|
||||
21 void Kid::showKid()
|
||||
22 {
|
||||
23 cout<<"姓名:"<<name<<endl<<"年龄:"<<age<<endl<<"性别:"<<sex<<endl;
|
||||
24 }
|
||||
|
||||
要注意几点:
|
||||
1.类名和函数名之间应加上作用__域运算符“::”__,用于声明这个成员函数是属于哪一个类的,如果在函数名前没有类名,或既无类名又无作用域运算符“::”,比如:::showKid()或showKid(),那么这个函数__不属于任何类,不是成员函数,而是普通函数__;
|
||||
2.在类的声明中,成员函数原型的参数表中可以不说明参数的名字,而只说明它的类型,但在类外定义时必须既要说明参数类型又要说明参数名;
|
||||
|
||||
==== 另外一种就是内联成员函数,它又分显式声明和隐式声明: ====
|
||||
|
||||
隐式声明:
|
||||
|
||||
1 class Kid
|
||||
2 {
|
||||
3 private:
|
||||
4 int age;
|
||||
5 char *name;
|
||||
6 char *sex;
|
||||
7 public:
|
||||
8 void setKid(int age,char *name,char *sex)
|
||||
9 {
|
||||
10 __Kid::age__=age;
|
||||
11 Kid::name=name;
|
||||
12 Kid::sex=sex;
|
||||
13 }
|
||||
14 void showKid()
|
||||
15 {
|
||||
16 cout<<"姓名:"<<name<<endl<<"年龄:"<<age<<endl<<"性别:"<<sex<<endl;
|
||||
17 }
|
||||
18
|
||||
19 };
|
||||
|
||||
因为这种定义的内联成员函数没有使用__关键字inline__进行声明,因此叫__隐式定义__;
|
||||
|
||||
显式声明:
|
||||
|
||||
1 class Kid
|
||||
2 {
|
||||
3 private:
|
||||
4 int age;
|
||||
5 char *name;
|
||||
6 char *sex;
|
||||
7 public:
|
||||
8 __ inline void __setKid(int age,char *name,char *sex);
|
||||
9 inline void showKid();
|
||||
10 };
|
||||
11
|
||||
12
|
||||
13__ inline void__ Kid::setKid(int age,char *name,char *sex)
|
||||
14 {
|
||||
15 Kid::age=age;
|
||||
16 Kid::name=name;
|
||||
17 Kid::sex=sex;
|
||||
18 }
|
||||
19
|
||||
20 inline void Kid::showKid()
|
||||
21 {
|
||||
22 cout<<"姓名:"<<name<<endl<<"年龄:"<<age<<endl<<"性别:"<<sex<<endl;
|
||||
23 }
|
||||
|
||||
__内联函数的调用就是代码的扩展__,而不是一般函数的调用的操作;但要注意的是__使用inline定义的内联函数必须将类的声明和内联成员函数的定义都放在同一个文件中__,否则编译时无法进行代码的置换;
|
||||
|
||||
===== 3.在C++中,类与对象的关系,可以用数据类型int和整形变量i之间的关系来类比。 =====
|
||||
int类型和类类型代表一种__抽象的概念__,而整形变量和类的对象代表__具体的东西__。C++把类的变量称为类的对象,对象也被称为类的实例;类的对象可以是:
|
||||
|
||||
1 class Kid
|
||||
2 {
|
||||
3 private:
|
||||
4 int age;
|
||||
5 char *name;
|
||||
6 char *sex;
|
||||
7 public:
|
||||
8 inline void setKid(int age,char *name,char *sex);
|
||||
9 inline void showKid();
|
||||
10 }kid1,kid2;
|
||||
|
||||
也可以是声明了类后,使用时再定义对象:Kid kid1,kid2;(__声明一个类后,它并不接受和存储具体的值__,只作为生成具体对象的一种“样板”,**只有定义了对象后,系统才为对象分配存储空间**,以存放对象中的成员);
|
||||
|
||||
==== 对对象中的成员的访问可以是: ====
|
||||
1.对象名.数据成员名/对象名.成员函数名(参数),比如kid1.setKid(10,"rookie_j","男");
|
||||
2.指针访问对象中成员,比如:class Kid{public: int age;}; Kid kid,*ptr; ptr=&kid;cout<<ptr->age//cout<<(*ptr).age;
|
||||
3.还记得上节所讲到的引用(reference)么,还可以通过引用来访问对象中的成员:class Kid{public: int age;}; Kid kid;Kid &ptr=kid; cout<<ptr.age//cout<<kid.age;
|
||||
4.最后我们还是一样,用一个实例来总结一下今天所讲的内容(开发工具:vs2010):
|
||||
|
||||
1 #include "stdafx.h"
|
||||
2 #include <iostream>
|
||||
3
|
||||
4 using namespace std;
|
||||
5
|
||||
6 struct struct_Kid //结构体
|
||||
7 {
|
||||
8 int age; //默认公有
|
||||
9 char *name;
|
||||
10 char *sex;
|
||||
11 }kid1;
|
||||
12
|
||||
13 class Kid
|
||||
14 {
|
||||
15 int age; __//默认私有__
|
||||
16
|
||||
17 private: //私有
|
||||
18 char *name;
|
||||
19 char *sex;
|
||||
20
|
||||
21 public: //公有
|
||||
22 __inline void __setKid(int age,char *name,char *sex);//显式内联
|
||||
23 /*{
|
||||
24 Kid::age=age;
|
||||
25 Kid::name=name;
|
||||
26 Kid::sex=sex;
|
||||
27 }*/
|
||||
28 void showKid()//隐式内联
|
||||
29 {
|
||||
30 cout<<"类:"<<endl<<"姓名:"<<name<<endl<<"年龄:"<<age<<endl<<"性别:"<<sex<<endl;
|
||||
31 }
|
||||
32
|
||||
33 }__kid2;//直接定义对象__
|
||||
34
|
||||
35
|
||||
36 inline void Kid::setKid(int age,char *name,char *sex)
|
||||
37 {
|
||||
38 Kid::age=age;
|
||||
39 Kid::name=name;
|
||||
40 Kid::sex=sex;
|
||||
41 }
|
||||
42
|
||||
43 int main()
|
||||
44 {
|
||||
45 //结构体
|
||||
46 kid1.age=10;
|
||||
47 kid1.name="rookie_j";
|
||||
48 kid1.sex="男";
|
||||
49 cout<<"结构体:"<<endl<<"姓名:"<<kid1.name<<endl<<"年龄:"<<kid1.age<<endl<<"性别:"<<kid1.sex<<endl;
|
||||
50
|
||||
51 cout<<"--------------------"<<endl;
|
||||
52
|
||||
53 //类
|
||||
54 Kid kid3,*ptr;
|
||||
55 Kid &kid4=kid3;
|
||||
56 ptr=&kid2;
|
||||
57
|
||||
58 kid2.setKid(0,"rookie_y","男");
|
||||
59 kid2.showKid();
|
||||
60
|
||||
61 cout<<"--------------------"<<endl;
|
||||
62
|
||||
63 //指针调用成员函数
|
||||
64 (*ptr).setKid(20,"rookie_y","女");//或ptr->setKid(20,"rookie_z","女");
|
||||
65 kid2.showKid();
|
||||
66
|
||||
67 cout<<"--------------------"<<endl;
|
||||
68
|
||||
69 //对象名调用成员函数
|
||||
70 kid3.setKid(10,"rookie_x","男");
|
||||
71 kid3.showKid();
|
||||
72
|
||||
73 cout<<"--------------------"<<endl;
|
||||
74
|
||||
75 //引用调用成员函数
|
||||
76 kid4.setKid(30,"rookie_x","女");
|
||||
77 kid3.showKid();
|
||||
78
|
||||
79 return 0;
|
||||
80 }
|
||||
|
||||
结果:
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-07T10:05:16+08:00
|
||||
|
||||
====== 数据的共享与保护(一) ======
|
||||
Created Sunday 07 August 2011
|
||||
|
||||
1 class X {
|
||||
2 private:
|
||||
3 int m;
|
||||
4 } // 声明X类
|
||||
5 __/* 类的作用域 */__
|
||||
6 /*-----------------------访问数据成员---------------------*/
|
||||
7 X x; // 定义x对象
|
||||
8 x.m ; // 访问对象成员
|
||||
9__ X::m; // 访问类的静态成员__
|
||||
10 ptr->m; // ptr是指向类X的一个对象的指针,可访问数据成员
|
||||
11
|
||||
12 // 如果对象的生存期与函数的运行期相同,则称它具有__静态生存期__:
|
||||
13 static int i; // 局部作用域中静态变量的特点是他不会随着函数的每次调用而产生一个副本,也不会随着函数的返回而失效。
|
||||
14 // 动态生存期对象也称为局部生存期对象,它诞生于声明点,结束于声明所在的块执行完毕之时
|
||||
15 // 类的静态数据成员是用来__描述类属性__的,它不属于任何一个对象,静态数据成员具有静态生存期,用法是“__类名::标识符__”。
|
||||
|
||||
__//类的静态数据成员必须在类定义之外在加以定义,是因为需要以这种方式专门为他们分配空间。__
|
||||
16 class Point{
|
||||
17 public:
|
||||
18 ....
|
||||
19 private:
|
||||
20 static int count;
|
||||
21 };
|
||||
22
|
||||
23 int Point::count=0; __//静态数据成员的初始化__
|
||||
24
|
||||
25 // __静态函数成员可以通过类名或对象名来调用__,而非静态函数成员只能通过对象名来调用
|
||||
26
|
||||
27 class A{
|
||||
28 public:
|
||||
29 static void f(A a); // 静态函数成员
|
||||
30 private:
|
||||
31 int x;
|
||||
32 };
|
||||
33 void A::f(A a) {
|
||||
34 cout<<x;
|
||||
35 cout<<a.x;
|
||||
36 }
|
||||
58
Zim/Programme/C++/C++的发展,特点和源程序构成/C++之类与对象(1)/类(一、内联函数).txt
Normal file
@@ -0,0 +1,58 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-07T09:42:59+08:00
|
||||
|
||||
====== 类(一、内联函数) ======
|
||||
Created Sunday 07 August 2011
|
||||
|
||||
1 //4_1.cpp
|
||||
2 #include<iostream>
|
||||
3 using namespace std;
|
||||
4
|
||||
5 // 抽象、封装、继承、多态
|
||||
6
|
||||
7 class Clock { //时钟类的定义
|
||||
8 public: //外部接口,公有成员函数
|
||||
9 void SetTime(int newH=0,int newM=0,int newS=0); // 设置参数的默认值
|
||||
10 void ShowTime();
|
||||
11 //====================================================================================
|
||||
12 // 函数的调用过程要消耗一些内存资源和运行时间来传递参数和返回值,要记录调用的状态,
|
||||
13 // 以保证调用完成后能够正确地返回并继续执行。如果有的函数成员需要被频繁调用,并且代
|
||||
14 // 码相对简单,这个函数可以定义为内联函数(inline function)。内联函数的函数体会在
|
||||
15 // 编译时被插入到每一个调用它的位置,这可以减少调用时的开销,却增加了编译后代码的长
|
||||
16 // 度。
|
||||
17 //
|
||||
18 // 内联函数的声明分为:隐式声明和显式声明。
|
||||
19 //===================================================================================
|
||||
20__ // 隐式声明内联函数__
|
||||
21 /*
|
||||
22 *
|
||||
23 * void ShowTime() {
|
||||
24 * cout<<hour<<":"<<minute<<":"<<second<<endl;
|
||||
25 *
|
||||
26 * */
|
||||
27 private: //私有数据成员
|
||||
28 int hour,minute,second;
|
||||
29 };
|
||||
30 // 时钟类成员函数的具体实现,必须声明该函数所属的类名
|
||||
31 void __Clock::__SetTime(int i,int j,int k) {
|
||||
32 hour=i;
|
||||
33 minute=j;
|
||||
34 second=k;
|
||||
35 }
|
||||
36 // 显式声明内联函数
|
||||
37 __inline v__oid Clock::ShowTime() {
|
||||
38 cout<<hour<<":"<<minute<<":"<<second<<endl;
|
||||
39 }
|
||||
40 // 主函数
|
||||
41 int main() {
|
||||
42 Clock myClock; // myClock 成为目的对象
|
||||
43 cout<<"First time set and output:"<<endl;
|
||||
44 myClock.SetTime();
|
||||
45 myClock.ShowTime();
|
||||
46 cout<<"Second time set and output:"<<endl;
|
||||
47 myClock.SetTime(0,12,30);
|
||||
48 myClock.ShowTime();
|
||||
49 return 0;
|
||||
50 }
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-07T09:47:01+08:00
|
||||
|
||||
====== 类(三、复制构造函数) ======
|
||||
Created Sunday 07 August 2011
|
||||
|
||||
1 #include<iostream>
|
||||
2 using namespace std;
|
||||
3 class Point {
|
||||
4 public:
|
||||
5 Point(int xx=0,int yy=0) {
|
||||
6 x=xx;
|
||||
7 y=yy;
|
||||
8 }
|
||||
9 /*------------------复制构造函数--------------------*/
|
||||
10 Point(Point &p) { __ //常用于初始化一个类的对象,或者是作为函数的参数进行值传递时调用,有时也在返回对像后使用;__
|
||||
11 x=p.x;
|
||||
12 y=p.y;
|
||||
13 cout<<"copy constructor....."<<endl;
|
||||
14 }
|
||||
15 int getX();
|
||||
16 int getY();
|
||||
17 private:
|
||||
18 int x,y;
|
||||
19 };
|
||||
20 int Point::getX() {
|
||||
21 return x;
|
||||
22 }
|
||||
23 int Point::getY() {
|
||||
24 return y;
|
||||
25 }
|
||||
26 fun1(__Point b__) { __ //进行值传递时调用类的复制构造函数;注意:实参与形参类型不同时还有可能调用构造函数进行隐式类型转换。__
|
||||
27 cout<<b.getX()<<endl;
|
||||
28 }
|
||||
29 Point fun2(){
|
||||
30 Point a(3,4); __//返回值为类的对象时,复制构造函数也会被调用;__
|
||||
31 return a;
|
||||
32 }
|
||||
33 int main() {
|
||||
34 Point m(3,0);
|
||||
35 Point l;
|
||||
36 fun1(m);
|
||||
37 l=fun2();
|
||||
38 cout<<"----------"<<endl;
|
||||
39 Point n(m); __ //用一个类的对象去初始化另一个对象;__
|
||||
40 Point o=m; __//上一种情况的另一种表示方法;__
|
||||
41 return 0;
|
||||
42 }
|
||||
@@ -0,0 +1,51 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-07T09:45:25+08:00
|
||||
|
||||
====== 类(二、构造与析构函数) ======
|
||||
Created Sunday 07 August 2011
|
||||
|
||||
1 #include<iostream>
|
||||
2 using namespace std;
|
||||
3
|
||||
4 class Clock {1 #include<iostream>
|
||||
2 using namespace std;
|
||||
3
|
||||
4 class Clock {
|
||||
5 public:
|
||||
6 /*----------------------------------构造函数------------------------*/
|
||||
7 Clock(int i,int j,int k); // 函数重载
|
||||
8 Clock() { // __内联函数的隐式表示__
|
||||
9 h=0;
|
||||
10 m=0;
|
||||
11 s=0;
|
||||
12 }
|
||||
13 /*----------------------------------析构函数------------------------*/
|
||||
14 ~Clock() {
|
||||
15 cout<<"析构函数已被调用!"<<endl; // 在对象撤销时调用
|
||||
16 }
|
||||
17 void setTime(int newH=0,int newM=0,int newS=0); // 设置函数的参数默认值,只有当函数
|
||||
18 void showTime();
|
||||
19 private:
|
||||
20 int h,m,s;
|
||||
21 };
|
||||
22 Clock::Clock(int i,int j,int k) { //__ 构造函数定义,不需要声明类型。__
|
||||
23 h=i;
|
||||
24 m=j;
|
||||
25 s=k;
|
||||
26 }
|
||||
27 void Clock::setTime(int newH,int newM,int newS) {
|
||||
28 h=newH;
|
||||
29 m=newM;
|
||||
30 s=newS;
|
||||
31 }
|
||||
32
|
||||
33 inline void Clock::showTime() { //__ inline位于类型void前,显式表示__
|
||||
34 cout<<h<<":"<<m<<":"<<s<<endl;
|
||||
35 }
|
||||
36
|
||||
37 int main() {
|
||||
38 Clock myclock(23,10,32);
|
||||
39 myclock.showTime();
|
||||
40 return 0;
|
||||
41 }
|
||||
68
Zim/Programme/C++/C++的发展,特点和源程序构成/C++之类与对象(1)/类(四、类的组合).txt
Normal file
@@ -0,0 +1,68 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-07T09:51:01+08:00
|
||||
|
||||
====== 类(四、类的组合) ======
|
||||
Created Sunday 07 August 2011
|
||||
|
||||
1 #include<iostream>
|
||||
2 #include<cmath> //sqrt()函数的头文件;__c++的类应当先定义后使用__,在两个类互相引用时(循环依赖),可用__前向引用声明:class ***__;
|
||||
3 using namespace std;
|
||||
4 class Point {
|
||||
5 public:
|
||||
6 Point(int xx=0,int yy=0) {
|
||||
7 x=xx;
|
||||
8 y=yy;
|
||||
9 }
|
||||
10 Point(Point &p) {
|
||||
11 x=p.x;
|
||||
12 y=p.y;
|
||||
13 cout<<"calling the copy constructor of Point..."<<endl;
|
||||
14 }
|
||||
15 int getX();
|
||||
16 int getY();
|
||||
17 private:
|
||||
18 int x,y;
|
||||
19 };
|
||||
20 int Point::getX() {
|
||||
21 return x;
|
||||
22 }
|
||||
23 int Point::getY() {
|
||||
24 return y;
|
||||
25 }
|
||||
26 /*------------------------类的组合-------------------*/
|
||||
27 class Line {
|
||||
28 public:
|
||||
29 __Line(Point pp1,Point pp2);__
|
||||
30 Line(Line &l);
|
||||
31 double getLen();
|
||||
32 private:
|
||||
33 __Point p1,p2;__
|
||||
34 double len;
|
||||
35 };
|
||||
36 /*---------------------组合类的构造函数---------------*/
|
||||
37 Line::Line(Point pp1,Point pp2):__p1(pp1),p2(pp2)__ { //p1(pp1)表示用pp1来初始化p1,注意中间用逗号隔开;
|
||||
__//注意:必须要用“初始式”的形式对对像成员进行初始化。__
|
||||
38 double x=static_cast<double> (pp1.getX()-pp2.getX()); /__/static_cast为强制类型转换;__
|
||||
39 double y=static_cast<double> (pp1.getY()-pp2.getY());
|
||||
40 len=sqrt(x*x+y*y);
|
||||
41 }
|
||||
42 double Line::getLen() {
|
||||
43 return len;
|
||||
44 }
|
||||
45 /*----------------------组合类的复制构造函数-------------*/
|
||||
46 Line::Line(Line &l):p1(l.p1),p2(l.p2) {
|
||||
47 len=l.getLen();
|
||||
48 cout<<"calling the copy constructor of Line..."<<endl;
|
||||
49 }
|
||||
50 int main() {
|
||||
51 Point mypt1(1,1),mypt2(4,5);
|
||||
52 Line line(mypt1,mypt2);
|
||||
53 Line line1(line);
|
||||
54 cout<<"the first line'lengh is :"<<endl;
|
||||
55 cout<<line.getLen()<<endl;
|
||||
56 cout<<"teh second line'lengh is :"<<endl;
|
||||
57 cout<<line1.getLen()<<endl;
|
||||
58 return 0;
|
||||
59 }
|
||||
|
||||
87
Zim/Programme/C++/C++的发展,特点和源程序构成/C++之类与对象(2).txt
Normal file
@@ -0,0 +1,87 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-07T10:14:44+08:00
|
||||
|
||||
====== C++之类与对象(2) ======
|
||||
Created Sunday 07 August 2011
|
||||
|
||||
接着上一节,今天讲C++中类的构造函数与析构函数,对象的赋值与复制.
|
||||
|
||||
1.用过C#语言的人,都知道__构造函数是一种特殊的成员函数,它主要用于对对象分配空间,进行初始化。__构造函数的名字必须与类名相同。可以有任何类型的参数,但不返回任何值,是在__建立对象时自动执行__的。和上一节一样,还是用Kid类来说明。
|
||||
|
||||
1 class Kid
|
||||
2 {
|
||||
3 private:
|
||||
4 int age;
|
||||
5 char *name;
|
||||
6 char *sex;
|
||||
7 public:
|
||||
8 Kid(int age,char *name,char *sex);
|
||||
9 void showKid();
|
||||
10 };
|
||||
11
|
||||
12 Kid::Kid(int age,char *name,char *sex)
|
||||
13 {
|
||||
14 Kid::age=age;
|
||||
15 Kid::name=name;
|
||||
16 Kid::sex=sex;
|
||||
17 }
|
||||
18
|
||||
19 void Kid::showKid()
|
||||
20 {
|
||||
21 cout<<"姓名:"<<name<<endl<<"年龄:"<<age<<endl<<"性别:"<<sex<<endl;
|
||||
22 }
|
||||
|
||||
接下来,建立对象并初始化:Kid kid(10,"rookie_j","男");另外一种使用new运算符__动态建立对象__:Kid *ptr=new Kid(10,"rookie_j","男");通过指针变量ptr来访问:ptr->showKid();当我们用new建立对象时,当不再使用它时,要用delete运算符释放它:delete ptr;和不同成员函数一样,__如果构造函数定义在类体内部,则作为内联函数处理__;
|
||||
|
||||
在声明类时,对数据成员的初始化一般在构造函数中用赋值语句进行,但C++还提供了另外一种初始化数据成员的方法——__用成员初始化表来实现对数据成员的初始化。__它的一般形式为:类名::构造函数名([参数表]):[(成员初始化表)];
|
||||
成员初始化表的形式为:成员名1(初始值1),成员名2(初始值2),成员名2(初始值2);比如:
|
||||
|
||||
Kid::Kid(int age,char *name,char *sex):age(age),name(name),sex(sex){};
|
||||
|
||||
接下来讲一下析构函数:在我第一次在C++里看到这个名词时,感觉这个知识点很深奥,结果看了以后,其实很简单。它的作用和构造函数刚好相反,__用于撤销对象__,如:释放分配给对象的内存空间。析构函数和构造函数名相同,但在其前面要加~符号,析构函数没有参数,也没有返回值,且不能重载,因此一个类中只有一个析构函数。
|
||||
以下三种情况,__当对象的生命周期结束时,析构函数会被自动调用__:
|
||||
(1)定义了全局对象,则在程序流程离开其作用域(如:main函数结束或调用Exit)时,调用该全局对象的析构函数;
|
||||
(2)对象被定义在函数体里,则当这个函数执行完后,该对象释放,析构函数被自动调用;
|
||||
(3)若一个对象使用new运算符动态创建的,在使用delete运算符释放它时,会自动调用析构函数;
|
||||
View Code
|
||||
|
||||
结果:
|
||||
|
||||
如果没有给类定义构造函数,则__编译系统自动地生成一个默认的构造函数__,比如在Kid类中编译系统会为其产生一个Kid::Kid(){};构造函数,这个__默认的构造函数只能给对象开辟存储空间,不能给数据成员赋值初始化,这时数据成员的初值就是随机数__。对没有定义构造函数的类,其公有数据成员可以用初始化值表进行初始化,如:
|
||||
|
||||
1 class Kid
|
||||
2 {
|
||||
3 public:
|
||||
4 int age;
|
||||
5 char *name;
|
||||
6 char *sex;
|
||||
7 };
|
||||
8
|
||||
9 int main()
|
||||
10 {
|
||||
11
|
||||
12 Kid kid={10,"Rookie_j","男"};
|
||||
13 cout<<"姓名:"<<kid.name<<endl<<"年龄:"<<kid.age<<endl<<"性别:"<<kid.sex<<endl;
|
||||
14
|
||||
15 return 0;
|
||||
16 }
|
||||
|
||||
但只要一个类定义了构造函数,系统将不再给它提供默认构造函数;另外还有默认的析构函数(Kid::~Kid(){}),一般来说默认的析构函数就能满足要求,但对一些需要做一些内部处理的则应该显式定义析构函数。带默认参数的构造函数和之前所说的带参数的成员函数是一样的,对于构造函数的重载,在这里我就不多说了,只想强调一点,如果是无参的构造函数创建对象,应该使用"类名 对象名"的形式,而不是"类名 对象名()";
|
||||
|
||||
2.对象的赋值其实和变量的赋值差不多,也是用赋值运算符=进行的,只不过进行赋值的两个对象的类型必须相同,对象之间的赋值只是数据成员的赋值,而不对成员函数赋值;
|
||||
|
||||
View Code
|
||||
|
||||
结果:
|
||||
|
||||
__拷贝构造函数是一种特殊的构造函数__,其形参是类对象的引用。它主要用于__在建立一个新的对象时,使用已经存在的对象去初始化这个新对象。__拷贝构造函数也是构造函数,所以函数名必须与类名相同,参数只有一个就是同类对象的引用,每个类必须要有一个拷贝构造函数。如果程序员自己不定义拷贝构造函数,系统会自动产生一个__默认拷贝构造函数__。
|
||||
|
||||
调用拷贝构造函数的形式有__代入法__:类名 对象2(对象1)和__赋值法__:类名 对象2=对象1;
|
||||
View Code
|
||||
|
||||
结果:
|
||||
|
||||
同样的默认的拷贝构造函数:复制出与源对象的数据成员的值一样的新对象。调用拷贝构造函数的3种情况:(1)Kid kid2(kid1)或Kid kid2=kid1;(2)函数的形参是类的对象:fun(Kid kid){kid.showKid();}; int main(){Kid kid(10,"Rookie_j","男");fun(kid);return 0;};(3)函数返回值为类的对象:Kid fun(){Kid kid(10,"Rookie_j","男"); return kid;} int main(){ Kid kid; kid=fun();kid.showKid();return 0;};
|
||||
|
||||
3.最后还是一样用一个实例来总结一下今天所说的内容(开发工具:vs2010):
|
||||
59
Zim/Programme/C++/C++的发展,特点和源程序构成/C++之类与对象(3).txt
Normal file
@@ -0,0 +1,59 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-07T10:28:18+08:00
|
||||
|
||||
====== C++之类与对象(3) ======
|
||||
Created Sunday 07 August 2011
|
||||
|
||||
在上篇的最后的实例程序代码中,我所写的成员函数中的参数变量名和数据成员名一样,为了编译时不发生错误,我在数据成员的前面加上"类名::"以区分。其实还有另外一种方法可以来加以区分,那就是C++中的__自引用指针this__。今天就讲一下C++中的this以及string类。可能代码不会很多,但突出原理;
|
||||
|
||||
1.this这个关键字其实对于我来说并不陌生,在C#中就常用到。但今天我想说说为什么要有this,this起到什么作用?话还是要从类和对象说起,大家都知道定义一个对象,系统要给该对象分配存储空间,如果该类包含了数据成员和成员函数,就要分别给数据和函数的代码分配存储空间。按照正常额思路,如果类定义了2个对象,那么就应该分别给这2个对象的数据和函数分配空间。但事实并非如此,__C++的编译系统只用了一段空间来存放在各个共同的函数代码段,在调用各个对象的成员函数时,都与调用这个公共的函数代码。__因此,__每个对象的存储空间都只是该对象数据成员所占用的存储空间__,而不包括成员函数代码所占有的空间,函数代码是存储在对象空间之外的。但是问题就出来了,既然所有对象所调用的成员函数代码只是单独一封,那么成员函数是怎么知道当前调用自己的是哪一个对象呢?
|
||||
|
||||
这时就诞生了this这个玩意,它是一个自引用的指针。每__当创建一个对象时,系统就把this指针初始化为指向该对象,即this指针的值为当前调用成员函数的对象的起始地址。每当调用一个成员函数时,系统就把this指针作为一个隐含的参数传给该函数。不同的对象调用同一个成员函数时,C++编译器将根据成员函数的this指针所指向的对象来确定应该调用哪一个对象的数据成员。__通过下面一个简单实例就可说明。
|
||||
|
||||
1 #include "stdafx.h"
|
||||
2 #include <iostream>
|
||||
3 #include <string>
|
||||
4
|
||||
5
|
||||
6 class Num
|
||||
7 {
|
||||
8 private:
|
||||
9 int a;
|
||||
10 __std::string__ objectName;
|
||||
11 public:
|
||||
12 Num(std::string objectName,int a);
|
||||
13 void showNum();
|
||||
14 };
|
||||
15
|
||||
16 Num::Num(std::string objectName,int a) //对像调用其方法时,系统会隐含地把其地址作为this指针传给相应函数。
|
||||
17 {
|
||||
18 __ this->objectName=objectName;__
|
||||
19 this->a=a;
|
||||
20 }
|
||||
21
|
||||
22 void Num::showNum()
|
||||
23 {
|
||||
24 std::cout<<this->objectName<<":this="<<this<<" a="<<this->a<<std::endl;
|
||||
25 }
|
||||
26
|
||||
27
|
||||
28 int main()
|
||||
29 {
|
||||
30 Num num1("num1",1);
|
||||
31 Num num2("num2",2);
|
||||
32 Num num3("num3",3);
|
||||
33
|
||||
34 num1.showNum();
|
||||
35 num2.showNum();
|
||||
36 num3.showNum();
|
||||
37
|
||||
38 return 0;
|
||||
39 }
|
||||
|
||||
结果:
|
||||
|
||||
2.在前几篇的代码中,我都是**用Char *字符串指针来定义字符变量**,还有就是在上一篇中的Kid类中的构造函数,用到过字符数组。并用字符复制函数strcpy,从中出现了问题,后来在博友的帮助下解决了。strcpy、strcat(字符串连接)和字符串长度(strlen),都是**C中标准库函数的字符串操作。**C++保留了这种格式,但是这种方法使用起来不太方便,另外__数据与处理数据的函数相分离也不符合面向对象的思想__。于是C++的标准库中,就出现了__字符串类string__。在使用string类型时,要在头文件中加入#include<string>;string的用法我在上述的例子中也有用到。在比如:string str("Hello C++")或string str="Hello C++";同时,也可以__在表达式中把string对象和以'\0'结束符混在一起使用__。
|
||||
|
||||
3.最后来列个表说明一下string中常用的运算符:
|
||||
{{./2011072923310661.jpg}}
|
||||
|
After Width: | Height: | Size: 42 KiB |
197
Zim/Programme/C++/C++的发展,特点和源程序构成/C++学习笔记之对文件的操作1.txt
Normal file
@@ -0,0 +1,197 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T17:20:04+08:00
|
||||
|
||||
====== C++学习笔记之对文件的操作1 ======
|
||||
Created Saturday 06 August 2011
|
||||
http://www.cnblogs.com/uniqueliu/archive/2011/08/03/2126545.html
|
||||
===========================前言===========================
|
||||
|
||||
我们在编写程序的时候,最密不可分的就是对文件进行相应的操作,我们可以从文件中读取数据,可以将数据保存到文件,可以……
|
||||
|
||||
总而言之,言而总之,一言以蔽之,对文件的操作是非常重要的,下面我们就来介绍一下C++中是如何对文件进行操作的。
|
||||
|
||||
===========================功能展示===========================
|
||||
|
||||
===== 文件的输出操作 =====
|
||||
|
||||
想要程序中的数据输出到文件中,一共需要以下5个步骤:
|
||||
|
||||
① 包含fstream头文件:#include <fstream>
|
||||
② 建立ofstream对象:ofstream ocout;
|
||||
③ 将对象与文件关联:ocout.open(“test.txt”);
|
||||
④ 使用该对象将数据输出到文件test中:ocout<<”Hello,C++!”;
|
||||
⑤ 关闭与文件的连接:ocout.close();
|
||||
|
||||
p.s. 在这里我们应用ofstream的对象ocout将数据直接输出到了文件中,而不是屏幕上!
|
||||
|
||||
完整程序示例:
|
||||
01 #include <fstream>
|
||||
02 using namespace std;
|
||||
03
|
||||
04 int main()
|
||||
05 {
|
||||
06 ofstream ocout;
|
||||
07 ocout.open("test.txt");
|
||||
08 ocout<<"Hello,C++!";
|
||||
09 ocout.close();
|
||||
10 return 0;
|
||||
11 }
|
||||
|
||||
运行程序后,我们就会在程序的目录下发现一个test.txt文件,打开之后会显示“Hello,C++!”。如下图所示
|
||||
{{./1.png}}
|
||||
特别注意的是,我们也可以把上面程序的第6和第7行合并为一句话:
|
||||
ofstream ocout("test.txt");
|
||||
|
||||
这句话的意思就是__调用ofstream类中的构造函数来创建这个文本文件。__另外,我们需要特别注意一点,在完成对整个文件的操作之后,一定要用close()函数将这个文件关闭了,否则在程序结束后,所操作的文件将什么都不会保存下来!!!
|
||||
|
||||
===== 读取文件中的数据 =====
|
||||
|
||||
打开文件读取数据的方法和输出数据到文集中的方法基本上是一样的,同样也需要5个步骤:
|
||||
|
||||
① 包含fstream头文件:#include <fstream>
|
||||
② 建立ifstream对象:ifstream icin;
|
||||
③ 将对象与文件关联:icin.open(“test.txt”);
|
||||
④ 使用该对象读取文件test中的数据到数组temp中:icin>>temp;
|
||||
⑤ 关闭与文件的连接:icin.close();
|
||||
|
||||
p.s同上面一样,我们也可以将第2步和第3步合并成一句话:
|
||||
ifstream icin("test.txt");
|
||||
|
||||
它的作用就是调用ifstream类中的构造函数来读取这个本地的文本文件。
|
||||
|
||||
完整的程序示例:
|
||||
01 #include <fstream>
|
||||
02 #include <iostream>
|
||||
03 using namespace std;
|
||||
04
|
||||
05 int main()
|
||||
06 {
|
||||
07 ifstream icin;
|
||||
08 icin.open("test.txt");
|
||||
09 char temp[100];//定义一个字符数组temp
|
||||
10 icin>>temp;//将文件中的数据读到字符数组temp中
|
||||
11 cout<<temp<<endl;//将temp中存放的内容输出到屏幕上
|
||||
12 return 0;
|
||||
13 }
|
||||
|
||||
运行之前,我们需要在该文件夹下建立test.txt文件,其中的内容就是上面的“Hello,C++!”。那么输出如下:
|
||||
{{./2.png}}
|
||||
可以看到,程序在命令行中显示出来了test.txt文本文件中的内容。
|
||||
|
||||
===== 如何读取空格和空格后面的字符 =====
|
||||
|
||||
我们在写文件的时候,空格是不可避免的。但是由于C++的插入操作符有一个毛病,它只要一**遇到空字符便会停止输出**。这里的空字符就是空格,或者是’\0’。那么这样一来,如果我们在文件中有空格字符,那么空格后面的字符就无法被输出到屏幕上了。比如说,我们建立的test.txt文件中的内容为:Hello C++!即把Hello后面的逗号改成空格,那么重新运行该程序,输出如下:
|
||||
{{./3.png}}
|
||||
|
||||
那么有没有什么解决方法呢,当然是有的哈。用__getline()__函数嘛。下面粘一段MSDN上面关于getline()函数原型和参数的介绍哈:
|
||||
|
||||
首先是函数原型:
|
||||
|
||||
template<class CharType, class Traits, class Allocator>
|
||||
basic_istream<CharType, Traits>& getline(
|
||||
basic_istream<CharType, Traits>& **_Istr**,
|
||||
basic_string<CharType, Traits, Allocator>& **_Str**
|
||||
);
|
||||
|
||||
template<class CharType, class Traits, class Allocator>
|
||||
basic_istream<CharType, Traits>& getline(
|
||||
basic_istream<CharType, Traits>&** _Istr**,
|
||||
basic_string<CharType, Traits, Allocator>& **_Str**,
|
||||
const CharType **_Delim**
|
||||
);
|
||||
|
||||
函数中的参数已经用黑体表示出来了哈,下面是参数说明:
|
||||
|
||||
_Istr
|
||||
The input stream from which a string is to be extracted.
|
||||
指明输入的缓冲区是谁
|
||||
|
||||
_Str
|
||||
The string into which are read the characters from the input stream.
|
||||
读取到流中的字符数据
|
||||
|
||||
_Delim
|
||||
The line delimiter.
|
||||
结束符号
|
||||
(默认的结束符号是’\n’,而这里采用自定义的结束符号替换掉默认的结束符号。意思就是输出遇到_Delim才会停止输出)
|
||||
|
||||
好了,有了这个函数,我们就可以把上面的程序中的第10行改成:
|
||||
icin.getline(temp,100);
|
||||
|
||||
这就表示把字符数组temp中的内容全部读取到屏幕上,如下图所示
|
||||
{{./4.png}}
|
||||
OK~~这样一来,我们就不怕文件中有空格了。
|
||||
|
||||
另外,如果我们想要在命令行中写一段话,而且这段换中包含了空格和回车,那么我们就应该利用上面getline()函数的第三个参数,因为一段话中有可能会有回车的出现,我们就必须利用getline()的第三个函数将__默认的结束符号’\n’换成空字符‘\0’__。这是由于空字符的ASCII码为0,我们不可能在文件中输入空字符,因此这个时候,getline()函数会一直读取到文件的末尾才会结束。而如何停止用户输入呢?方法其实很简单,在我们输入完一段话之后,肯定会按下回车。之后我们就应该向该函数发出EOF标志,即文件结束符标志(End Of File)。在命令行里面发送文件结束符的方法是“Ctrl+Z”,之后再次按下回车键就能停止输入了。
|
||||
p.s. 空格不是空字符,它的ASCII码为32。
|
||||
|
||||
下面,咱们用一个实际的例子来演示一下:首先读取一段话,然后将其输出到文件中:
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
const int num=255;
|
||||
char temp1[num]={0};//初始化数组temp1
|
||||
char temp2[num]={0};//初始化数组temp2
|
||||
//① 输出数据到文件text.txt中
|
||||
ofstream f_out("text.txt");
|
||||
cout<<"请输入文本的内容:\n";
|
||||
cin.getline(temp1,num,0);
|
||||
f_out<<temp1;
|
||||
f_out.close();
|
||||
//② 将文件text.txt中的内容重新读回屏幕上
|
||||
ifstream f_in("text.txt");
|
||||
f_in.getline(temp2,num,0);
|
||||
cout<<temp2<<endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
对整个程序的分析:
|
||||
① 输出数据到文件text.txt中
|
||||
首先我们在第11行定义了一个文件的输出流对象f_out,并用该对象创建了一个text.txt文本文件。之后在程序的第13行采用getline()函数接受文本内容,并将其放到temp1字符数组中。注意,这里的getline()函数的第三个参数为空字符,说明它可以接受空格,并且只有达到文件的末尾才能停止读取用户的键盘输入。好了,如果我们输入完文件之后按下Ctrl+Z,那么接着再次按下Enter回车键就会停止输入。之后在程序的第14行,我们用ofstream的对象fout将缓冲区中的内容输出到文本文件text.txt中。最后关闭这个文件。
|
||||
② 将文件text.txt中的内容重新读回屏幕上
|
||||
同输出一样,首先我们在程序的第17行定义了一个文件的输入流对象f_in,并用该对象读取了刚刚创建的text.txt文本文件。之后又调用getlin()函数将文件中的内容输出到了字符数组temp2中,之后运用cout来输出temp2数组的内容到屏幕上。这样一来,我们就完成了对文件的输入输出操作。
|
||||
|
||||
其运行的结果如下:
|
||||
{{./5.png}}
|
||||
OK啦!!!程序输出成功咯!!但是这个程序还有一个小小的瑕疵,注意看上面输出结果,我们可以看到在命令行中“请按任意键继续…”上面居然还有一个回车!这是怎么回事呢?我们并没有在多输出一个回车啊?
|
||||
|
||||
其实是有的!!!!注意,我们在“!”之后回了一次车,然后才输出了ctrl+Z,向getline()函数发送了一个文件结束的标志。之后为了让程序结束,又按了一下回车。那么这里面第2次按下的回车由于超出了文件结束符EOF被自动抛弃了,但是,第一次按下的回车,就是!之后的那个回车却没有被丢弃掉,而是被写入了temp1函数中。这个就是问题的所在。所以我们在输出之后,会看到在“请按任意键继续…”上面居然还有一个回车!对于这个问题,解决方法其实很简单,我们只需要把最后一个Enter改成空字符’\0’就可以了。即在程序的13行之后添加上这样两句话:
|
||||
1 int n=strlen(temp1);
|
||||
2 temp1[n-1]='\0';
|
||||
|
||||
第一句话的意思就是读取字符数组temp1中可见字符的长度,并保存到整型变量n中;第二句话的意思就是找到保存Enter键的元素的下标,然后将这个下标的元素赋值成空字符就可以了。完整的程序如下:
|
||||
01 #include <iostream>
|
||||
02 #include <fstream>
|
||||
03 using namespace std;
|
||||
04
|
||||
05 int main()
|
||||
06 {
|
||||
07 const int num=255;
|
||||
08 char temp1[num]={0};//初始化数组temp1
|
||||
09 char temp2[num]={0};//初始化数组temp2
|
||||
10 //① 输出数据到文件text.txt中
|
||||
11 ofstream f_out("text.txt");
|
||||
12 cout<<"请输入文本的内容:\n";
|
||||
13 cin.getline(temp1,num,0);
|
||||
14 int n=strlen(temp1);
|
||||
15 temp1[n-1]='\0';
|
||||
16 f_out<<temp1;
|
||||
17 f_out.close();
|
||||
18 //② 将文件text.txt中的内容重新读回屏幕上
|
||||
19 ifstream f_in("text.txt");
|
||||
20 f_in.getline(temp2,num,0);
|
||||
21 cout<<temp2<<endl;
|
||||
22 return 0;
|
||||
23 }
|
||||
|
||||
然后是程序的输出:
|
||||
{{./6.png}}
|
||||
好了,终于把这个程序搞定了,好麻烦!!!呼呼~~
|
||||
|
||||
这篇博文就记录这么多了,下次接着学习关于C++对文件的操作的方法,^_^
|
||||
|
||||
|
||||
BIN
Zim/Programme/C++/C++的发展,特点和源程序构成/C++学习笔记之对文件的操作1/1.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
Zim/Programme/C++/C++的发展,特点和源程序构成/C++学习笔记之对文件的操作1/2.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
Zim/Programme/C++/C++的发展,特点和源程序构成/C++学习笔记之对文件的操作1/3.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
Zim/Programme/C++/C++的发展,特点和源程序构成/C++学习笔记之对文件的操作1/4.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
Zim/Programme/C++/C++的发展,特点和源程序构成/C++学习笔记之对文件的操作1/5.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
Zim/Programme/C++/C++的发展,特点和源程序构成/C++学习笔记之对文件的操作1/6.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
@@ -0,0 +1,141 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T21:14:45+08:00
|
||||
|
||||
====== C++学习笔记之对文件的操作2 ======
|
||||
Created Saturday 06 August 2011
|
||||
===========================功能展示==========================
|
||||
|
||||
==== 打开文件的方式 ====
|
||||
|
||||
当我们想要打开的文件不存在的时候,一般地,ofstream类的对象会默认地自动创建一个文件。而如果我们想要打开的文件是存在的,那么就会调用ofstream的构造函数或者是调用open()函数进行打开。下面,我们来看一下MSDN上面是如何定义open()函数的:
|
||||
|
||||
首先是函数原型:
|
||||
|
||||
void open(
|
||||
const char *_Filename,
|
||||
ios_base::openmode _Mode = ios_base::in | ios_base::out,
|
||||
int _Prot = (int)ios_base::_Openprot
|
||||
);
|
||||
void open(
|
||||
const char *_Filename,
|
||||
ios_base::openmode _Mode
|
||||
);
|
||||
void open(
|
||||
const wchar_t *_Filename,
|
||||
ios_base::openmode _Mode = ios_base::in | ios_base::out,
|
||||
int _Prot = (int)ios_base::_Openprot
|
||||
);
|
||||
void open(
|
||||
const wchar_t *_Filename,
|
||||
ios_base::openmode _Mode
|
||||
);
|
||||
|
||||
接下来是参数的说明:
|
||||
|
||||
_Filename
|
||||
The name of the file to open.
|
||||
打开文件名
|
||||
|
||||
_Mode
|
||||
One of the enumerations in ios_base::openmode.
|
||||
文件的打开方式(在ios_base::openmode中定义)
|
||||
|
||||
_Prot
|
||||
The default file opening protection.
|
||||
默认进行文件打开时的保护
|
||||
|
||||
OK,我们再来看看ios_base::openmode中定义的打开方式:
|
||||
|
||||
ios::in, to permit extraction from a stream.
|
||||
打开文件进行读操作,即读取文件中的数据
|
||||
|
||||
ios::out, to permit insertion to a stream.
|
||||
打开文件进行写操作,即输出数据到文件中
|
||||
|
||||
ios::app, to seek to the end of a stream before each insertion.
|
||||
打开文件之后文件指针指向文件末尾,只能在文件末尾进行数据的写入
|
||||
|
||||
ios::ate, to seek to the end of a stream when its controlling object is first created.
|
||||
打开文件之后文件指针指向文件末尾,但是可以在文件的任何地方进行数据的写入
|
||||
|
||||
ios::trunc, to delete contents of an existing file when its controlling object is created.
|
||||
默认的文件打开方式,__若文件已经存在,则清空文件的内容__
|
||||
|
||||
ios::binary, to read a file as a binary stream, rather than as a text stream.
|
||||
打开文件为二进制文件,否则为文本文件
|
||||
|
||||
好了,open()函数的用法全部列举出来了。下面就针对ios_base::binary的二进制打开方式,我们在来谈一谈二进制文件的输出方式和文本文件的输出方式。
|
||||
|
||||
① 文本形式输出到文件,我们完全可以在open函数的mode选项中调用
|
||||
ios::out|ios::app
|
||||
|
||||
好了,上面这句话说的就是将数据依次输出。注意,这里用的是依次,原因就是我们采用了app(append)模式,此表示在文件末尾继续写入文件,这就实现了数据的挨个写入 ^_^。一个完整的程序例子如下:
|
||||
01 #include <iostream>
|
||||
02 #include<fstream>
|
||||
03 using namespace std;
|
||||
04 const int num=20;
|
||||
05 struct people
|
||||
06 {
|
||||
07 char name[num];
|
||||
08 double weight;
|
||||
09 int tall;
|
||||
10 int age;
|
||||
11 char sex;
|
||||
12 };
|
||||
13 int main()
|
||||
14 {
|
||||
15 people pe={"李勇",78.5,181,25,'f'};
|
||||
16 ofstream fout("people.txt",ios::out|ios::app);
|
||||
17 fout<<pe.name<<" "<<pe.age<<" "<<pe.sex<<" "<<pe.tall<<" "<<pe.weight<<" "<<"\n";
|
||||
18 fout.close();
|
||||
19 ifstream fin("people.txt");
|
||||
20 char ch[255];
|
||||
21 fin.getline(ch,255-1,0);
|
||||
22 cout<<ch;
|
||||
23 fin.close();
|
||||
24 return 0;
|
||||
25 }
|
||||
|
||||
输出如下:
|
||||
{{./1.png}}
|
||||
我们可以看到,people.txt文件中的内容和命令行中的一样。
|
||||
|
||||
|
||||
|
||||
② 二进制形式输出到文件 为了能够让其用二进制方式输出文件,我们只需要把上面程序的第16行和17行换做
|
||||
1 ofstream fout("people.txt",ios::binary);
|
||||
2 fout.write((char*)&pe,sizeof pe);
|
||||
|
||||
程序的第1行中的标志binary用于开启二进制模式,第2行调用了__write函数__。该函数有两个参数,第一个是要写入数据的首地址,在这里是结构体pe的地址,而第二个参数是要写入的字符数目,这里我们用sizeof来计算pe的字符数。具体程序如下:
|
||||
01 #include <iostream>
|
||||
02 #include<fstream>
|
||||
03 using namespace std;
|
||||
04 const int num=20;
|
||||
05 struct people
|
||||
06 {
|
||||
07 char name[num];
|
||||
08 double weight;
|
||||
09 int tall;
|
||||
10 int age;
|
||||
11 char sex;
|
||||
12 };
|
||||
13 int main()
|
||||
14 {
|
||||
15 people pe={"李勇",78.5,181,25,'f'};
|
||||
16 ofstream fout("people.txt",ios::binary);
|
||||
17 __ fout.write((char*)&pe,sizeof pe);__
|
||||
18 fout.close();
|
||||
19 people pe1={"张玲",65.4,165,62,'m'};
|
||||
20 ifstream fin("people.txt",ios::binary);
|
||||
21 __ fin.read((char*)&pe1,sizeof pe1);__
|
||||
22 cout<<pe1.name<<" "<<pe1.age<<" "<<pe1.sex<<" "<<pe1.tall<<" "
|
||||
23 << pe1.weight <<" "<<"\n";
|
||||
24 fin.close();
|
||||
25 return 0;
|
||||
26 }
|
||||
|
||||
我们再来看看这个东东的输出,我们可以看到,以txt文档打开文件时候,会产生乱码。这就是因为txt文件是以文本方式打开的,所以我们看到的都是乱码。如下图:
|
||||
{{./2.png}}
|
||||
呼呼,以上就是我自认的自己不是很懂的C++关于如何操作文件的记录,到这里了~~全文完 ^_^
|
||||
分类: C++拾遗
|
||||
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 20 KiB |
182
Zim/Programme/C++/C++的发展,特点和源程序构成/C++对C语言的非面向对象特性扩充(3).txt
Normal file
@@ -0,0 +1,182 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T13:52:43+08:00
|
||||
|
||||
====== C++对C语言的非面向对象特性扩充(3) ======
|
||||
Created Saturday 06 August 2011
|
||||
|
||||
今天要讲的是C++作用__域运算符"::"__,强制类型转换的扩充,C++中相对于C中malloc和free函数的运算符new和delete,以及C++对C的一个重要扩充:引用(reference);这也是C++对C语言的非面向对象特性扩充系列的最后一节。
|
||||
|
||||
1.如果有两个同名变量,一个是全局的,一个是局部的,那么__局部的变量在其作用域拥有较高的优先权,全局变量则被屏蔽__。那如果我希望在局部变量的作用域里使用全局变量怎么办,这时就要用到::作用域运算符了。比如:
|
||||
|
||||
1 #include<iostream>
|
||||
2
|
||||
3 using namespace std;
|
||||
4
|
||||
5 int x;
|
||||
6
|
||||
7 int main()
|
||||
8
|
||||
9 {
|
||||
10
|
||||
11 int x;
|
||||
12
|
||||
13 x=50;
|
||||
14
|
||||
15 __::x=100;__
|
||||
16
|
||||
17 cout<<"局部变量x="<<x<<endl;
|
||||
18
|
||||
19 cout<<"全局变量x="<<::x<<endl;
|
||||
20
|
||||
21 return 0;
|
||||
22
|
||||
23 }
|
||||
|
||||
结果:
|
||||
|
||||
{{./1.jpg}}
|
||||
|
||||
2.在C语言中有强制类型的转换比如:int x=1;double y=(double)x;而__C++不但支持这种格式,还提供了一种类似于函数格式的转换__:int x=1;double y=double(x);
|
||||
__注意:__这其实是利用double类型的__构造函数__生成一个__无对像名的临时对像__double(x),然后调用double的__复制构造函数__生成一个新的double对像y;
|
||||
通过这种方式可以__使创建临时对像的方法与调用普通函数一样调用构造函数__。
|
||||
|
||||
3.C中的malloc和free函数被用于动态分配内存和释放动态分配的内存,而在C++里,不但保留了这两个函数,另外使用运算符new和delete来更好地进行内存的分配和释放。内存分配的基本形式:指针变量名=new 类型,如:int *x;x=new int;或char *chr;chr=new char;释放内存(delete 指针变量名):delete x;delete chr;虽然new和delete的功能和malloc和free相似,但是前者有几个优点:(1)new可以根据数据类型__自动计算所要分配的内存大小__,而malloc必须使用sizeof函数来计算所需要的字节;(2)new能够__自动返回正确类型的指针__,而malloc的返回值一律为void*,必须在程序中进行强制类型转换;
|
||||
|
||||
new可以为__数组动态分配内存空间__如:int *array=new int[10]或int *xyz=new int[8][9][10];释放时用__delete []__array和delete []xyz;另外new可以在给简单变量分配内存的同时初始化,比如int *x=new int(100);但不能对数据进行初始化;
|
||||
|
||||
有时候没有足够的内存满足分配要求,则有些编译系统将会返回空指针NULL,比如:
|
||||
|
||||
1 #include <iostream>
|
||||
2
|
||||
3 using namespace std;
|
||||
4 int main()
|
||||
5 {
|
||||
6 int *x;
|
||||
7 x=new int;
|
||||
8 if(!x)
|
||||
9 {
|
||||
10 cout<<"分配内存失败!"<<endl;
|
||||
11 return 1;
|
||||
12 }
|
||||
13 *x=10;
|
||||
14 cout<<*x;
|
||||
15 delete x;
|
||||
16 return 0;
|
||||
17 }
|
||||
|
||||
4.接下来详细地说一下C++的引用(reference),先解释一下,什么是引用?打个比方,一个人可能有三四个名字,但这三四个名字所做的事,其实就是那一个人所做的。引用就是给变量起了个别名罢了。它的格式:类型 &引用名=以定义的变量名;比如:
|
||||
|
||||
1 #include <iostream>
|
||||
2
|
||||
3 using namespace std;
|
||||
4 int main()
|
||||
5 {
|
||||
6 int x=100;
|
||||
7 int &y=x;
|
||||
8 x=50;
|
||||
9 cout<<"x="<<x<<endl;
|
||||
10 cout<<"y="<<y<<endl;
|
||||
11
|
||||
12 y=0;
|
||||
13 cout<<"x="<<x<<endl;
|
||||
14 cout<<"y="<<y<<endl;
|
||||
15
|
||||
16 return 0;
|
||||
17 }
|
||||
|
||||
结果:
|
||||
|
||||
实际上,引用与其所代表的变量共享同一个内存单元,系统部位引用另外分配存储空间,编译系统使引用和其代表的变量具有相同地址。
|
||||
|
||||
1 #include <iostream>
|
||||
2
|
||||
3 using namespace std;
|
||||
4 int main()
|
||||
5 {
|
||||
6 int x=100;
|
||||
7 int &y=x;
|
||||
8 x=50;
|
||||
9 cout<<"变量x的地址:"<<&x<<endl;
|
||||
10 cout<<"引用y的地址:"<<&y<<endl;
|
||||
11 return 0;
|
||||
12 }
|
||||
|
||||
结果:
|
||||
|
||||
发现其实引用就那么回事,但是也有几个注意点:(1)在声明引用时,必须立即对它进行初始化,不能声明完后在赋值:如int x=10;int &y;y=x;(2)引用的类型必须和给其赋值的变量的类型相同,不可以这样:int x;double &y=x;(3)为引用提供的值,可以是变量也可以是引用:int x=5;int &y=x;int &z=y;(4)引用在初始化后不能再被重新声明为另一个变量的引用:int x,y;int &z=x;z=&y;
|
||||
|
||||
其实引用主要的用途就在于作为函数的参数,回顾一下,以前在C中传递函数参数有两种情况,分别是"传值调用"和"传址调用",前者传递是单向的,后者则为双向,而引用作为函数参数传递,则是"传址调用",它和C中指针作为参数传递的效果是一致的,只不过它不用像指针一样,需要交间接引用运算符"*";举个例子,比较一下这两种方法:
|
||||
|
||||
1 #include <iostream>
|
||||
2
|
||||
3 using namespace std;
|
||||
4 void swap(int *x,int *y)
|
||||
5 {
|
||||
6 int temp;
|
||||
7 temp=*x;
|
||||
8 *x=*y;
|
||||
9 *y=temp;
|
||||
10 }
|
||||
11
|
||||
12 void swap(int &x,int &y)
|
||||
13 {
|
||||
14 int temp;
|
||||
15 temp=x;
|
||||
16 x=y;
|
||||
17 y=temp;
|
||||
18 }
|
||||
19 int main()
|
||||
20 {
|
||||
21 int i=10,j=5;
|
||||
22 cout<<"i="<<i<<" j="<<j<<endl;
|
||||
23 swap(&i,&j);
|
||||
24 cout<<"i="<<i<<" j="<<j<<endl;
|
||||
25 swap(i,j);
|
||||
26 cout<<"i="<<i<<" j="<<j<<endl;
|
||||
27 return 0;
|
||||
28 }
|
||||
|
||||
结果:
|
||||
|
||||
对于引用,还有点小小的细节要说一下:(1)不能建立引用数组,比如:int a[4]="abcd";int &araay[4]=a;(2)不能建立引用的引用,不能建立指向引用的指针,比如:int x=50;int &&y=x;int &z=x;int *p=z;(3)可以把引用的地址赋给指针;(4)可以用const对引用加以限定,不允许改变引用的值,比如:int x=10;const int &t=x;t=5,但是x=5却可以,此时x和t都等于5;(5)引用运算符和地址操作符虽然都是&,但是引用的话,只是在声明时才用,而其它场合使用&都是地址操作符!比如:int x=5;int &y=x;y=10;int *z=&y//&为地址操作符;cout<<&z//&为地址操作符;
|
||||
|
||||
最后,我们还是用一个例子来总结一下今天所讲的内容(开发工具:vs2010):
|
||||
|
||||
1 #include "stdafx.h"
|
||||
2 #include <iostream>
|
||||
3
|
||||
4 using namespace std;
|
||||
5
|
||||
6 int x=10;//全局变量
|
||||
7 void swap(int *x,int *y)//指针类型的参数
|
||||
8 {
|
||||
9 int temp;
|
||||
10 temp=*x;
|
||||
11 *x=*y;
|
||||
12 *y=temp;
|
||||
13 }
|
||||
14
|
||||
15 void swap(int &x,int &y)//带有引用类型的参数
|
||||
16 {
|
||||
17 int temp;
|
||||
18 temp=x;
|
||||
19 x=y;
|
||||
20 y=temp;
|
||||
21 }
|
||||
22 int main()
|
||||
23 {
|
||||
24 double *y=new double(5.55);//new动态分配内存空间,字节大小和double类型所占字节一样,并初始化值
|
||||
25 int x=double(*y);//强制类型转换
|
||||
26
|
||||
27 cout<<"局部变量x="<<x<<" 全局变量x="<<::x<<endl;
|
||||
28 swap(&x,&::x);
|
||||
29 cout<<"局部变量x="<<x<<" 全局变量x="<<::x<<endl;
|
||||
30 swap(x,::x);
|
||||
31 cout<<"局部变量x="<<x<<" 全局变量x="<<::x<<endl;
|
||||
32
|
||||
33 delete y;//释放内存空间
|
||||
34 return 0;
|
||||
35 }
|
||||
|
||||
结果:
|
||||
BIN
Zim/Programme/C++/C++的发展,特点和源程序构成/C++对C语言的非面向对象特性扩充(3)/1.jpg
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
Zim/Programme/C++/C++的发展,特点和源程序构成/C++对C语言的非面向对象特性扩充(3)/2.jpg
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
Zim/Programme/C++/C++的发展,特点和源程序构成/C++对C语言的非面向对象特性扩充(3)/3.jpg
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
Zim/Programme/C++/C++的发展,特点和源程序构成/C++对C语言的非面向对象特性扩充(3)/4.jpg
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
Zim/Programme/C++/C++的发展,特点和源程序构成/C++对C语言的非面向对象特性扩充(3)/5.jpg
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
112
Zim/Programme/C++/C++的发展,特点和源程序构成/C++对C语言的非面向对象特性扩充(1).txt
Normal file
@@ -0,0 +1,112 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T13:11:18+08:00
|
||||
|
||||
====== C++对C语言的非面向对象特性扩充(1) ======
|
||||
Created Saturday 06 August 2011
|
||||
|
||||
我将分3篇来介绍C++相对于C在非对象特性上的扩充,今天要讲的是C++在注释,输入输出,局部变量说明的扩充,以及const修饰符与C中的#define的比较。
|
||||
|
||||
1.C++注释除了包括原有C的块注释/*...*/,还提供了行注释//,另外要注意的是对于注释/*...*/的方式是不能嵌套的,比如/*C++/*C++*/C*/,这是不允许的。但是__嵌套注释//是可以的__。
|
||||
|
||||
2.我想大家对于C的输入Printf和输出Scanf函数应该是在熟悉不过了,在C++中除了可以正常使用这两个函数外,又增加了__标准输入流对象cin和标准输出流对象cout__。但使用cin和cout进行输入输出更安全,方便。我用VC++写了一下printf和scanf函数,可以正常运行,但会报出警告:
|
||||
|
||||
大概的意思是说__scanf函数不安全__,建议使用scanf_s代替,于是我用了scanf_s,结果警告就没有了!
|
||||
|
||||
再来说说cin对象,它在程序中用于代表__标准输入设备__,比如键盘,它必须与运算符>>一起运用,即cin>>,而运算符“>>”仍保留了C中“右移”的功能,作为输入功能时,用于从对象cin读取数值传送给右方变量,比如int x;cin>>x;也可以连续输入一连串数据比如cin>>a>>b>>c;它会按顺序提取数据,存入对应变量,数据之间可以用__空格、回车、TAB等空格符__分隔。
|
||||
|
||||
但是当输入的是字符串(即类型为char*)时,提取运算符“>>”是会跳过空白,读入后面的非空字符,直到遇到另一个空白字符为止,并在串尾放一个字符串结束标志__‘\0’__。因此输入字符串遇到空格时,就当作数据输入结束比如:
|
||||
|
||||
char* str; cin>>str;
|
||||
|
||||
假如从键盘上输入的是C++C++ CC!
|
||||
|
||||
则输入后str的值为C++C++,后面的CC!被忽略了。另一种情况是要检查输入数据与变量的匹配情况比如:
|
||||
|
||||
int x;float y;
|
||||
|
||||
cin>>x>>y;
|
||||
|
||||
假如从键盘上输入的是88.88 99.99
|
||||
|
||||
那么结果并非是预想的i=88 y=99.99,而是i=88 y=0.88,原因__系统是根据变量的类型来分隔输入的数据__,对于上述这种情况,系统把小数点前面的部分给了x,把剩下的0.88给了y;
|
||||
|
||||
对于cout对象就不详细说了,和cin差不多,要说的是像double a=3.1415926;cout<<"PI="<<a<<'\n';结果和C#里的字符串相加一样的概念。其中C++中有一个操作符__endl,它的作用与'\n'__一样;
|
||||
|
||||
3.简单说说C++局部变量,对于用过C#的人,不说也可以,举个例子:一个函数
|
||||
|
||||
func()
|
||||
|
||||
{
|
||||
|
||||
int x;
|
||||
|
||||
x=1;
|
||||
|
||||
int y;
|
||||
|
||||
y=2;
|
||||
|
||||
...
|
||||
|
||||
}
|
||||
|
||||
这个函数在C中是不允许的,在编译时出错,并终止编译,但在C++中是正确的,变量的声明可以和执行语句交替出现,只不过有效作用是有范围限制的,但无论怎么样都要符合“__先定义,再使用的原则__”;
|
||||
|
||||
4.做一下const修饰符和C中#define的比较,在C中,用#define来定义常量如:#define PI 3.14;程序在__编译预处理时__标识符PI被全部置换为3.14.在预编译后,程序中不再出现PI这个标识符,__PI不是变量,没有类型,不占存储单元__,且易出错;而C++则用Const来修饰常量;如:
|
||||
|
||||
const double PI=3.14或double const PI=3.14
|
||||
|
||||
两者相同,这时__PI有类型,占用存储单元,有地址__,可以用指针指向它;
|
||||
|
||||
const也可以与指针结合使用,看看这两条语句const char* str="c++"和char* const str="c++"有什么区别,一看好像没什么不同,其实他们的__意义完全不一样__,前一个str是指向字符串常量的普通指针变量,它不允许str指针所指的常量比如:str[2]=‘-';但是可允许改变它所指的地址,比如str="C--";对于第二种情况,则刚好相反,创建了一个__常指针__,即不能改变指针所指的地址,但可以改变数据,即str="C--"//不可以;str[2]='-';//可以。还有如果const定义的是一个整型常量,那么关键字int可省略;
|
||||
|
||||
5.最后用一段程序来联系总结一下今天的内容(开发工具vs2010,VC++控制台程序):
|
||||
|
||||
1 #include "stdafx.h"
|
||||
2 #include <iostream>
|
||||
3 **#define PI 3.14; **//C中定义常量
|
||||
4 using namespace std;
|
||||
5 /*C++在注释,输入输出,6 局部变量说明的扩充,以
|
||||
7 及const修饰符与C中的
|
||||
8 #define的比较*/
|
||||
9 int main()
|
||||
10 {
|
||||
11 int x;
|
||||
12 float y;
|
||||
13 //C和C++的输入输出的比较
|
||||
14 printf("scanf:请输入一个整数:");
|
||||
15 __scanf_s__("%d",&x);
|
||||
16 printf("prinf:所输入的整数:%d\n",x);
|
||||
17 cout<<"cin:请输入一个整数,一个浮点数:";
|
||||
18 cin>>x>>y;
|
||||
19 cout<<"cout:输入的整数:"<<x<<"浮点数:"<<y<<endl;
|
||||
20 //C++中cin的一些注意点
|
||||
21 __char *str=new char[20];//局部变量说明__
|
||||
22 cout<<"请输入一个字符串:";
|
||||
23 cin>>str;
|
||||
24 cout<<"所输入的字符串:"<<str<<endl;
|
||||
25
|
||||
26 cout<<"请输入一个整数,一个浮点数:";
|
||||
27 cin>>x>>y;
|
||||
28 cout<<"输入的整数:"<<x<<"浮点数:"<<y<<endl;
|
||||
29 //const的运用
|
||||
30__ const double pi=3.14;//或double const pi=3.14__
|
||||
31 cout<<"#define:PI"<<PI;
|
||||
32 cout<<"const:pi"<<pi<<endl;
|
||||
33
|
||||
34 //指向字符串常量的普通指针变量和常指针区别
|
||||
35 const char *chr_0="C++";//普通指针
|
||||
36 //chr_0[2]='_';不允许
|
||||
37 chr_0="C--";
|
||||
38 cout<<"普通指针:"<<chr_0<<endl;
|
||||
39
|
||||
40 char* __const chr_1__="C++";//常指针
|
||||
41 //chr_1="C--";//不允许
|
||||
42 chr_1[2]='-';
|
||||
43 cout<<"常指针:"<<chr_1<<endl;
|
||||
44 return 0;
|
||||
45 }
|
||||
|
||||
|
||||
|
||||
140
Zim/Programme/C++/C++的发展,特点和源程序构成/C++对C语言的非面向对象特性扩充(2).txt
Normal file
@@ -0,0 +1,140 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T13:28:28+08:00
|
||||
|
||||
====== C++对C语言的非面向对象特性扩充(2) ======
|
||||
Created Saturday 06 August 2011
|
||||
|
||||
上一篇随笔写了关于C++在注释,输入输出,局部变量说明的扩充,以及const修饰符与C中的#define的比较,也得到了几位学习C++朋友们的帮助讲解,十分感谢,我也希望欢迎有更多学习C++的朋友一起来讨论,这样大家都能共同进步。那么,今天这篇要讲的是C++在函数原型上和C的区别、内联函数、带有默认参数的函数以及函数的重载。
|
||||
|
||||
1.大家都熟悉在C中,如果函数调用的位置在函数定义之前,那么在函数调用之前要对函数原型声明或调用之前就把函数直接定义好了。比如:
|
||||
|
||||
#include<stdio.h>
|
||||
|
||||
int add(int x,int y);
|
||||
|
||||
int main()
|
||||
|
||||
{
|
||||
|
||||
int x,y,sum;
|
||||
|
||||
printf("请输入两个整数:\n");
|
||||
|
||||
scanf("%d,%d",&x,&y);
|
||||
|
||||
sum=add(x,y);
|
||||
|
||||
printf("x+y=%d",sum);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int add(int x,int y)
|
||||
|
||||
{
|
||||
|
||||
return x+y;
|
||||
|
||||
}
|
||||
|
||||
不过也可以采用简洁的方式来声明,如:int add(); add(); 都可以通过编译;__但是在C++里,如果函数定义在后,调用在前,那函数原型的声明必须是int add(int x,int y);函数名称,参数类型和个数,以及返回值都必须说明;__如果函数定义在前,调用在后则和C一样。以上这种形式在C++里也等同于int add(int ,int);如果在原型说明中没有指出返回类型C++__默认返回类型为int__,不需要返回值,就用void。另外标准C++要求的__main函数的返回值必须为int;__
|
||||
|
||||
2.内联函数就是在函数说明前冠以关键字"inline",当C++在编译时__使用函数体中的代码插入到要调用该函数的语句之处,同时用实参代替形参,以便在程序运行时不再进行函数调用。__比如:
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
inline int add(int a,int b)
|
||||
|
||||
{
|
||||
|
||||
return a+b;
|
||||
|
||||
}
|
||||
|
||||
int main()
|
||||
|
||||
{
|
||||
|
||||
int x,y,sum;
|
||||
|
||||
cin>>x>>y;
|
||||
|
||||
sum=add(x,y);
|
||||
|
||||
cout<<"x+y="<<sum<<endl;
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
在编译时,遇到函数啊add(x,y)时,用函数体代替add(x,y),同时实参代替形参,这样“sum=add(x,y)”被替换成“{
|
||||
|
||||
int a=x;int b=y;sum=a+b;}”;那么为什么要引入内联函数呢?__主要是为消除函数调用时的系统开销__,以提高系统的运行速度。在程序执行过程中调用函数,系统要将程序当前的一些状态信息存到栈中,同时转到函数的代码处去执行函数体的语句,这些参数保存和传递过程中需要时间和空间的开销,使得程序效率降低。但是并不是什么函数都可以定义为内联函数,一般情况下,只有规模很小而是用频繁的函数才定义为内联函数,这样可以大大提高运行速率。
|
||||
|
||||
3.一般情况下,实参的个数应该和形参的一样,但在C++中则不一定,方法是在__说明函数原型时,为一个或多个形参默指定认值__,以后调用此函数,如省略其中一实参,C++自动地以默认值作为相应参数的值。比如int add(int x=10,int y=10),那么我们在调用该函数时可以有三种写法:add(50,50)//结果为50+50;add(50)//结果为50+10;add()//结果10+10;这样使函数更加灵活。但要注意的是__默认参数必须是在参数列表的最右端__,int add(int x,int y=10,int z)这样是错误的,还有不允许某个参数省略后,再给其后的参数指定参数值。如果函数定义在函数调用之后,则函数调用之前需要函数声明,此时__必须在函数声明中给出默认值__,在函数定义时就不要给出默认值了(因为有的C++编译系统会给出"重复指定默认值"的错误信息);其实,给了也无妨,而且更明确。
|
||||
|
||||
4.函数的重载,对于这个我想学过C#的朋友一定在熟悉不过了,它意味着,只要函数参数的__类型__不同,或者参数的__个数__不同,或者两者兼而有之,两个或两个以上的的函数可以使用相同的函数名。尽管简单,但是我还是想说说它在C++里所要注意的几个问题:
|
||||
__1.函数返回值不再函数参数匹配检查之列__;
|
||||
2.函数重载与带默认参数的函数一起使用可能引起__二义性__比如:int fun(int x=0;int y=10){return x+y;}和int fun(int r){return r;}这时候我这样调用fun(10);
|
||||
3.如果函数调用给出的实参和形参类型不符,__C++会自定执行类型转换__,转换成功会继续执行,但是在这种情况下可能会出现不可识别的错误:int add(int x,int y)和long add(long,long),这时候我这样调用add(9.9,8.8);
|
||||
5.最后还是一样通过一个实例来总结一下今天的内容:
|
||||
|
||||
1 #include "stdafx.h"
|
||||
2 #include <iostream>
|
||||
3 using namespace std;
|
||||
4
|
||||
5 int add(int x,int y);//或int add(int,int)
|
||||
6
|
||||
7 inline int sub(int x,int y)//内联函数
|
||||
8 {
|
||||
9 return x-y;
|
||||
10 }
|
||||
11
|
||||
12 double mul(double x=10.0,double y=10.0);//带有默认参数的函数
|
||||
13
|
||||
14 float add(float x,float y)//函数重载
|
||||
15 {
|
||||
16 return x+y;
|
||||
17 }
|
||||
18
|
||||
19 int main()
|
||||
20 {
|
||||
21 int x,y,result;
|
||||
22 cout<<"请输入两个整数:";
|
||||
23 cin>>x>>y;
|
||||
24 result=add(x,y);
|
||||
25 cout<<"普通函数(加法):x+y="<<result<<endl;
|
||||
26
|
||||
27 cout<<"请输入两个整数:";
|
||||
28 cin>>x>>y;
|
||||
29 result=sub(x,y);
|
||||
30 cout<<"内联函数(减法):x-y="<<result<<endl;
|
||||
31
|
||||
32 double a,b,mul_result;
|
||||
33 cout<<"请输入两个双精度数:";
|
||||
34 cin>>a>>b;
|
||||
35 mul_result=mul(a,b);
|
||||
36 cout<<"带有默认参数的函数(乘法):a*b="<<mul_result<<endl;
|
||||
37
|
||||
38 float c,d,sum;
|
||||
39 cout<<"请输入两个单精度数:";
|
||||
40 cin>>c>>d;
|
||||
41 sum=add(c,d);
|
||||
42 cout<<"重载加法函数:c+d="<<sum<<endl;
|
||||
43
|
||||
44 return 0;
|
||||
45 }
|
||||
46
|
||||
47 int add(int x,int y)
|
||||
48 {
|
||||
49 return x+y;
|
||||
50 }
|
||||
51
|
||||
52 double mul(double x,double y)
|
||||
53 {
|
||||
54 return x*y;
|
||||
55 }
|
||||
574
Zim/Programme/C++/C++的发展,特点和源程序构成/C++类与对象的进一步讨论(1).txt
Normal file
@@ -0,0 +1,574 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-07T10:37:00+08:00
|
||||
|
||||
====== C++类与对象的进一步讨论(1) ======
|
||||
Created Sunday 07 August 2011
|
||||
|
||||
上一系列主要讲了C++中的类和对象的一些基础概念,但也是面向对象程序设计中重要的一部分。接下来,我将在上一系列的基础上对C++的类与对象做进一步的讨论。从而更加熟悉类和对象在编程中的应用和进一步理解其作用。那么今天的主要讲的__C++类与对象中的静态成员__,其中实例代码中会涉及到__对象数组与对象指针的应用__,还有就是讲一下C++中的友元,包括__友元类和友元函数__;
|
||||
|
||||
1.如果一个类有多个对象,那么每个对象分别有自己的数据成员,不同对象的数据成员互不相干,各自独立。但是有时候我们希望有一个或几个__数据成员被所有对象所共享__。于是C++就提出了__静态成员__的概念。静态成员包括静态数据成员和静态成员函数。也许有人会问,那我们把多个对象要共享的数据,声明为全局变量不就行了吗?的确是,共享的目的是达到了,但是使用全局变量会带来不安全性,且与面向对象的封装性特征相矛盾。
|
||||
|
||||
在类中静态数据成员用关键字static来说明。格式:static 数据类型 数据成员名;
|
||||
|
||||
1 #include "stdafx.h"
|
||||
2 #include<iostream>
|
||||
3 #include<string>
|
||||
4
|
||||
5 class Employee
|
||||
6 {
|
||||
7 private:
|
||||
8 std::string id;
|
||||
9 std::string name;
|
||||
10 double salary;
|
||||
11 __ static int count; //静态成员变量__
|
||||
12 public:
|
||||
13 Employee(std::string id,std::string name,double salary);
|
||||
14 void showEmployee();
|
||||
15 void showEmployeeCount();
|
||||
16
|
||||
17 };
|
||||
18
|
||||
19 Employee::Employee(std::string id,std::string name,double salary):id(id),name(name),salary(salary)
|
||||
20 {
|
||||
21 ++count;
|
||||
22 }
|
||||
23
|
||||
24 void Employee::showEmployee()
|
||||
25 {
|
||||
26 std::cout<<"编号:"<<this->id<<std::endl;
|
||||
27 std::cout<<"姓名:"<<this->name<<std::endl;
|
||||
28 std::cout<<"薪水:"<<this->salary<<std::endl;
|
||||
29 std::cout<<"--------------------------------"<<std::endl;
|
||||
30 }
|
||||
31
|
||||
32 void Employee::showEmployeeCount()
|
||||
33 {
|
||||
34 std::cout<<"雇员总数:"<<this->count<<std::endl;
|
||||
35 std::cout<<"********************************"<<std::endl;
|
||||
36 }
|
||||
37
|
||||
38 __int Employee::count=0; //对于静态数据成员,必须在类界面定义之外对其进行初始化。__
|
||||
39
|
||||
40 int main()
|
||||
41 {
|
||||
42
|
||||
43 Employee employee[3]={ //对象数组
|
||||
44 Employee("0001","aaa",6000),
|
||||
45 Employee("0002","bbb",7000),
|
||||
46 Employee("0003","ccc",10000)
|
||||
47 };
|
||||
48
|
||||
49 Employee *emp=employee; //给对象指针赋值(值为对象数组首地址)
|
||||
50 __// 注意:不能将数组名赋值给引用类型。__
|
||||
51 for(int i=0;i<3;i++)
|
||||
52 {
|
||||
53 employee[i].showEmployee();
|
||||
54 }
|
||||
55
|
||||
56 employee->showEmployeeCount();//输出雇员个数
|
||||
57
|
||||
58 for(int i=0;i<3;i++)
|
||||
59 {
|
||||
60 (emp++)->showEmployee(); //emp++对象指针加1,即指向下一个对象数组元素的地址
|
||||
61 ;
|
||||
62 }
|
||||
63
|
||||
64 emp->showEmployeeCount();//输出雇员个数
|
||||
65
|
||||
66 return 0;
|
||||
67 }
|
||||
|
||||
结果:
|
||||
|
||||
针对上述实例中,尽管静态数据成员只涉及到了雇员人数cout,但我补充几个注意点:
|
||||
(1)静态数据成员的初始化与普通成员初始化不同:__静态数据成员初始化应该是在类外单独进行,而且应在定义对象之前进行__,比如上述中
|
||||
int Employee::count=0//__前面不需要加static,当没有给其赋初值时,int Employee::count,那么系统将自动赋予初值0;__
|
||||
(2)__静态数据成员属于类__,而不像普通成员属于对象,因此可以用"类名::"访问静态数据成员;
|
||||
(3)__静态数据成员在对象定义之前就存在__,公有的静态数据成员可在对象定义之前被访问;
|
||||
|
||||
同样的,还是用static来说明成员函数为静态成员函数。__静态成员函数也属于类,它主要是用来处理静态数据成员__。
|
||||
格式: static 返回类型 静态成员函数名(参数表);我们把上述的例子稍加改动,在成员函数void showEmployeeCount()前加上static,再把该__函数实现部分里的this->去掉__。改后代码示例如下:
|
||||
|
||||
__静态成员函数一般不访问类中的非静态成员数据,如果确实需要,只能通过对象名、对象指针和对象引用访问对象的非静态成员。__比如:
|
||||
|
||||
1 #include "stdafx.h"
|
||||
2 #include<iostream>
|
||||
3 #include<string>
|
||||
4
|
||||
5 class Employee
|
||||
6 {
|
||||
7 private:
|
||||
8 std::string id;
|
||||
9 std::string name;
|
||||
10 double salary;
|
||||
11 **static int count; //静态成员变量**
|
||||
12 public:
|
||||
13 Employee(std::string id,std::string name,double salary);
|
||||
14 **static void showSalary(Employee &emp);**
|
||||
**15 static void showEmployeeCount();//静态成员函数**
|
||||
16
|
||||
17 };
|
||||
18
|
||||
19 Employee::Employee(std::string id,std::string name,double salary):id(id),name(name),salary(salary)
|
||||
20 {
|
||||
21 ++count;
|
||||
22 }
|
||||
23
|
||||
24 void Employee::showEmployeeCount()
|
||||
25 {
|
||||
26 std::cout<<"雇员总数:"<<count<<std::endl;//把this->count该为count
|
||||
27 std::cout<<"********************************"<<std::endl;
|
||||
28 }
|
||||
29
|
||||
30 void Employee::showSalary(Employee &emp)
|
||||
31 {
|
||||
32 std::cout<<"姓名:"<<__emp.name__<<std::endl;
|
||||
33 std::cout<<"薪水:"<<__emp.salary__<<std::endl;
|
||||
34 std::cout<<"--------------------------------"<<std::endl;
|
||||
35 }
|
||||
36
|
||||
37 int Employee::count=0;
|
||||
38
|
||||
39 int main()
|
||||
40 {
|
||||
41
|
||||
42 Employee employee[3]={ //对象数组
|
||||
43 Employee("0001","aaa",6000),
|
||||
44 Employee("0002","bbb",7000),
|
||||
45 Employee("0003","ccc",10000)
|
||||
46 };
|
||||
47
|
||||
48 Employee *emp=employee; //给对象指针赋值(值为对象数组首地址)
|
||||
49
|
||||
50 for(int i=0;i<3;i++)
|
||||
51 {
|
||||
52 __employee[i].showSalary(employee[i]);//或Employee::showSalary(employee[i]);__
|
||||
53
|
||||
54 }
|
||||
55
|
||||
56 employee->showEmployeeCount();//输出雇员个数
|
||||
57
|
||||
58 for(int i=0;i<3;i++)
|
||||
59 {
|
||||
60 (emp++)->showSalary(employee[i]);//或Employee::showSalary(employee[i]);
|
||||
61 }
|
||||
62
|
||||
63 emp->showEmployeeCount();//输出雇员个数
|
||||
64
|
||||
65 return 0;
|
||||
66 }
|
||||
|
||||
结果:
|
||||
|
||||
刚刚之前有说到,当我给成员函数void showEmployeeCount()前加上static,就需要把该函数实现部分里的this->去掉(不去掉,错误提示__"this"只能用于非静态成员函数内部__)。
|
||||
为什么呢?其实静态成员函数和非静态成员函数最重要的区别在于:__前者木有this指针而后者有__。因为静态成员函数是属于类的,而不是对象。那为什么属于类的就没有this指针呢?这个问题在上节C++之类与对象(3)中有具体说明;
|
||||
|
||||
===== 2. =====
|
||||
类体现了数据隐藏性和封装性,类的私有成员只能在类定义的范围内使用,也就是私有成员只能有成员函数来访问。那么在不放弃私有成员数据安全的情况下,__能使普通函数或类中的成员函数来访问封装于某一类中的私有,公有或保护信息呢,C++中用友元(friend)实现这个要求。__C++中的友元包括友元函数和友元类,而友元函数又分为友元非成员函数和友元成员函数;分别举两个实例来说明:
|
||||
|
||||
1 #include "stdafx.h"
|
||||
2 #include<iostream>
|
||||
3 #include<string>
|
||||
4
|
||||
5 class Employee
|
||||
6 {
|
||||
7 private:
|
||||
8 std::string id;
|
||||
9 std::string name;
|
||||
10 double salary;
|
||||
11 static int count; //静态成员变量
|
||||
12 public:
|
||||
13 Employee(std::string id,std::string name,double salary);
|
||||
14 __ friend void showEmployee(Employee &emp);//友元非成员函数__
|
||||
15 static void showEmployeeCount();//静态成员函数
|
||||
16
|
||||
17 };
|
||||
18
|
||||
19 Employee::Employee(std::string id,std::string name,double salary):id(id),name(name),salary(salary)
|
||||
20 {
|
||||
21 ++count;
|
||||
22 }
|
||||
23
|
||||
24 __void showEmployee(Employee &emp)__
|
||||
25 {
|
||||
26 std::cout<<"编号:"<<emp.id<<std::endl; //可访问私有成员变量id
|
||||
27 std::cout<<"姓名:"<<emp.name<<std::endl;//可访问私有成员变量name
|
||||
28 std::cout<<"薪水:"<<emp.salary<<std::endl;//可访问私有成员变量salary
|
||||
29 std::cout<<"--------------------------------"<<std::endl;
|
||||
30 }
|
||||
31
|
||||
32
|
||||
33 void Employee::showEmployeeCount()
|
||||
34 {
|
||||
35 std::cout<<"雇员总数:"<<count<<std::endl;//把this->count该为count
|
||||
36 std::cout<<"********************************"<<std::endl;
|
||||
37 }
|
||||
38
|
||||
39
|
||||
40 int Employee::count=0;
|
||||
41
|
||||
42 int main()
|
||||
43 {
|
||||
44
|
||||
45 Employee employee[3]={ //对象数组
|
||||
46 Employee("0001","aaa",6000),
|
||||
47 Employee("0002","bbb",7000),
|
||||
48 Employee("0003","ccc",10000)
|
||||
49 };
|
||||
50
|
||||
51 for(int i=0;i<3;i++)
|
||||
52 {
|
||||
53 showEmployee(employee[i]);//友元非成员函数调用
|
||||
54 }
|
||||
55
|
||||
56 employee->showEmployeeCount();//输出雇员个数
|
||||
57
|
||||
58 return 0;
|
||||
59 }
|
||||
|
||||
结果:
|
||||
|
||||
__在类中声明友元函数要在其函数名前加关键字friend__。友元函数的定义可以放在类内部,也可以定义在外部;如果我们把上述代码中的friend关键字去掉,那么对对象employee[i]的私有数据访问是非法的。该__友元函数为非成员函数__,那么在定义该函数时不用在前面加"类名::",同样的它也没有所谓的this指针。
|
||||
|
||||
|
||||
|
||||
===== 友元成员函数 =====
|
||||
:__友元成员函数不仅可以访问自己所在类对象中的私有,公有或保护成员,也可以访问friend声明语句所在的类对象中的所有成员,这样就实现了类与类之前的协作。__
|
||||
|
||||
1 #include "stdafx.h"
|
||||
2 #include<iostream>
|
||||
3 #include<string>
|
||||
4
|
||||
5 __class Salary;//对Salary类的提前引用声明__
|
||||
6 class Employee
|
||||
7 {
|
||||
8 private:
|
||||
9 std::string id;
|
||||
10 std::string name;
|
||||
11 double salary;
|
||||
12 static int count; //静态成员变量
|
||||
13 public:
|
||||
14 Employee(std::string id,std::string name);
|
||||
15 void showEmployee(Salary &sal);//成员函数
|
||||
16 static void showEmployeeCount();//静态成员函数
|
||||
17
|
||||
18 };
|
||||
19
|
||||
20
|
||||
21 Employee::Employee(std::string id,std::string name):id(id),name(name)
|
||||
22 {
|
||||
23 ++count;
|
||||
24 }
|
||||
25
|
||||
26
|
||||
27 void Employee::showEmployeeCount()
|
||||
28 {
|
||||
29 std::cout<<"雇员总数:"<<count<<std::endl;//把this->count该为count
|
||||
30 std::cout<<"********************************"<<std::endl;
|
||||
31 }
|
||||
32
|
||||
33 int Employee::count=0;
|
||||
34
|
||||
35 __class Salary__
|
||||
36 {
|
||||
37 private:
|
||||
38 double wage;//工资
|
||||
39 double bonus;//奖金
|
||||
40 double commission;//提成
|
||||
41 double allowance;//津贴
|
||||
42 double subsidy;//补贴
|
||||
43 public:
|
||||
44 Salary(double wage,double bonus,double commission,double allowance,double subsidy);
|
||||
45
|
||||
46 __friend void Employee::showEmployee(Salary &sal);//是类Salary的友元函数,也是Employee类的成员函数__
|
||||
47
|
||||
48 };
|
||||
49
|
||||
50 Salary::Salary(double wage,double bonus,double commission,double allowance,double subsidy):wage(wage),bonus(bonus),commission(commission),allowance(allowance),subsidy(subsidy)
|
||||
51 {
|
||||
52
|
||||
53 }
|
||||
54
|
||||
55__ void Employee::showEmployee(Salary &sal)__
|
||||
56 {
|
||||
57 std::cout<<"编号:"<<id<<std::endl; //可访问本类中的私有变量id,name
|
||||
58 std::cout<<"姓名:"<<name<<std::endl;
|
||||
59 std::cout<<"薪水:"<<std::endl;
|
||||
60 std::cout<<" 工资:"<<sal.wage<<std::endl;//可访问薪水类里私有成员变量wage,bonus等
|
||||
61 std::cout<<" 奖金:"<<sal.bonus<<std::endl;
|
||||
62 std::cout<<" 提成:"<<sal.commission<<std::endl;
|
||||
63 std::cout<<" 补贴:"<<sal.subsidy<<std::endl;
|
||||
64 std::cout<<" 津贴:"<<sal.allowance<<std::endl;
|
||||
65 salary=sal.allowance+sal.bonus+sal.commission+sal.subsidy+sal.wage;
|
||||
66 std::cout<<"薪水总数:"<<salary<<std::endl;
|
||||
67 std::cout<<"--------------------------------"<<std::endl;
|
||||
68 }
|
||||
69
|
||||
70 int main()
|
||||
71 {
|
||||
72
|
||||
73 Employee employee[3]={ //对象数组
|
||||
74 Employee("0001","aaa"),
|
||||
75 Employee("0002","bbb"),
|
||||
76 Employee("0003","ccc")
|
||||
77 };
|
||||
78
|
||||
79 Salary salary[3]={
|
||||
80 Salary(3000,3000,0,200,100),
|
||||
81 Salary(3000,4000,0,200,0),
|
||||
82 Salary(4000,6000,0,0,0),
|
||||
83 };
|
||||
84
|
||||
85 Employee *emp=employee; //给对象指针赋值(值为对象数组首地址)
|
||||
86
|
||||
87 for(int i=0;i<3;i++)
|
||||
88 {
|
||||
89 (emp++)->showEmployee(salary[i]);//友元成员函数调用
|
||||
90 }
|
||||
91
|
||||
92 emp->showEmployeeCount();//输出雇员个数
|
||||
93
|
||||
94 return 0;
|
||||
95 }
|
||||
|
||||
结果:
|
||||
|
||||
在实例代码中的第5行中,__Salary提前引用声明__,因为函数showEmployee(Salary &sal)中的参数要用到,而定义可以推迟;
|
||||
|
||||
除了友元函数,还有就是__友元类__了,如果能够很好地理解友元函数,那么友元类也不再话下,__如果一个类被说明为另一个类的友元类,那么这个类的所有成员函数都将成为另一个类的友元函数。__比如,我们把上述中的Salary的友元函数改为friend Employee;
|
||||
|
||||
1 #include "stdafx.h"
|
||||
2 #include<iostream>
|
||||
3 #include<string>
|
||||
4
|
||||
5 class Salary;//对Salary类的提前引用声明
|
||||
6
|
||||
7 class Employee
|
||||
8 {
|
||||
9 private:
|
||||
10 std::string id;
|
||||
11 std::string name;
|
||||
12 double salary;
|
||||
13 static int count; //静态成员变量
|
||||
14 public:
|
||||
15 Employee(std::string id,std::string name);
|
||||
16 __void showEmployee(Salary &sal);//成员函数__
|
||||
17 static void showEmployeeCount();//静态成员函数
|
||||
18
|
||||
19 };
|
||||
20
|
||||
21 Employee::Employee(std::string id,std::string name):id(id),name(name)
|
||||
22 {
|
||||
23 ++count;
|
||||
24 }
|
||||
25
|
||||
26 void Employee::showEmployeeCount()
|
||||
27 {
|
||||
28 std::cout<<"雇员总数:"<<count<<std::endl;//把this->count该为count
|
||||
29 std::cout<<"********************************"<<std::endl;
|
||||
30 }
|
||||
31
|
||||
32 int Employee::count=0;
|
||||
33
|
||||
34 class Salary
|
||||
35 {
|
||||
36 private:
|
||||
37 double wage;//工资
|
||||
38 double bonus;//奖金
|
||||
39 double commission;//提成
|
||||
40 double allowance;//津贴
|
||||
41 double subsidy;//补贴
|
||||
42 public:
|
||||
43 Salary(double wage,double bonus,double commission,double allowance,double subsidy);
|
||||
44 __ friend Employee;//Salary的友元类Employee__
|
||||
45 };
|
||||
46
|
||||
47 Salary::Salary(double wage,double bonus,double commission,double allowance,double subsidy):wage(wage),bonus(bonus),commission(commission),allowance(allowance),subsidy(subsidy)
|
||||
48 {
|
||||
49
|
||||
50 }
|
||||
51
|
||||
52 void Employee::showEmployee(Salary &sal)
|
||||
53 {
|
||||
54 std::cout<<"编号:"<<id<<std::endl; //可访问本类中的私有变量id,name
|
||||
55 std::cout<<"姓名:"<<name<<std::endl;
|
||||
56 std::cout<<"薪水:"<<std::endl;
|
||||
57 std::cout<<" 工资:"<<sal.wage<<std::endl;//可访问薪水类里私有成员变量wage,bonus等
|
||||
58 std::cout<<" 奖金:"<<sal.bonus<<std::endl;
|
||||
59 std::cout<<" 提成:"<<sal.commission<<std::endl;
|
||||
60 std::cout<<" 补贴:"<<sal.subsidy<<std::endl;
|
||||
61 std::cout<<" 津贴:"<<sal.allowance<<std::endl;
|
||||
62 salary=sal.allowance+sal.bonus+sal.commission+sal.subsidy+sal.wage;
|
||||
63 std::cout<<"薪水总数:"<<salary<<std::endl;
|
||||
64 std::cout<<"--------------------------------"<<std::endl;
|
||||
65 }
|
||||
66
|
||||
67 int main()
|
||||
68 {
|
||||
69
|
||||
70 Employee employee[3]={ //对象数组
|
||||
71 Employee("0001","aaa"),
|
||||
72 Employee("0002","bbb"),
|
||||
73 Employee("0003","ccc")
|
||||
74 };
|
||||
75
|
||||
76 Salary salary[3]={
|
||||
77 Salary(3000,3000,0,200,100),
|
||||
78 Salary(3000,4000,0,200,0),
|
||||
79 Salary(4000,6000,0,0,0),
|
||||
80 };
|
||||
81
|
||||
82 Employee *emp=employee; //给对象指针赋值(值为对象数组首地址)
|
||||
83
|
||||
84
|
||||
85 for(int i=0;i<3;i++)
|
||||
86 {
|
||||
87 (emp++)->showEmployee(salary[i]);//友元成员函数调用
|
||||
88 }
|
||||
89
|
||||
90 emp->showEmployeeCount();//输出雇员个数
|
||||
91
|
||||
92 return 0;
|
||||
93 }
|
||||
|
||||
结果和上一个实例一样,另外我在补充一点:__友元关系是单向的,不具有交换性,也不具有传递性__。比如类A为类B的友元类,类B为类C的友元类,并不代表类A为类C的友元类,是不是友元类,看其类A有没有在类C中声明;
|
||||
|
||||
友元的内容就说到这里,好像写得有点长了,那么我用书中的一个比喻来小结一下友元这东东,自己认为这了比喻很生动很形象:__私有的数据就像是一度不透明的封闭的墙,而友元就是在这堵墙上开了个小孔,外界可以通过这个小孔来窥视类中的秘密,友元是一扇通向私有成员的后门;__
|
||||
|
||||
3.最后还是一样用一个实例来总结一下今天的内容(开发工具:vs2010):
|
||||
View Code
|
||||
|
||||
1 #include "stdafx.h"
|
||||
2 #include<iostream>
|
||||
3 #include<string>
|
||||
4
|
||||
5 class Salary;//对Salary类的提前引用声明
|
||||
6
|
||||
7 class Employee
|
||||
8 {
|
||||
9 private:
|
||||
10 std::string id;
|
||||
11 std::string name;
|
||||
12 double salary;
|
||||
13 static int count; //静态成员变量
|
||||
14 public:
|
||||
15 Employee(std::string id,std::string name);
|
||||
16 Employee(std::string id);
|
||||
17 void showEmployeeID();
|
||||
18 void showEmployeeSalary(Salary &sal);//成员函数
|
||||
19 static void showEmployeeCount();//静态成员函数
|
||||
20 friend void showEmployee(Employee &emp);//友元非成员函数
|
||||
21 };
|
||||
22
|
||||
23
|
||||
24 Employee::Employee(std::string id):id(id)
|
||||
25 {
|
||||
26
|
||||
27 }
|
||||
28
|
||||
29 Employee::Employee(std::string id,std::string name):id(id),name(name)
|
||||
30 {
|
||||
31 ++count;
|
||||
32 }
|
||||
33
|
||||
34 void Employee::showEmployeeID()
|
||||
35 {
|
||||
36 std::cout<<"编号:"<<id<<std::endl;
|
||||
37 }
|
||||
38
|
||||
39 void showEmployee(Employee &emp)
|
||||
40 {
|
||||
41 std::cout<<"编号:"<<emp.id<<std::endl; //可访问私有成员变量id
|
||||
42 std::cout<<"姓名:"<<emp.name<<std::endl;//可访问私有成员变量name
|
||||
43 }
|
||||
44
|
||||
45 void Employee::showEmployeeCount()
|
||||
46 {
|
||||
47 std::cout<<"雇员总数:"<<count<<std::endl;//把this->count该为count
|
||||
48 std::cout<<"********************************"<<std::endl;
|
||||
49 }
|
||||
50
|
||||
51 int Employee::count=0;
|
||||
52
|
||||
53 class Salary
|
||||
54 {
|
||||
55 private:
|
||||
56 double wage;//工资
|
||||
57 double bonus;//奖金
|
||||
58 double commission;//提成
|
||||
59 double allowance;//津贴
|
||||
60 double subsidy;//补贴
|
||||
61 public:
|
||||
62 Salary(double wage,double bonus,double commission,double allowance,double subsidy);
|
||||
63
|
||||
64 //friend void Employee::showEmployee(Salary &sal);//是类Salary的友元函数,也是Employee类的成员函数
|
||||
65 friend Employee;//Salary的友元类Employee
|
||||
66
|
||||
67 };
|
||||
68
|
||||
69 Salary::Salary(double wage,double bonus,double commission,double allowance,double subsidy):wage(wage),bonus(bonus),commission(commission),allowance(allowance),subsidy(subsidy)
|
||||
70 {
|
||||
71
|
||||
72 }
|
||||
73
|
||||
74 void Employee::showEmployeeSalary(Salary &sal)
|
||||
75 {
|
||||
76 std::cout<<"编号:"<<id<<std::endl; //可访问本类中的私有变量id,name
|
||||
77 std::cout<<"姓名:"<<name<<std::endl;
|
||||
78 std::cout<<"薪水:"<<std::endl;
|
||||
79 std::cout<<" 工资:"<<sal.wage<<std::endl;//可访问薪水类里私有成员变量wage,bonus等
|
||||
80 std::cout<<" 奖金:"<<sal.bonus<<std::endl;
|
||||
81 std::cout<<" 提成:"<<sal.commission<<std::endl;
|
||||
82 std::cout<<" 补贴:"<<sal.subsidy<<std::endl;
|
||||
83 std::cout<<" 津贴:"<<sal.allowance<<std::endl;
|
||||
84 salary=sal.allowance+sal.bonus+sal.commission+sal.subsidy+sal.wage;
|
||||
85 std::cout<<"薪水总数:"<<salary<<std::endl;
|
||||
86 std::cout<<"--------------------------------"<<std::endl;
|
||||
87 }
|
||||
88
|
||||
89 int main()
|
||||
90 {
|
||||
91 Employee employee1[2]={"001","002"};//如果构造函数的参数只有一个,那么可以采用这种方式来对对象数组初始化
|
||||
92
|
||||
93 for(int i=0;i<2;i++)
|
||||
94 {
|
||||
95 employee1[i].showEmployeeID();
|
||||
96 }
|
||||
97
|
||||
98 std::cout<<"********************************"<<std::endl;
|
||||
99
|
||||
100
|
||||
101 Employee employee[3]={ //对象数组
|
||||
102 Employee("0001","aaa"),
|
||||
103 Employee("0002","bbb"),
|
||||
104 Employee("0003","ccc")
|
||||
105 };
|
||||
106
|
||||
107 Salary salary[3]={
|
||||
108 Salary(3000,3000,0,200,100),
|
||||
109 Salary(3000,4000,0,200,0),
|
||||
110 Salary(4000,6000,0,0,0),
|
||||
111 };
|
||||
112
|
||||
113 Employee *emp=employee; //给对象指针赋值(值为对象数组首地址)
|
||||
114
|
||||
115 for(int i=0;i<3;i++)
|
||||
116 {
|
||||
117 showEmployee(employee[i]);//友元非成员函数调用
|
||||
118 (emp++)->showEmployeeSalary(salary[i]);//emp++对象指针加1,即指向下一个对象数组元素的地址
|
||||
119 //友元成员函数调用
|
||||
120 //或employee[i].showEmployeeSalary(salary[i]);
|
||||
121 /*employee[i].showEmployeeSalary(salary[i]);*/
|
||||
122 }
|
||||
123
|
||||
124 emp->showEmployeeCount();//静态成员函数调用,输出雇员个数
|
||||
125 //或Employee::showEmployeeCount();
|
||||
126 //或employee->showEmployeeCount();
|
||||
127 //或employee[2].showEmployeeCount();
|
||||
128 return 0;
|
||||
129 }
|
||||
|
||||
结果:
|
||||
|
||||
|
||||
289
Zim/Programme/C++/C++的发展,特点和源程序构成/C++类与对象的进一步讨论(2).txt
Normal file
@@ -0,0 +1,289 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-07T11:05:03+08:00
|
||||
|
||||
====== C++类与对象的进一步讨论(2) ======
|
||||
Created Sunday 07 August 2011
|
||||
|
||||
我们经常会看到**一个类中可能会出现另一个类的对象作为它的数据成员**,既然是对象,那么就会涉及到这个对象成员要初始化的问题。而程序中各种数据的共享,在一定程度上破环了数据的安全性。C++中有什么方法可以保证数据共享又防止数据改动。另外除以上两个问题,我还将说说C++的**多文件程序**。
|
||||
|
||||
1.我们在创建类的对象时,如果这个类具有__内嵌的对象__,那么__该对象成员也将被自动创建__。所以创建对象时既**要对本类的基本数据成员初始化,又要对内嵌的对象初始化**。具体怎么做呢,我还是用Employee(雇员)和Salary(薪水)两个类来举个实例:
|
||||
|
||||
1 #include "stdafx.h"
|
||||
2 #include<string>
|
||||
3 #include<iostream>
|
||||
4
|
||||
5 class Employee
|
||||
6 {
|
||||
7 private:
|
||||
8 std::string id;
|
||||
9 std::string name;
|
||||
10 public:
|
||||
11 Employee(std::string id,std::string name);
|
||||
12 ~Employee();
|
||||
13 void showEmployee();
|
||||
14 };
|
||||
15
|
||||
16 Employee::Employee(std::string id,std::string name):id(id),name(name)
|
||||
17 {
|
||||
18 std::cout<<"构造对象employee"<<std::endl;
|
||||
19 }
|
||||
20
|
||||
21 Employee::~Employee()
|
||||
22 {
|
||||
23 std::cout<<"释放对象employee内存空间"<<std::endl;
|
||||
24 }
|
||||
25
|
||||
26 void Employee::showEmployee()
|
||||
27 {
|
||||
28 std::cout<<"编号:"<<id<<std::endl;
|
||||
29 std::cout<<"姓名:"<<name<<std::endl;
|
||||
30 }
|
||||
31
|
||||
32
|
||||
33 class Salary
|
||||
34 {
|
||||
35 private:
|
||||
36 __ Employee employee; //雇员__
|
||||
37 double wage;//工资
|
||||
38 double bonus;//奖金
|
||||
39 double commission;//提成
|
||||
40 double allowance;//津贴
|
||||
41 double subsidy;//补贴
|
||||
42 public:
|
||||
43 Salary(double wage,double bonus,double commission,double allowance,double subsidy,__std::string id,std::string name);__
|
||||
44 ~Salary();
|
||||
45 void showSalary();
|
||||
46 };
|
||||
47
|
||||
48 Salary::Salary(double wage,double bonus,double commission,double allowance,double subsidy,std::string id,std::string name):__wage(wage),bonus(bonus),commission(commission),allowance(allowance),subsidy(subsidy),employee(id,name)//初始化对象成员employee__
|
||||
49 {
|
||||
50 std::cout<<"构造对象salary"<<std::endl;
|
||||
51 }
|
||||
52
|
||||
53 Salary::~Salary()
|
||||
54 {
|
||||
55 std::cout<<"释放对象salary内存空间"<<std::endl;
|
||||
56 }
|
||||
57
|
||||
58 void Salary::showSalary()
|
||||
59 {
|
||||
60 employee.showEmployee();//显示雇员信息
|
||||
61 std::cout<<"薪水:"<<std::endl;
|
||||
62 std::cout<<" 工资:"<<wage<<std::endl;
|
||||
63 std::cout<<" 奖金:"<<bonus<<std::endl;
|
||||
64 std::cout<<" 提成:"<<commission<<std::endl;
|
||||
65 std::cout<<" 补贴:"<<subsidy<<std::endl;
|
||||
66 std::cout<<" 津贴:"<<allowance<<std::endl;
|
||||
67 }
|
||||
68
|
||||
69
|
||||
70 int main()
|
||||
71 {
|
||||
72 {
|
||||
73 Salary salary(3000,3000,0,200,100,"001","aaa");
|
||||
74 salary.showSalary();
|
||||
75 }
|
||||
76
|
||||
77 return 0;
|
||||
78 }
|
||||
|
||||
结果:
|
||||
|
||||
从初始化成员对象的代码中可以看到其实和之前所说的__成员初始化表对数据成员初始化的形式是一样的__。看到main函数体里的实现部分,可能有人会问那个大括号是否画蛇添足了。如果单单是从程序运行的流程考虑,的确没什么必要。而我特意加上这个大括号是为了说明在系统编译运行时它的顺序是怎么样的,从显示的结果来看,是先调用了Employee()的构造函数,在调用自己(Salary())的构造函数,而在调用各自的析构函数时则刚好相反。也许有人会问,那如果Salary里的对象成员不只一个,那么这些对象成员的构造函数调用的顺序是怎么样的?其实__系统编译运行时对对象成员的构造函数调用的顺序是根据其在类声明中的顺序来依次调用,而释放对象空间(析构函数)的过程则刚好相反;__
|
||||
|
||||
|
||||
|
||||
===== 2.C++的常类型的引入 =====
|
||||
,就是为了**既保证数据共享又防止数据被改动**。在面向对象里常__类型主要有常对象、常数据成员以及常成员函数。__
|
||||
(1)常对象的形式有:类名 const 对象名[参数表]或const 类名 对象名[参数表],__常对象中的数据成员值在对象的整个对象的生存期内不能被改变,而且常对象不能调用普通成员函数,只能调用常成员函数。__
|
||||
(2)常数据成员的形式其实和C++对C语言的非面向对象特性扩充(1)所讲到过的常量是一样,但要注意的是类__里的常数据成员只能通过初始化列表对其进行初始化,其他函数都不能对其赋值__;
|
||||
注意:__常数据成员和静态数据成员是不同的__,后者不是常类型而是生存期较长,可以对其赋值初始化和修改。
|
||||
(3)常成员函数的形式:__类型 函数名(参数表) const__,const是函数类型的__组成部分__,所以__在声明函数和定义函数时都要加关键字const__;同样我们以Employee(雇员)类为例:
|
||||
|
||||
1 #include "stdafx.h"
|
||||
2 #include<string>
|
||||
3 #include<iostream>
|
||||
4
|
||||
5 class Employee
|
||||
6 {
|
||||
7 private:
|
||||
8 const std::string id;//常数据成员
|
||||
9 const std::string name;//常数据成员
|
||||
10 public:
|
||||
11 Employee(std::string id,std::string name);
|
||||
12 ~Employee();
|
||||
13 void showEmployee();//普通成员函数
|
||||
14 __void showEmployee() const;//常成员函数__
|
||||
__// 注意:const是函数类型的一部分,故上面两个函数是重载的。__
|
||||
15 };
|
||||
16
|
||||
17 Employee::Employee(std::string id,std::string name):__id(id),name(name)__
|
||||
18 {
|
||||
19 //std::cout<<"构造对象employee"<<std::endl;
|
||||
20 }
|
||||
21
|
||||
22 Employee::~Employee()
|
||||
23 {
|
||||
24 //std::cout<<"释放对象employee内存空间"<<std::endl;
|
||||
25 }
|
||||
26
|
||||
27 void Employee::showEmployee()
|
||||
28 {
|
||||
29 std::cout<<"普通成员函数:"<<std::endl;
|
||||
30 std::cout<<"编号:"<<id<<std::endl;
|
||||
31 std::cout<<"姓名:"<<name<<std::endl;
|
||||
32 }
|
||||
33
|
||||
34 void Employee::showEmployee()__ const__
|
||||
35 {
|
||||
36 std::cout<<"常成员函数:"<<std::endl;
|
||||
37 std::cout<<"编号:"<<id<<std::endl;
|
||||
38 std::cout<<"姓名:"<<name<<std::endl;
|
||||
39 }
|
||||
40
|
||||
41 int main()
|
||||
42 {
|
||||
43 Employee employee("001","aa");
|
||||
44 employee.showEmployee();
|
||||
45
|
||||
46 std::cout<<"********************************"<<std::endl;
|
||||
47
|
||||
48 __ Employee const const_employee("002","bb");//常对象__
|
||||
49 const_employee.showEmployee();
|
||||
50
|
||||
51 return 0;
|
||||
52 }
|
||||
|
||||
结果:
|
||||
|
||||
从上述示例代码中,我可以看到两个同名函数void showEmployee(),一个是普通成员函数,一个是常成员函数,它们是重载的,由此说明__const可以被用于对重载函数的区分__;
|
||||
|
||||
|
||||
|
||||
===== 3.C++的源程序基本上由3个部分组成 =====
|
||||
:类的声明部分、类的实现部分和类的使用部分,针对3个部分,C++中对应分为3个文件:__类声明文件(*.h文件)、类的实现文件(*.cpp)和类的使用文件(*.cpp,主函数main文件)__。
|
||||
|
||||
那么为什么C++中的要使用多文件,一个*.cpp类的使用文件就可解决的问题,为何那么麻烦要分三步走呢?主要有以下几个方面:(1)类的实现文件通常会比较大,将类的声明和实现放在一起,不利于程序的阅读、管理和维护。我们通常说__接口应该和实现的部分分离__,这样可以更易于修改程序。
|
||||
(2)把类成员函数的实现放在声明文件中和单独放实现文件里,__在编译时是不一样的。前者是作为类的内联函数来处理的__。
|
||||
(3)对于软件的厂商来说,__它只需向用户提供程序公开的接口__,而不用公开程序源代码。从这一点来看,我觉得C++在面向对象方面比C#更加明确,清晰。
|
||||
|
||||
4.最后还是一样,我将用一个示例来总结一下今天所讲的内容,同时将把程序按照C++多文件的原则分成3个文件来实现(开发工具:vs2010):
|
||||
|
||||
**1.声明文件*.h:**
|
||||
|
||||
==== employee.h(雇员类的声明文件) ====
|
||||
|
||||
1 #include<string>
|
||||
2 #include<iostream>
|
||||
3
|
||||
4 class Employee
|
||||
5 {
|
||||
6 private:
|
||||
7 const std::string id;//常数据成员
|
||||
8 const std::string name;//常数据成员
|
||||
9 public:
|
||||
10 Employee(std::string id,std::string name);
|
||||
11 ~Employee();
|
||||
12 void showEmployee();//普通成员函数
|
||||
13 void showEmployee() const;//常成员函数
|
||||
14 };
|
||||
|
||||
==== salary.h(薪水类的声明文件) ====
|
||||
#include "employee.h"
|
||||
2
|
||||
3 class Salary
|
||||
4 {
|
||||
5 private:
|
||||
6 const Employee employee; //雇员
|
||||
7 const double wage;//工资(常数据成员)
|
||||
8 double bonus;//奖金
|
||||
9 double commission;//提成
|
||||
10 double allowance;//津贴
|
||||
11 double subsidy;//补贴
|
||||
12 public:
|
||||
13 Salary(double wage,double bonus,double commission,double allowance,double subsidy,std::string id,std::string name);
|
||||
14 ~Salary();
|
||||
15 void showSalary();
|
||||
16 };
|
||||
|
||||
**2.实现文件*.cpp:**
|
||||
|
||||
==== employee.cpp(雇员类的实现文件) ====
|
||||
1 #include "stdafx.h"
|
||||
2 #include<iostream>
|
||||
3
|
||||
4 #include "employee.h"
|
||||
5
|
||||
6 Employee::Employee(std::string id,std::string name):id(id),name(name)
|
||||
7 {
|
||||
8 std::cout<<"构造对象employee"<<std::endl;
|
||||
9 }
|
||||
10
|
||||
11 Employee::~Employee()
|
||||
12 {
|
||||
13 std::cout<<"释放对象employee内存空间"<<std::endl;
|
||||
14 }
|
||||
15
|
||||
16 void Employee::showEmployee()
|
||||
17 {
|
||||
18 std::cout<<"普通成员函数:"<<std::endl;
|
||||
19 std::cout<<"编号:"<<id<<std::endl;
|
||||
20 std::cout<<"姓名:"<<name<<std::endl;
|
||||
21 }
|
||||
22
|
||||
23 void Employee::showEmployee() const
|
||||
24 {
|
||||
25 std::cout<<"常成员函数:"<<std::endl;
|
||||
26 std::cout<<"编号:"<<id<<std::endl;
|
||||
27 std::cout<<"姓名:"<<name<<std::endl;
|
||||
28 }
|
||||
|
||||
==== salary.cpp(薪水类的实现文件) ====
|
||||
1 #include "stdafx.h"
|
||||
2 #include<iostream>
|
||||
3
|
||||
4 #include "salary.h"
|
||||
5
|
||||
6 Salary::Salary(double wage,double bonus,double commission,double allowance,double subsidy,std::string id,std::string name):wage(wage),bonus(bonus),commission(commission),allowance(allowance),subsidy(subsidy),employee(id,name)
|
||||
7 {
|
||||
8 std::cout<<"构造对象salary"<<std::endl;
|
||||
9 }
|
||||
10
|
||||
11 Salary::~Salary()
|
||||
12 {
|
||||
13 std::cout<<"释放对象salary内存空间"<<std::endl;
|
||||
14 }
|
||||
15
|
||||
16 void Salary::showSalary()
|
||||
17 {
|
||||
18 employee.showEmployee();//显示雇员信息
|
||||
19 std::cout<<"薪水:"<<std::endl;
|
||||
20 std::cout<<" 工资:"<<wage<<std::endl;
|
||||
21 std::cout<<" 奖金:"<<bonus<<std::endl;
|
||||
22 std::cout<<" 提成:"<<commission<<std::endl;
|
||||
23 std::cout<<" 补贴:"<<subsidy<<std::endl;
|
||||
24 std::cout<<" 津贴:"<<allowance<<std::endl;
|
||||
25 }
|
||||
|
||||
**3.使用文件*.cpp**
|
||||
|
||||
main主函数文件
|
||||
|
||||
1 #include "stdafx.h"
|
||||
2 #include<iostream>
|
||||
3
|
||||
4 #include "salary.h"
|
||||
5 int main()
|
||||
6 {
|
||||
7 {
|
||||
8 Salary salary(3000,3000,0,200,100,"001","aaa");
|
||||
9 salary.showSalary();
|
||||
10 }
|
||||
11
|
||||
12 return 0;
|
||||
13 }
|
||||
|
||||
结果:
|
||||
|
||||
|
||||
97
Zim/Programme/C++/C++类的“合成”机制.txt
Normal file
@@ -0,0 +1,97 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T10:51:22+08:00
|
||||
|
||||
====== C++类的“合成”机制 ======
|
||||
Created Saturday 06 August 2011
|
||||
|
||||
注意:这里的“包含”更严谨的说法是__“合成”__,这是C++的一种代码重用机制之一(另一种是__继承__)。
|
||||
|
||||
本人在学习Qt的时候发现了一个非常有趣的现象。有很多函数的调用方法都写成了如下的形式:
|
||||
object.func().func2();
|
||||
|
||||
这令小弟着实不懂。在上面这段代码中,第一个对象调用它的成员函数func()是完全没有问题的,但是后面那个func2()就奇怪了。我们只知道,点运算符(.)的作用就是调用对象的成员,但是如果按照上面这个程序的字面意思来理解,就是对象object调用它的成员函数func(),然后函数func()再调用它的成员函数func2()。这怎么能解释得通哩??我们只知道对象有成员函数,但是从来没有听说过函数也可以有成员函数的啊。没有办法,只有翻C++的工具书,最后,居然发现了这个原来就是C++中的“__包含”__思想。那么究竟何为包含呢,且听小弟慢慢叙来......^_^
|
||||
|
||||
何为“包含”,其实说白了就是一个类可以包含另一个类的对象。即如下程序所示:
|
||||
|
||||
01 class A
|
||||
02 {
|
||||
03 //...
|
||||
04
|
||||
05 };
|
||||
06
|
||||
07 class B
|
||||
08 {
|
||||
09 //...
|
||||
10 A a;
|
||||
11 A b;
|
||||
12 };
|
||||
|
||||
在上面这个程序中,我们定义了类A和类B。其中类B里面我们定义了类A的两个对象a和b。这样的情况就叫类B包含了类A。下面,我们用一个程序来看一下“包含”:
|
||||
01 #include <iostream>
|
||||
02 using namespace std;
|
||||
03 class A
|
||||
04 {
|
||||
05 public:
|
||||
06 A(int i){x=i;cout<<"调用A类的构造函数\n";} //注意:__没有定义类的复制构造函数。__
|
||||
07 ~A(){cout<<"调用A类的析构函数\n";};
|
||||
08 void get() {cout<<"A类中X的值为:"<<x<<endl;}
|
||||
09 private:
|
||||
10 int x;
|
||||
11 };
|
||||
12
|
||||
13 class B
|
||||
14 {
|
||||
15 public:
|
||||
16 __B(int i,int j,int k):a(i),b(j),y(k)__{cout<<"调用B类的构造函数\n";}
|
||||
17 A geta(){return a;}
|
||||
18 A getb(){return b;}
|
||||
19 ~B(){cout<<"调用B类的析构函数\n";}
|
||||
20 void gety(){cout<<"B类中y的值为:"<<y<<endl;}
|
||||
21 private:
|
||||
22 A a;
|
||||
23 A b;
|
||||
24 int y;
|
||||
25 };
|
||||
26
|
||||
27 int main()
|
||||
28 {
|
||||
29 B b(1,2,3);
|
||||
30 b.geta().get();
|
||||
31 b.getb().get();
|
||||
32 b.gety();
|
||||
33 return 0;
|
||||
34 }
|
||||
|
||||
首先是对两个类进行分析:在上面这个程序中,我们定义了两个类A和B。其中可以看到,在类B的私有成员变量里面,我们定义了一个类B自己的成员变量,另外还定义了两个类A的对象a和b(22行和23行);另外在类B的公有函数中,我们定义了两个返回值为类A的函数:geta()和getb(),它们的作用就是返回在类B中定义的两个类A的对象a和b。在这里我们特别应该注意的是类B的构造函数:
|
||||
|
||||
B(int i,int j,int k):a(i),b(j),y(k){cout<<"调用B类的构造函数\n";}
|
||||
|
||||
这个构造函数很有意思。我们可以__看到它不仅初始化了自己的私有成员变量y,而且也顺带初始化了类A的两个对象a和b__。那么它肯定会调用类A的构造函数,而且会调用两次。然后再调用一次B类自己的构造函数。那么析构的时候顺序应该就是相反的,首先调用B类的析构函数,然后再调用两次A类的析构函数。我们可以看到后面的程序输出图这样说滴,^_^。(见输出的红色框和黄色框)
|
||||
|
||||
现在我们再来看一看主函数中的东东。首先在程序的第29行,我们定义了B类的对象b,并调用了A类和B类的构造函数初始化了类A的对象a、b和类B的对象b。然后在程序的第30行我们就可以看到在博文一开始介绍的Qt中的东东。这里我们就搞不懂了,它们到底是干啥用的,什么都不说了,先看一下运行结果:
|
||||
{{./2011080121561481.png}}
|
||||
我们可以看到,返回的类A的对象a中x的值为1,另外一个类A的对象b中x的值为2。好了,豁然开朗了,我们来解释一下程序第30行和31行。第29行用类B的对象b来调用成员函数geta(),该函数是在17行定义的,作用就是返回类A的对象a,因此第30行
|
||||
b.geta().get();
|
||||
|
||||
就相当于
|
||||
A a(1);
|
||||
a.get();
|
||||
|
||||
这里的原因就是,因为b.geta()返回的是x的成员值为1的对象a,所以再调用类A的get()函数就可以**省略了对象**a了。那么同理,程序第31行就相当于
|
||||
A b(2);
|
||||
b.get();
|
||||
|
||||
这就说明了类B可以通过成员函数来访问被包含的类A对象的成员变量。就是文章一开始提到的方式,其实**它是隐藏了声明类A的对象**,因为由于包含的原因,类B已经帮类A搞定了对象的声明了~~~
|
||||
|
||||
__注意:__上面所谓的“隐藏”其实是:
|
||||
A geta(){return a;}
|
||||
这里面其实是返回一个A类型的对像,其状态属性是通过调用A类的__拷贝构造函数__(至于为何不是构造函数,是因为返回的a是一个A类型的__对像__而非构造函数要求的整型数)来获取的。但由于A类中没有定义其拷贝构造函数,因此编译器就使用类的默认构造函数即将已有对像的所有状态拷贝到新对像状态中。这中默认行为在通常情况下是可行的,但当类中有指针类型的成员属性时就有问题。因此,我们在定义一个类时最好定义一个拷贝构造函数。例如,在A类中加入:
|
||||
A(const A & a){ x = a.x; cout << "A copy construction." << endl; }
|
||||
则会在上图的“A 中的X值为1”前显示该构造函数输出的内容“A copy construction.”
|
||||
另外,注意上图中的,第3个和第4个“调用A中的析构函数”的输出,这其实是__b.geta()生成的临时对像析构的结果__。
|
||||
|
||||
另外这里其实可以讲B中的
|
||||
A geta(){ return a;}改为:
|
||||
A& geta(){ return a;} __const__
|
||||
这样 b.geta()返回的只是对像b中状态a的一个引用,而__不会__产生一个临时对像。但这样做其实是__有风险的__:即调用函数可以改变a的状态。
|
||||
BIN
Zim/Programme/C++/C++类的“合成”机制/2011080121561481.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
76
Zim/Programme/C++/C++默认参数与函数重载_注意事项.txt
Normal file
@@ -0,0 +1,76 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T13:44:25+08:00
|
||||
|
||||
====== C++默认参数与函数重载 注意事项 ======
|
||||
Created Saturday 06 August 2011
|
||||
|
||||
一、默认参数
|
||||
在C++中,可以为参数指定默认值。在函数调用时没有指定与形参相对应的实参时, 就自动使用默认参数。
|
||||
|
||||
默认参数的语法与使用:
|
||||
(1)在函数声明或定义时,直接对参数赋值。这就是默认参数;
|
||||
(2)在函数调用时,省略部分或全部参数。这时可以用默认参数来代替。
|
||||
|
||||
注意:
|
||||
(1)默认参数__只可在函数声明中设定一次。只有在没有函数声明时,才可以在函数定义中设定__。(#add ,此句意为存在函数声明和定义两部分的时候。验证表明有这个限制,可以随便,但出于规范__,在声明中指定__)
|
||||
(2)如果一个参数设定了缺省值时,其__右边的参数都要有缺省值__。(#add 这是定义时,类的成员函数的参数表在声明时默认参数位于参数表右部, 使用时该怎样待总结)
|
||||
如:int mal(int a, int b=3, int c=6, int d=8) 正确,按从右到左顺序设定默认值。
|
||||
int mal(int a=6, int b=3, int c=5, int d) 错误,未按照从右到左设定默认值。c设定缺省值了,而其右边的d没有缺省值。
|
||||
(3)默认参数调用时,则遵循参数调用顺序,**自左到右逐个调用**。这一点要与第(2)分清楚,不要混淆。(#add 神马意思啊? 暂理解为两个默认参数之间的参数必须赋缺省值, 错,意为调用函数时,从左至右第一个实参即为第一个形参的实参,依此类推)
|
||||
如:void mal(int a, int b=3, int c=5); //默认参数
|
||||
mal(3, 8, 9 ); //调用时有指定参数,则不使用默认参数
|
||||
mal(3, 5); //调用时只指定两个参数,按从左到右顺序调用,相当于mal(3,5,5);
|
||||
mal(3); //调用时只指定1个参数,按从左到右顺序调用,相当于mal(3,3,5);
|
||||
mal( ); //错误,因为a没有默认值
|
||||
mal(3, , 9) //错误,应按从左到右顺序__逐个__调用
|
||||
再如: void mal(int a=8, int b=3, int c=5); //默认参数
|
||||
mal( ); //正确,调用所有默认参数,相当于mal(8,3,5);
|
||||
|
||||
(4)默认值可以是全局变量、全局常量,甚至是一个函数。但__不可以是局部变量__。因为默认参数的调用是在__编译时__确定的,而局部变量位置与默认值在编译时无法确定。
|
||||
|
||||
|
||||
二、函数重载
|
||||
在相同的声明域中,函数名相同,而参数表不同。通过函数的参数表而唯一标识并且来区分函数的一种特殊的函数用法。
|
||||
|
||||
参数表的不同表现为:
|
||||
1、参数类型不同;
|
||||
2、参数个数不同;
|
||||
|
||||
特别注意:__返回类型不同不可以作为函数重载的标识__。
|
||||
|
||||
|
||||
函数重载的注意事项
|
||||
|
||||
1、函数的形参必须不同,或者个数不同,或者类型不同,不能够只依靠函数的返回值类型不同或形参变量名不同来实现函数重载。
|
||||
2、不要将不同功能的函数定义为重载函数,以免出现对调用结果的误解。如:
|
||||
int add(int x,int y)
|
||||
|
||||
{
|
||||
|
||||
return x+y;
|
||||
|
||||
}
|
||||
|
||||
|
||||
float add(float x,float y)
|
||||
|
||||
{
|
||||
|
||||
return x-y;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
重载函数与默认参数重叠导致的__二义性__问题:
|
||||
func(int); //重载函数1,只有1个参数,无默认参数
|
||||
func(int, int =4); //重载函数2,有2个参数,有1个默认参数
|
||||
func(int a=3, int b=4, int c=6); //重载函数3,有3个参数,有3个默认参数
|
||||
fucn(float a=3.0, float b=4.0 float c=5.0); //重载函数4,有3个参数,有3个默认参数
|
||||
fucn(float a=3.0, float b=4.0 float c=5.0 float d=7.9 ); //重载函数5,有4个参数,有4个默认参数
|
||||
|
||||
func(2); //可调用前3个函数,出现二义性
|
||||
func(2.0); //可调用后2个函数,出现二义性
|
||||
|
||||
所以当重载函数与默认参数共同使用时,要注意出现二义性问题。
|
||||
174
Zim/Programme/C++/C、C++枚举enum学习小记.txt
Normal file
@@ -0,0 +1,174 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-08T16:21:26+08:00
|
||||
|
||||
====== C、C++枚举enum学习小记 ======
|
||||
Created Monday 08 August 2011
|
||||
|
||||
参考文献:
|
||||
[1]C++程序设计语言(特别版), 裘宗燕译, 机械工业出版社
|
||||
[2]C++ Primer (3rd Ed.), S.B. Lippman and J. Lajoie, 人民邮电出版社
|
||||
|
||||
===== 1、枚举enum的用途浅例 =====
|
||||
写程序时,我们常常需要为**某个对象关联一组可选alternative属性**.例如,学生的成绩分A,B,C,D等,天气分sunny, cloudy, rainy等等。
|
||||
更常见的,打开一个文件可能有三种状态:input, output和append. 典型做法是,对应定义3个常数,即:
|
||||
const int input = 1;
|
||||
const int output = 2;
|
||||
const int append = 3;
|
||||
然后,调用以下函数:
|
||||
bool open_file(string file_name, int open_mode);
|
||||
比如,
|
||||
open_file("Phenix_and_the_Crane", append);
|
||||
这种做法比较简单,但存在许多缺点,主要的一点就是无法限制传递给open_file函数的第2个参数的取值范围,只要传递int类型的值都是合法的。(当然,这样的情况下的应对措施就是在open_file函数内部判断第二个参数的取值,只有在1,2,3范围内才处理。)
|
||||
使用枚举能在一定程度上减轻这种尴尬(注1),__它不但能实现类似于之前定义三个常量的功能,还能够将这三个值组合起来成为独一无二的组。__例如:
|
||||
enum open_modes {input = 1, output, append};
|
||||
以上定义了open_modes为枚举类型enumeration type。每一个命名了的枚举都是唯一的类型,是一个类型标示器type specifier。例如,我们可以重新写一个open_file函数:
|
||||
bool open_file(string file_name, open_modes om);
|
||||
在open_modes枚举中,input, output, append称为__枚举子enumerator, 它们限定了open_modes定义的对象的取值范围__。这个时候,调用open_file函数和之前的方法还是一模一样:
|
||||
open_file("Phenix_and_the_Crane", append);
|
||||
但是,如果传递给open_file的第二个参数不是open_modes枚举类型值的话(注1),那么编译器就会识别出错误;就算该参数取值等价于input, output, append中的某个,
|
||||
也一样会出错哦!例如:
|
||||
open_file("Phenix_and_the_Crane", 1);
|
||||
|
||||
===== 2、枚举的定义 =====
|
||||
__一个枚举是一个类型,可以保存一组由用户刻画的值__。定义之类,枚举的使用很像一个整数类型。
|
||||
枚举的定义具有以下形式,即以关键词enum开头,接着一个可选的枚举名,下来是由大括号{}包含着一个由逗号分隔的枚举子列表enumerators list:
|
||||
enum [enumeration name] {enumerator1[=value1], enumerator2[=value2], ...};
|
||||
|
||||
===== 3、枚举子的类型和取值 =====
|
||||
__枚举子的类型就是它所在的那个枚举__,例如前面说到的open_modes枚举中,input,output和append等枚举子的类型都是open_modes。这种做法,其实是为了赋予用户和编译器一些有关该变量拟议中的用途的提示。
|
||||
默认下,第一个枚举子被赋值0,接下来的枚举子取值是前面一个枚举子的取值+1,例如:
|
||||
enum weather {sunny, cloudy, rainy, windy};
|
||||
其中
|
||||
sunny == 0,
|
||||
cloudy == 1,
|
||||
rainy == 2,
|
||||
windy == 3;
|
||||
以上是默认情况,有时候我们希望显式地指定某个枚举子的值,那么会出现什么情况呢?看看:
|
||||
enum some_fruit {apple = 3, orange, banana = 4, bear};
|
||||
好了,apple == 3, banana == 4; 那么orange和bear呢?记得前面说过一句,默认下”接下来的枚举子取值是前面一个枚举子的取值+1“。既然这两个枚举子没有显式赋值,那么就按照默认规则办事,所以 orange == 4, bear == 5.
|
||||
从这个例子也可以看出,同一枚举中枚举子的取值不需要唯一。这样有什么用处呢?下面是个简单的例子:
|
||||
enum some_big_cities {
|
||||
Guangzhou = 4,
|
||||
Shenzhen = 4,
|
||||
Hongkong = 4,
|
||||
Shanghai = 2,
|
||||
Beijing = 3,
|
||||
Chongqi = 3
|
||||
};
|
||||
以上简单地按区域,将五个城市按照华南(4),华东(2), 华北(3)的几个城市分类了。
|
||||
|
||||
===== 4、枚举变量的定义、初始化和赋值 =====
|
||||
既然每个枚举都是一个类型,那么由这个类型自然可以声明变量,例如,由前面定义的some_big_cities:
|
||||
some_big_cities where_I_am;
|
||||
需要注意的是,在声明where_I_am时__没有初始化__,如果这时打印where_I_am的值:
|
||||
enum some_big_cities {
|
||||
Guangzhou = 4,
|
||||
Shenzhen = 4,
|
||||
Hongkong = 4,
|
||||
Shanghai = 2,
|
||||
Beijing = 3,
|
||||
Chongqi = 5};
|
||||
int main(void)
|
||||
{
|
||||
some_big_cities wh;
|
||||
cout<<"the value is: "<<wh<<endl;
|
||||
return 0;
|
||||
}
|
||||
输出将是the value is: 1. 然而,如果声明wh为全局变量,则另一种情况:
|
||||
enum some_big_cities {Guangzhou = 1 Shenzhen = 1, Hongkong = 1,
|
||||
Shanghai = 2, Beijing = 3, Chongqi = 5};
|
||||
some_big_cities wh;
|
||||
int main(void)
|
||||
{
|
||||
cout<<"the value is: "<<wh<<endl;
|
||||
return 0;
|
||||
}
|
||||
输出将是the value is: 0;
|
||||
以上结果是在Visual C++ 2005 Express中得到,不知道其它编译器情况如何,也不知为什么得到这样的结果。下来再找找资料。
|
||||
__定义一个枚举变量时,可以给它初始化__,例如:
|
||||
some_big_cities wh = Guangzhou;
|
||||
注意等号右边__只能取枚举子中的某一个__;特别地,以Guangzhou为例,虽然Guangzhou==4, 但以下初始化是出错的:
|
||||
some_big_cities wh = 4;
|
||||
Visual C++ 2005编译器提示:
|
||||
error C2440: 'initializing' : cannot convert from 'int' to 'some_big_cities'
|
||||
可见,__不能直接地把一个整型赋值给一个枚举变量__,__因为枚举和整型是不同类型的,除非显式转换__。关于枚举与整型的关系,后面再讲。
|
||||
除了初始化,枚举变量也有赋值运算:
|
||||
some_big_cities wh;
|
||||
wh = Guangzhou;
|
||||
wh = Shanghai;
|
||||
或者
|
||||
some_big_cities wh1 = Guangzhou;
|
||||
some_big_cities wh2 = Shanghai;
|
||||
wh2 = wh1;
|
||||
|
||||
===== 5、枚举的取值范围 =====
|
||||
如果某个枚举中所有枚举子的值均非负,该枚举的表示范围就是__[0:2^k-1]__,其中2^k是能使所有枚举子都位于此范围内的最小的2的幂;如果存在负的枚举值,该枚举的取值范围就是[-2^k,2^k-1].例如:
|
||||
enum e1 {dark, light}; //范围0:1
|
||||
enum e3 {min = -10, max = 1000}; //范围-1024:1023
|
||||
|
||||
===== 6、枚举与整型的关系 =====
|
||||
整型值只能__显式__地转换成一个枚举值,但是,如果转换的结果位于该枚举取值范围之外,则结果是无定义的。
|
||||
enum e1 {dark = 1, light = 10};
|
||||
e1 VAR1 = e1(50); //无定义
|
||||
e1 VAR2 = e1(3); //编译通过
|
||||
在这里也说明了__不允许隐式(编译器不会自动进行这种类型转换)__地从整型转换到枚举的原因,因为大部分整型值在特定的枚举里没有对应的表示。
|
||||
至于枚举可以当作特定的整型数来用的例子,从open_modes可以体会。
|
||||
|
||||
===== 7、自定义运算符 =====
|
||||
枚举是用户自定义类型,所以在用户可以为它定义自身的操作,例如++或者<<等。但是,在没有定义之前,不能因为枚举像整型就可以默认使用,例如:
|
||||
enum SomeCities
|
||||
{
|
||||
zhanjiang,
|
||||
Maoming,
|
||||
Yangjiang,
|
||||
Jiangmen,
|
||||
Zhongshan
|
||||
};
|
||||
SomeCities oneCity;
|
||||
for (oneCity = zhanjiang; oneCity != Zhongshan; ++oneCity)
|
||||
{
|
||||
cout<<oneCity<<endl;
|
||||
}
|
||||
|
||||
以上的++OneCity是没有定义的,在Visual C++ 6 编译下得到如下错误:
|
||||
error C2675: unary '++' : 'enum main::SomeCities' does not define this operator or a conversion to a type acceptable to the predefined operator
|
||||
|
||||
===== 8、Sizeof =====
|
||||
一个枚举类型的sizeof就是某个能够容纳其范围的整型的sizeof, 而且不会大于sizeof(int), 除非某个枚举子的值不能用int或者unsigned int来表示。
|
||||
在32位机器中,sizeof(int)一般等于4。前面介绍的所有枚举,例如,
|
||||
enum SomeCities
|
||||
{
|
||||
zhanjiang,
|
||||
Maoming,
|
||||
Yangjiang,
|
||||
Jiangmen,
|
||||
Zhongshan
|
||||
};
|
||||
|
||||
计算其sizeof, 可能是1,也可能是是4。在我的intel E2160双核、32位机器中,得到4。
|
||||
-----------------------------------------------------------------------------------
|
||||
[注1, Begin]
|
||||
由于通过将整型数显式转换就可能得到对应枚举类型的值,所以声明一个枚举来达到限制传递给函数的参数取值范围还是力不从心的,以下是一个例子:
|
||||
enum SomeCities
|
||||
{
|
||||
zhanjiang=1, //1
|
||||
Maoming, //2
|
||||
Yangjiang, //3
|
||||
Jiangmen, //4
|
||||
Zhongshan = 1000 //1000
|
||||
};
|
||||
void printEnum(SomeCities sc)
|
||||
{
|
||||
cout<<sc<<endl;
|
||||
}
|
||||
int main(void)
|
||||
{
|
||||
SomeCities oneCity = SomeCities(50); //__将50通过显式转换__,为oneCity赋值
|
||||
printEnum(oneCity); //在VC++ 6 编译器下得到50输出
|
||||
return 0;
|
||||
}
|
||||
|
||||
以上例子说明,虽然SomeCities的定义里没有赋值为50的枚举值,但是,由于50在该枚举的取值范围内,所以通过显式声明得到一个有定义的枚举值,从而成功传递给printEnum函数。
|
||||
[注1, End]
|
||||
|
||||
69
Zim/Programme/C++/POD对象.txt
Normal file
@@ -0,0 +1,69 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-16T21:36:19+08:00
|
||||
|
||||
====== POD对象 ======
|
||||
Created Thursday 16 February 2012
|
||||
|
||||
http://blog.csdn.net/amossavez/article/details/4388756
|
||||
|
||||
在C++中,我们__把传统的C风格的struct叫做POD(Plain Old Data)对象__。一般来说,POD对象应该满足如下特性。
|
||||
|
||||
* 对于POD类型T的对象,不管这个对象是否拥有类型T的有效值,如果将该对象的底层字节序列复制到一个字符数组(或者无符号字符数组)中,再将其复制回对象,那么该对象的值与原始值一样。
|
||||
* 对于任意的POD类型T,如果两个T指针分别指向两个不同的对象obj1和obj2,如果用memcpy库函数把obj1的值复制到obj2,那么obj2将拥有与obj1相同的值。
|
||||
|
||||
简言之,__针对POD对象,其二进制内容是可以随便复制的__,在任何地方,只要其二进制内容在,就能还原出正确无误的POD对象。对于任何POD对象,都可以使用memset()函数或者其他类似的内存初始化函数。
|
||||
|
||||
===== 现在动手 =====
|
||||
|
||||
为了更好地理解POD对象的含义,我们体验一下如何采用memxxx()函数对POD对象进行存储与还原。
|
||||
|
||||
编写Win32控制台程序,主程序如下:
|
||||
|
||||
【程序】使用memxxx函数操作POD对象
|
||||
|
||||
[cpp] view plaincopy
|
||||
|
||||
01 #include "stdafx.h"
|
||||
02 #include <cstring>
|
||||
03
|
||||
04 //PERSON为POD
|
||||
05 struct PERSON
|
||||
06 {
|
||||
07 char _name[16];
|
||||
08 int _age;
|
||||
09 bool _gender;
|
||||
10 };
|
||||
11
|
||||
12 void print(PERSON * p)
|
||||
13 {
|
||||
14 printf("%s,%d,%s/r/n", p->_name, p->_age, (p->_gender ? "男" : "女"));
|
||||
15 }
|
||||
16
|
||||
17 int main()
|
||||
18 {
|
||||
19 //POD对象可以使用初始化列表
|
||||
20 PERSON p1 = {"佟湘玉", 28, false};
|
||||
21 PERSON p3 = {"白展堂", 26, true};
|
||||
22 print(&p1);
|
||||
23 print(&p3);
|
||||
24
|
||||
25 //将p1转储为char数组
|
||||
26 char bytes[sizeof(PERSON)];
|
||||
27 memcpy(bytes, &p1, sizeof(PERSON));
|
||||
28
|
||||
29 PERSON p2;
|
||||
30 memset(&p2, 0, sizeof(PERSON));
|
||||
31 print(&p2);
|
||||
32
|
||||
33 //将char数组还原为p2
|
||||
34 memcpy(&p2, bytes, sizeof(PERSON));
|
||||
35 print(&p2);
|
||||
36
|
||||
37 //将p3复制至p2
|
||||
38 memcpy(&p2, &p3, sizeof(PERSON));
|
||||
39 print(&p2);
|
||||
40
|
||||
41 return 0;42 }
|
||||
|
||||
因此,对于POD对象,我们完全可以大胆地使用memxxx函数进行操作,从而完成对对象复制、赋值的目的。但是注意,__对于多态类的对象,要慎重考虑使用memset__,因为它会同时修改vtable指针!vtable指针是多态的根本所在,弄乱了对象的虚表指针,很有可能会酿成大错。
|
||||
90
Zim/Programme/C++/Pimpl机制.txt
Normal file
@@ -0,0 +1,90 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-15T22:07:30+08:00
|
||||
|
||||
====== Pimpl机制 ======
|
||||
Created Wednesday 15 February 2012
|
||||
http://developer.51cto.com/art/201106/267942.htm
|
||||
|
||||
pImpl惯用手法的运用方式大家都很清楚,其主要作用是解开类的使用接口和实现的耦合。本文从Pimpl机制分析开始讲起,一起来看。
|
||||
|
||||
AD:
|
||||
|
||||
Pimpl机制是Private Implementation的缩写,我们常常听到诸如“不要改动你的公有接口”这样的建议,所以我们一般都会修改私有接口,但是这会导致包含该头文件的所有源文件都要重新编译,这会是个麻烦事儿。Pimpl机制,顾名思义,将实现私有化,力图使得头文件对改变不透明。
|
||||
|
||||
机制分析
|
||||
|
||||
首先,我们先看看不使用这个机制的一个实现:
|
||||
|
||||
// MyBase.h
|
||||
class MyBase {
|
||||
public:
|
||||
int foo();
|
||||
};
|
||||
// MyDerived.h
|
||||
#include "MyBase.h"
|
||||
class MyDerived : public MyBase {
|
||||
public:
|
||||
int bar();
|
||||
};
|
||||
|
||||
假设你现在希望在MyBase.h中加入一个新的private和protected成员函数,那么MyDerived和所有包含MyBase.h的源文件都需要重新编译。在一个大工程中,这样的修改可能导致重新编译时间的激增。你可以使用Doxygen或者SciTools看看头文件依赖。
|
||||
|
||||
一般来说,不在头文件中包含头文件是一个比较好的习惯,但是这也不能完全消除修改MyBase.h带来的重新编译代价。有没有一个机制可以使得对私有接口做修改时我们可以减小重新编译的代价。
|
||||
|
||||
在Pimpl机制中,我们使用前置声明一个Impl类,并将这个类的一个指针实例放入主类中,如下:
|
||||
|
||||
// MyClass.h
|
||||
class MyClassImpl; // forward declaration
|
||||
class MyClass {
|
||||
public:
|
||||
MyClass();
|
||||
~MyClass();
|
||||
int foo();
|
||||
private:
|
||||
MyClassImpl *m_pImpl;
|
||||
};
|
||||
|
||||
现在,除非我们修改MyClass的公有接口,否则这个头文件是不会被修改了。然后,我们用这个Impl类的实现来完成主类的细节实现,在主类的构造函数中,我们完成了实现类指针的实例化:
|
||||
|
||||
// MyClass.cpp
|
||||
class MyClassImpl {
|
||||
public:
|
||||
int foo() {
|
||||
return bar();
|
||||
}
|
||||
int bar() { return var++; }
|
||||
int var;
|
||||
};
|
||||
MyClass::MyClass() : m_pImpl(new MyClassImpl){}
|
||||
MyClass::~MyClass()
|
||||
{
|
||||
try {
|
||||
delete m_pImpl;
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
int MyClass::foo(){ return m_pImpl->foo(); }
|
||||
|
||||
Pimpl机制其实这是桥接模式的一种变种。我们可以对实现类随意的进行增删和修改,而不会导致包含MyClass.h的源代码重新编译。当然,这样做的时间开销和空间开销也是有的。
|
||||
|
||||
在实践中,我们常常采用内部类来完成Pimpl机制:
|
||||
|
||||
// header
|
||||
class fruit
|
||||
{
|
||||
public:
|
||||
private:
|
||||
class impl;
|
||||
impl* pimpl_;
|
||||
}
|
||||
// implementation
|
||||
class fruit::impl
|
||||
{
|
||||
};
|
||||
fruit::fruit()
|
||||
{
|
||||
pimpl_ = new impl();
|
||||
}
|
||||
|
||||
希望看后本文,你会有收获。
|
||||
213
Zim/Programme/C++/Refer_VS_Pointer.txt
Normal file
@@ -0,0 +1,213 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-04-10T21:00:30+08:00
|
||||
|
||||
====== Refer VS Pointer ======
|
||||
Created Sunday 10 April 2011
|
||||
http://www.eetimes.com/discussion/programming-pointers/4023307/References-vs-Pointers
|
||||
References vs. Pointers
|
||||
Dan Saks
|
||||
3/15/2001 1:04 PM EST
|
||||
References vs. Pointers
|
||||
Knowing how references really differ from pointers should help you decide when to use references and when to stick with pointers.
|
||||
|
||||
In C++, references provide many of the same capabilities as pointers. Although most C++ programmers seem to develop some intuition about when to use references instead of pointers, and vice versa, they still encounter situations where the choice isn't so clear. If you'd like to develop a reasonably consistent philosophy about using references, it really helps to know exactly how references differ from pointers. Those differences are my subject this month.
|
||||
|
||||
More than skin deep
|
||||
|
||||
A reference, like a pointer, is an object that you can use to refer indirectly to another object. A reference declaration has essentially the same syntactic structure as a pointer declaration. The difference is that while a pointer declaration uses the * operator, a reference declaration uses the & operator. For example, given:
|
||||
|
||||
int i = 3;
|
||||
|
||||
then:
|
||||
|
||||
int *pi = &i;
|
||||
|
||||
declares pi as an object of type "pointer to int" whose initial value is the address of object i. On the other hand:
|
||||
|
||||
int &ri = i;
|
||||
|
||||
declares ri as an object of type "reference to int" referring to i. The difference between pointer and reference declarations is noteworthy, but it's not the basis for deciding to use one over the other. The real basis for the choice is the difference in appearance between references and pointers when you use them in expressions.
|
||||
|
||||
The big difference between pointers and references is that you must use an explicit operator-the * operator-to dereference a pointer, but you don't use an operator to dereference a reference. For example, once the previous declarations are in place, the indirection expression *pi derefences pi to refer to i. In contrast, the expression ri-without any operators at all-dereferences ri to refer to i. Thus, an assignment such as: *p = 4;
|
||||
|
||||
changes the value of i to 4, as does the assignment:
|
||||
|
||||
ri = 4;
|
||||
|
||||
This difference in appearance is significant when you're choosing between pointers and references for function parameter types and return types. This is especially true in functions that declare overloaded operators.
|
||||
|
||||
In my first column on references, I illustrated this point with the example of defining a ++ operator for an enumeration type.1 In C++, the built-in ++ operator does not apply to enumerations. For example, given just:
|
||||
|
||||
enum day
|
||||
{
|
||||
Sunday, Monday, ...
|
||||
};
|
||||
|
||||
day x;
|
||||
|
||||
the expression ++x does not compile. If you want it to, you must define a function named operator++ which accepts a day as an argument.
|
||||
|
||||
Invoking ++x should change the value in x. Therefore, declaring operator++ with a parameter of type day, as in:
|
||||
|
||||
day operator++(day d);
|
||||
|
||||
won't have the desired effect. This function passes its argument by value, which means the function sees a copy of the argument, not the argument itself. For the function to alter the value of its operand, it must pass that operand either by a pointer or by a reference.
|
||||
|
||||
Passing the argument by a pointer, as in:
|
||||
|
||||
day *operator++(day *d);
|
||||
|
||||
would let the function alter the value of the day by storing the incremented value into *d. However, you would then invoke the operator using an expression such as ++&x, which doesn't look right.
|
||||
|
||||
The proper way to define operator++ is with a reference as the parameter type, as in:
|
||||
|
||||
day &operator++(day &d)
|
||||
{
|
||||
d = (day)(d + 1);
|
||||
return d;
|
||||
}
|
||||
|
||||
Using this function, expressions such as ++x have the proper appearance as well as the proper behavior.
|
||||
|
||||
Passing by reference is not just the better way to write operator++, it's the only way. C++ doesn't really give you a choice here. A declaration such as:
|
||||
|
||||
day *operator++(day *d);
|
||||
|
||||
does not compile. Every overloaded operator function must either be a member of a class, or have a parameter of type T, T &, or T const &, where T is a class or enumeration type. In other words, every overloaded operator must accept an argument of a class or enumeration type. A pointer, even to an object of class or enumeration type, doesn't count. C++ does not let you overload operators that redefine the meaning of operators for built-in types, including pointer types. Thus, you cannot declare:
|
||||
|
||||
int operator++(int i); // error
|
||||
|
||||
which attempts to redefine the meaning of ++ for int, nor can you declare:
|
||||
|
||||
int *operator++(int *i); // error
|
||||
|
||||
which attempts to redefine ++ for int *.
|
||||
|
||||
References vs. const pointers
|
||||
|
||||
In my last column, I explained that C++ doesn't let you declare a "const reference" because a reference is inherently const.2 In other words, once you bind a reference to refer to an object, you cannot rebind it to refer to a different object. There's no notation for rebinding a reference after you've declared the reference. For example:
|
||||
|
||||
int &ri = i;
|
||||
|
||||
binds ri to refer to i. Then an assignment such as:
|
||||
|
||||
ri = j;
|
||||
|
||||
doesn't bind ri to j. It assigns the value in j to the object referenced by ri, namely, i.
|
||||
|
||||
In short, whereas a pointer can point to many different objects during its lifetime, a reference can refer to only one object during its lifetime. Some people claim that's a significant difference between references and pointers. I'm not sold on the idea. Maybe this is a difference between references and pointers, but it's not a difference between references and const pointers. Again, once you bind a reference to an object, you can't change it to refer to something else. Since you can't change the reference after you bind it, you must bind the reference at the beginning of its lifetime. Otherwise, the reference will never be bound to anything and will be useless, if not downright dangerous.
|
||||
|
||||
All of the statements in the previous paragraph apply to const pointers just as much as they do to references. (I'm talking about "const pointers" here, not about "pointers to const.") For example, a reference declaration at block scope must have an initializer, as in:
|
||||
|
||||
void f()
|
||||
{
|
||||
int &r = i;
|
||||
...
|
||||
}
|
||||
|
||||
Omitting the initializer produces a compile-time error:
|
||||
|
||||
void f()
|
||||
{
|
||||
int &r; // error
|
||||
...
|
||||
}
|
||||
|
||||
A const pointer declaration at block scope must also have an initializer, as in:
|
||||
|
||||
void f()
|
||||
{
|
||||
int *const p = &i;
|
||||
...
|
||||
}
|
||||
|
||||
Omitting the initializer is just as much an error:
|
||||
|
||||
void f()
|
||||
{
|
||||
int *const p; // error
|
||||
...
|
||||
}
|
||||
|
||||
In my opinion, the fact that you can't rebind a reference is no more a difference between references and pointers than that which exists between const pointers and non-const pointers.
|
||||
|
||||
Null references
|
||||
|
||||
Appearances aside, const pointers differ from references in one subtle but significant way. A valid reference must refer to an object; a pointer need not. A pointer, even a const pointer, can have a null value. A null pointer doesn't point to anything.
|
||||
|
||||
This difference suggests that you should use a reference as a parameter type when you want to insist that the parameter refer to an object. For example, let's revisit the swap function, which accepts two int arguments and swaps the value of its first argument with the value of its second argument. For example:
|
||||
|
||||
int i, j;
|
||||
...
|
||||
swap(i, j);
|
||||
|
||||
leaves the value that was in i in j, and the value that was in j in i. You could write this function as:
|
||||
|
||||
void swap(int *v1, int *v2)
|
||||
{
|
||||
int temp = *v1;
|
||||
*v1 = *v2;
|
||||
*v2 = temp;
|
||||
}
|
||||
|
||||
so that a call looks like: swap(&i, &j);
|
||||
|
||||
This interface suggests that either or both arguments might be a null pointer. The suggestion is misleading. For example, the consequences of calling: swap(&i, NULL);
|
||||
|
||||
are likely to be unpleasant. Defining the function with reference parameters, as in:
|
||||
|
||||
void swap(int &v1, int &v2)
|
||||
{
|
||||
int temp = v1;
|
||||
v1 = v2;
|
||||
v2 = temp;
|
||||
}
|
||||
|
||||
clearly suggests that a call to swap should provide two objects whose values will be swapped. As an added bonus, calls to this function look nicer without the &s cluttering things up:
|
||||
|
||||
swap(i, j);
|
||||
|
||||
Greater safety?
|
||||
|
||||
Some people take the fact that a reference can't be null to mean that references are somehow safer than pointers. They may be a little safer, but I don't think they're a lot safer. Although a valid reference can't be null, an invalid one can be null. In fact, there's a ton of ways programs can produce invalid references, not just null references. For example, you can define a reference so that it refers to the object addressed by a pointer, as in:
|
||||
|
||||
int *p;
|
||||
...
|
||||
int &r = *p;
|
||||
|
||||
If the pointer happens to be null at the time of the reference definition, the reference is a null reference. Technically, the error is not in binding the reference, but in dereferencing the null pointer. Dereferencing a null pointer produces undefined behavior, which means lots of things could happen, and most of them aren't good. It's likely that, when the program binds reference r to *p (the object that p points to), it won't actually dereference p. Rather, it will just copy the value of p to the pointer that implements r. The program will keep running only to have the error manifest itself more overtly sometime later during program execution. Oh joy.
|
||||
|
||||
The following function shows another way to produce an invalid reference:
|
||||
|
||||
int &f()
|
||||
{
|
||||
int i;
|
||||
...
|
||||
return i;
|
||||
}
|
||||
|
||||
This function returns a reference to local variable i. However, the storage for i vanishes as the function returns. Thus, the function returns a reference to deallocated storage. The behavior is the same as returning a pointer to a local variable. Some compilers do detect this particular error at compile time. However, you can disguise the sin so it will go undetected.
|
||||
|
||||
I like references. There are good reasons to use them instead of pointers. But, if you expect that using references will make your program significantly more robust, you're likely to be disappointed.
|
||||
|
||||
My bad
|
||||
|
||||
In my last column, I explained that although you can't define a "const reference" directly you can do it indirectly through a typedef. I gave this example:
|
||||
|
||||
typedef int &ref_to_int;
|
||||
...
|
||||
int_ref const r = i;
|
||||
|
||||
That second line should have been:
|
||||
|
||||
ref_to_int const r = i;
|
||||
|
||||
Dan Saks is a high school track coach and the president of Saks & Associates, a C/C++ training and consulting company. He is also a consulting editor for the C/C++ Users Journal. He served for many years as secretary of the C++ standards committee. You can write to him at dsaks@wittenberg.edu.
|
||||
|
||||
References
|
||||
|
||||
Saks, Dan. "Introduction to References," Embedded Systems Programming, January 2001, p. 81.
|
||||
Saks, Dan. "References and const", Embedded Systems Programming February 2001, p. 73.
|
||||
|
||||
Return to April ESP Index
|
||||
7
Zim/Programme/C++/boost.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-14T15:45:02+08:00
|
||||
|
||||
====== boost ======
|
||||
Created Tuesday 14 February 2012
|
||||
|
||||
53
Zim/Programme/C++/boost/BOOST_FOREACH_介绍.txt
Normal file
@@ -0,0 +1,53 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-19T22:22:45+08:00
|
||||
|
||||
====== BOOST FOREACH 介绍 ======
|
||||
Created Sunday 19 February 2012
|
||||
http://www.cnblogs.com/sld666666/archive/2011/05/30/2063140.html
|
||||
|
||||
“Make simple things easy.”
|
||||
-- Larry Wall
|
||||
|
||||
c++中,写一个循环去迭代一个序列是很单调的。
|
||||
|
||||
1 string hello("hello, boost!");
|
||||
2
|
||||
3 for (int i = 0; i != hello.size(); ++i)
|
||||
4 {
|
||||
5 cout<<hello.at(i);
|
||||
6 }
|
||||
|
||||
我们可以用__std:for_each__,但是这样并没有减少代码了,而且让很多功能分离太远
|
||||
|
||||
1 void print (char ch)
|
||||
2 {
|
||||
3 cout << ch;
|
||||
4 }
|
||||
5 int _tmain(int argc, _TCHAR* argv[])
|
||||
6 {
|
||||
7 string hello("hello, boost!");
|
||||
8 __for_each__(hello.begin(), hello.end(),print);
|
||||
9
|
||||
10 cout<<endl;
|
||||
11
|
||||
12 return 0;
|
||||
13 }
|
||||
|
||||
|
||||
BOOST_FOREACH 是为了__易用性和高效性__而设计的。它不进行动态的内存分配,没有虚拟函数调用或通过函数指针的调用。这样可以生成近似于最优化的代码。
|
||||
|
||||
1 string hello("hello, boost!");
|
||||
2 __BOOST_FOREACH(char ch, hello)__
|
||||
3 {
|
||||
4 cout<< ch;
|
||||
5 }
|
||||
6
|
||||
7 cout<<endl;
|
||||
|
||||
|
||||
BOOST_FOREACH **支持所有序列式容器。**
|
||||
当然,为了更漂亮我们可以这样改造
|
||||
|
||||
1 #define foreach BOOST_FOREACH
|
||||
2 #define reverse_foreach BOOST_REVERSE_FOREACH
|
||||
727
Zim/Programme/C++/boost/Boost_Serialization_库.txt
Normal file
@@ -0,0 +1,727 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-14T16:15:11+08:00
|
||||
|
||||
====== Boost Serialization 库 ======
|
||||
Created Tuesday 14 February 2012
|
||||
|
||||
http://www.ibm.com/developerworks/cn/aix/library/au-boostserialization/
|
||||
|
||||
Arpan Sen, 独立作家
|
||||
简介: Boost C++ 库让编写优秀的代码变得很容易,但出了问题时该怎么办?本文将介绍 Boost Serialization 库,了解如何在您的代码中采用__序列化技术__,让以后的调试变得更容易。
|
||||
|
||||
发布日期: 2012 年 1 月 04 日
|
||||
级别: 中级
|
||||
原创语言: 英文
|
||||
访问情况 : 516 次浏览
|
||||
评论: 0 (查看 | 添加评论 - 登录)
|
||||
平均分 0 星 共 0 个评分 平均分 (0个评分)
|
||||
为本文评分
|
||||
|
||||
===== 简介 =====
|
||||
|
||||
当您熬了几个通宵才编出的程序突然在客户站点上崩溃时,您可能会感到束手无策,因为__没有测试用例__可用来帮您再现灾难现场,因此也就无法进行调试。这是很多人都很熟悉的场景,但对于此问题,人们关心的更多的是__如何解决它__? 只是__转储堆栈追踪信息__显然并不是一个好办法。您需要**深入了解代码的数据结构**并检查它们的值。
|
||||
|
||||
Boost Serialization 是一个解决方案。__可以将程序内容转出到归档文件(文本或 XML 文件)中,并从该归档文件中恢复数据,重新生成一个崩溃之前的代码快照__。这听起来还不错吧?让我们接着往下看。
|
||||
|
||||
Serialization 源来自标准 Boost 安装包(参阅 参考资料)。与其他 Boost 库不同的是,Serialization 并__不是一个只包含头文件的库,因此需要lianjie它__。为此,请参阅安装包中的构建说明(参阅 参考资料)。如果您喜欢利用现成的安装,请参见 boostpro(仍请参阅 参考资料)。在本文中,我使用的是 1.46.1 版本的 Boost ,并使用 gcc-4.3.4 编译代码。
|
||||
|
||||
===== 使用了 Boost Serialization 的 Hello World =====
|
||||
|
||||
在执行更重要的任务之前,我们先来验证一下概念。在以下的 清单 1 中,您会看到一个字符串,它的**值被转储到一个归档文件中**。在以下的 清单 2 中,将此归档文件的内容恢复,以验证此字符串的值是否与原来相符。
|
||||
|
||||
清单 1. 将字符串内容保存到文本归档文件中
|
||||
|
||||
|
||||
#include <boost/archive/text_oarchive.hpp>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
void save()
|
||||
{
|
||||
std::ofstream file("archive.txt");
|
||||
__boost::archive::text_oarchive__ oa(file);
|
||||
std::string s = "Hello World!\n";
|
||||
oa << s;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
save();
|
||||
}
|
||||
|
||||
|
||||
现在将内容加载回来。
|
||||
|
||||
清单 2. 将字符串的内容加载到文本归档文件中
|
||||
|
||||
|
||||
#include <boost/archive/text_iarchive.hpp>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
void load()
|
||||
{
|
||||
std::ifstream file("archive.txt");
|
||||
__boost::archive::text_iarchive__ ia(file);
|
||||
std::string s;
|
||||
ia >> s;
|
||||
std::cout << s << std::endl;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
load();
|
||||
}
|
||||
|
||||
|
||||
不出所料,清单 2 的输出内容是 “Hello World”。
|
||||
|
||||
现在,我们仔细看一下代码。Boost 创建了一个**文本归档文件**(一个文本文件),它含有需要转储的内容。为了转储这些内容,您需要创建了一个 text_oarchive。为了恢复内容,您还创建了一个 text_iarchive,并分别在头文件 text_oarchive.hpp 和 text_iarchive.hpp 中声明它。转储和恢复内容很直观,使用 << 和 >> 运算符,其工作原理非常__类似于流 I/O__,除了要将内容转储到文件中,然后过一段时间以后再从该文件中恢复。
|
||||
|
||||
不过,您可能想只用一个 & 运算符完成转储和恢复操作,而不是使用上述的两个不同运算符。下面的 清单 3 显示了相关的使用方法。
|
||||
|
||||
清单 3. 使用 & 运算符执行 “转储 - 恢复” 操作
|
||||
|
||||
|
||||
#include <boost/archive/text_iarchive.hpp>
|
||||
#include <boost/archive/text_oarchive.hpp>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
void save()
|
||||
{
|
||||
std::ofstream file("archive.txt");
|
||||
boost::archive::text_oarchive oa(file);
|
||||
std::string s = "Hello World!\n";
|
||||
** oa & s; **// has same effect as oa << s;
|
||||
}
|
||||
|
||||
void load()
|
||||
{
|
||||
std::ifstream file("archive.txt");
|
||||
boost::archive::text_iarchive ia(file);
|
||||
std::string s;
|
||||
** ia & s;**
|
||||
std::cout << s << std::endl;
|
||||
}
|
||||
|
||||
|
||||
我们来看一下转储的文本文件:
|
||||
|
||||
22 serialization::archive 9 13 Hello World!
|
||||
|
||||
|
||||
请注意,在以后的 Boost 版本中,文本文件的内容和格式可能有所不同,因此应用程序代码最好不要依赖于内部归档文件内容。
|
||||
|
||||
===== 创建一个 XML 归档文件 =====
|
||||
|
||||
如果您想使用 __XML 归档文件__,而不是文本归档文件,则必须包含来自 Boost 源的头文件 xml_iarchive.hpp 和 xml_oarchive.hpp。这些头文件声明或定义了 XML 归档文件语义。但是,该 “转储 - 恢复” 操作与应用于文本归档文件的 “转储 - 恢复” 操作仍有些微不同之处:**需要将数据打包到一个名为 BOOST_SERIALIZATION_NVP 的宏中**。下面的 清单 4 提供了相关的代码。
|
||||
|
||||
清单 4. 从 XML 归档文件执行 “转储 - 恢复” 操作
|
||||
|
||||
|
||||
#include <boost/archive/xml_iarchive.hpp>
|
||||
#include <boost/archive/xml_oarchive.hpp>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
void save()
|
||||
{
|
||||
std::ofstream file("archive.xml");
|
||||
boost::archive::xml_oarchive oa(file);
|
||||
std::string s = "Hello World!\n";
|
||||
__oa & BOOST_SERIALIZATION_NVP(s);__
|
||||
}
|
||||
|
||||
void load()
|
||||
{
|
||||
std::ifstream file("archive.xml");
|
||||
boost::archive::xml_iarchive ia(file);
|
||||
std::string s;
|
||||
__ia & BOOST_SERIALIZATION_NVP(s); __
|
||||
std::cout << s << std::endl;
|
||||
}
|
||||
|
||||
|
||||
清单 5 显示了 XML 归档文件的内容。变量名可充当标记 (<s>Hello World!</s>)。
|
||||
|
||||
清单 5. XML 归档文件的内容
|
||||
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<!DOCTYPE boost_serialization>
|
||||
<boost_serialization signature="serialization::archive" version="9">
|
||||
<s>Hello World!</s>
|
||||
</boost_serialization>
|
||||
|
||||
|
||||
===== 还有其他哪些内容可以序列化? =====
|
||||
|
||||
以下内容才是精华。无需额外编码,就可以将 C++ 编程语言中的很多元素序列化。__类、类指针、数组和 Standard Template Library (STL) 集合__都可以被序列化。下面的 清单 6 提供了一个包含数组的样例。
|
||||
|
||||
清单 6. 对整数数组执行 “转储 - 恢复” 操作
|
||||
|
||||
#include <boost/archive/xml_oarchive.hpp>
|
||||
#include <boost/archive/xml_iarchive.hpp>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
void save()
|
||||
{
|
||||
std::ofstream file("archive.xml");
|
||||
boost::archive::xml_oarchive oa(file);
|
||||
int array1[] = {34, 78, 22, 1, 910};
|
||||
oa & BOOST_SERIALIZATION_NVP(array1);
|
||||
}
|
||||
|
||||
void load()
|
||||
{
|
||||
std::ifstream file("archive.xml");
|
||||
boost::archive::xml_iarchive ia(file);
|
||||
int restored[5]; // Need to specify expected array size
|
||||
ia >> BOOST_SERIALIZATION_NVP(restored);
|
||||
__ std::ostream_iterator<int> oi(std::cout, " ");__
|
||||
std::copy(a, a+5, oi);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
save();
|
||||
load();
|
||||
}
|
||||
|
||||
|
||||
这很简单。转储过程与字符串类一样;但在恢复过程中,需要指定预期的数组大小。否则,程序会崩溃。清单 7 提供了 清单 6 中代码的已转储的 XML 归档文件。
|
||||
|
||||
清单 7. 通过数组转储创建的 XML 归档文件
|
||||
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<!DOCTYPE boost_serialization>
|
||||
<boost_serialization signature="serialization::archive" version="9">
|
||||
<array1>
|
||||
<count>5</count>
|
||||
<item>34</item>
|
||||
<item>78</item>
|
||||
<item>22</item>
|
||||
<item>1</item>
|
||||
<item>910</item>
|
||||
</array1>
|
||||
</boost_serialization>
|
||||
|
||||
|
||||
是否可以仅通过指定指针 int* restored 完成此操作并为您恢复数组?答案是否定的。必须每次都指定大小。如果认真回答此问题的话,答案是对基本类型的指针进行序列化非常复杂。
|
||||
|
||||
===== 序列化 STL 集合 =====
|
||||
|
||||
要序列化 STL 列表和向量,则必须了解每个 STL 类型,应用程序代码必须包含来自 Serialization 源的具有类似名称的头文件。对于列表,需要包含 boost/serialization/list.hpp 等等。请注意,对于列表和向量,在将信息加载回来的过程中,不需要提供任何大小和范围信息,这也是相对于具有相同功能的应用程序容器,人们更喜欢使用 STL 容器的另一个原因。清单 8 显示了用于序列化 STL 集合的代码。
|
||||
|
||||
清单 8. 序列化 STL 集合
|
||||
|
||||
|
||||
#include <boost/archive/xml_oarchive.hpp>
|
||||
#include <boost/archive/xml_iarchive.hpp>
|
||||
#include <boost/serialization/list.hpp>
|
||||
#include <boost/serialization/vector.hpp>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
void save()
|
||||
{
|
||||
std::ofstream file("archive.xml");
|
||||
boost::archive::xml_oarchive oa(file);
|
||||
float array[] = {34.2, 78.1, 22.221, 1.0, -910.88};
|
||||
std::list<float> L1(array, array+5);
|
||||
std::vector<float> V1(array, array+5);
|
||||
oa & BOOST_SERIALIZATION_NVP(L1);
|
||||
oa & BOOST_SERIALIZATION_NVP(V1);
|
||||
}
|
||||
|
||||
void load()
|
||||
{
|
||||
std::ifstream file("archive.xml");
|
||||
boost::archive::xml_iarchive ia(file);
|
||||
std::list<float> L2;
|
||||
ia >> BOOST_SERIALIZATION_NVP(L2); // No size/range needed
|
||||
|
||||
std::vector<float> V2;
|
||||
ia >> BOOST_SERIALIZATION_NVP(V2); // No size/range needed
|
||||
|
||||
std::ostream_iterator<float> oi(std::cout, " ");
|
||||
std::copy(L2.begin(), L2.end(), oi);
|
||||
std::copy(V2.begin(), V2.end(), oi);
|
||||
}
|
||||
|
||||
|
||||
清单 9 显示了一个 XML 归档文件。
|
||||
|
||||
清单 9. 使用 STL 容器的转储归档文件
|
||||
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<!DOCTYPE boost_serialization>
|
||||
<boost_serialization signature="serialization::archive" version="9">
|
||||
<L1>
|
||||
<count>5</count>
|
||||
<item_version>0</item_version>
|
||||
<item>34.200001</item>
|
||||
<item>78.099998</item>
|
||||
<item>22.221001</item>
|
||||
<item>1</item>
|
||||
<item>-910.88</item>
|
||||
</L1>
|
||||
<V1>
|
||||
<count>5</count>
|
||||
<item_version>0</item_version>
|
||||
<item>34.200001</item>
|
||||
<item>78.099998</item>
|
||||
<item>22.221001</item>
|
||||
<item>1</item>
|
||||
<item>-910.88</item>
|
||||
</V1>
|
||||
</boost_serialization>
|
||||
|
||||
|
||||
回页首
|
||||
|
||||
===== 序列化自己的类型 =====
|
||||
|
||||
是否想序列化自己的类型?毫无疑问您是愿意的!我们将采用一个表示日期的结构作为一个简单的示例:
|
||||
|
||||
typedef struct date {
|
||||
unsigned int m_day;
|
||||
unsigned int m_month;
|
||||
unsigned int m_year;
|
||||
} date;
|
||||
|
||||
|
||||
要对某个类进行序列化,则必须__在类定义中定义一个名为 serialize 的方法__。在转储和恢复类的过程中会调用该方法。以下是对 serialize 方法的声明:
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive& archive, const unsigned int version)
|
||||
{
|
||||
//… your custom code here
|
||||
}
|
||||
|
||||
|
||||
在第二段代码片段中,serialize 是一个模板函数,第一个参数应该是对 Boost 归档文件的引用。那么,XML 归档文件的代码会是什么样呢?请看以下代码:
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive& archive, const unsigned int version)
|
||||
{
|
||||
archive & BOOST_SERIALIZATION_NVP(m_day);
|
||||
archive & BOOST_SERIALIZATION_NVP(m_month);
|
||||
archive & BOOST_SERIALIZATION_NVP(m_year);
|
||||
}
|
||||
|
||||
|
||||
下面的 清单 10 提供了完整代码。
|
||||
|
||||
清单 10. 日期类型的 “转储 - 恢复”
|
||||
|
||||
|
||||
#include <boost/archive/xml_oarchive.hpp>
|
||||
#include <boost/archive/xml_iarchive.hpp>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
typedef struct date {
|
||||
unsigned int m_day;
|
||||
unsigned int m_month;
|
||||
unsigned int m_year;
|
||||
|
||||
date( int d, int m, int y) : m_day(d), m_month(m), m_year(y)
|
||||
{}
|
||||
date() : m_day(1), m_month(1), m_year(2000)
|
||||
{}
|
||||
friend std::ostream& operator << (std::ostream& out, date& d)
|
||||
{
|
||||
out << "day:" << d.m_day
|
||||
<< " month:" << d.m_month
|
||||
<< " year:" << d.m_year;
|
||||
return out;
|
||||
}
|
||||
template<class Archive>
|
||||
void serialize(Archive& archive, const unsigned int version)
|
||||
{
|
||||
archive & BOOST_SERIALIZATION_NVP(m_day);
|
||||
archive & BOOST_SERIALIZATION_NVP(m_month);
|
||||
archive & BOOST_SERIALIZATION_NVP(m_year);
|
||||
}
|
||||
} date;
|
||||
|
||||
void save()
|
||||
{
|
||||
std::ofstream file("archive.xml");
|
||||
boost::archive::xml_oarchive oa(file);
|
||||
date d(15, 8, 1947);
|
||||
oa & BOOST_SERIALIZATION_NVP(d);
|
||||
}
|
||||
|
||||
void load()
|
||||
{
|
||||
std::ifstream file("archive.xml");
|
||||
boost::archive::xml_iarchive ia(file);
|
||||
date dr;
|
||||
ia >> BOOST_SERIALIZATION_NVP(dr);
|
||||
std::cout << dr;
|
||||
}
|
||||
|
||||
|
||||
请注意,除了定义 serialize 方法之外,并没有为了处理用户定义类型而执行任何特殊操作。以上代码运行正常,但有一个很明显的问题:可能需要序列化来自第三方的类型,而它们的类声明可能是无法修改的。对于这种情况,您__可以使用 serialize 的非侵入性(non-intrusive)版本,以便可以在类范围之外定义该方法__。下面的 清单11 显示了 date 类的非侵入性 serialize 方法。请注意,如果 serialize 方法已在全局范围内定义,该代码仍然有效;但是,好的编程实践是在相关命名空间内定义该方法。
|
||||
|
||||
清单 11. serialize 方法的非侵入性版本
|
||||
|
||||
|
||||
namespace boost {
|
||||
namespace serialization {
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive& archive, date& d, const unsigned int version)
|
||||
{
|
||||
archive & BOOST_SERIALIZATION___NVP__(d.m_day);
|
||||
archive & BOOST_SERIALIZATION_NVP(d.m_month);
|
||||
archive & BOOST_SERIALIZATION_NVP(d.m_year);
|
||||
}
|
||||
|
||||
} // namespace serialization
|
||||
} // namespace boost
|
||||
|
||||
|
||||
清单 12 显示了 date 类型的 XML 归档文件。
|
||||
|
||||
清单 12. 用户定义类型的 XML 归档文件
|
||||
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<!DOCTYPE boost_serialization>
|
||||
<boost_serialization signature="serialization::archive" version="9">
|
||||
<d class_id="0" tracking_level="0" version="0">
|
||||
<d.m_day>15</d.m_day>
|
||||
<d.m_month>8</d.m_month>
|
||||
<d.m_year>1947</d.m_year>
|
||||
</d>
|
||||
</boost_serialization>
|
||||
|
||||
===== 处理类层次结构 =====
|
||||
|
||||
类通常是从其他类派生的,因此您需要找到一种能够__在序列化派生类的同时序列化基类的方法__。对于基类和派生类,都必须定义 serialize 方法。另外,还需要调整派生类的 serialize 定义,如 清单 13 所示。
|
||||
|
||||
清单 13. 序列化基类
|
||||
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive& archive, const unsigned int version)
|
||||
{
|
||||
// serialize base class information
|
||||
archive & boost::serialization::base_object<Base Class>(*this);
|
||||
// serialize derived class members
|
||||
archive & derived-class-member1;
|
||||
archive & derived-class-member2;
|
||||
// …
|
||||
}
|
||||
|
||||
|
||||
直接在派生类的 serialize 方法中调用基类的 serialize 方法是一个很糟糕的想法。尽管能够这么做,但是无法追踪类版本控制(稍后讲述),或者无法消除生成的归档文件中的冗余。避免这种错误的一种推荐编码方式是在所有类中将 serialize 方法设置为 private,并在所有将要序列化的类中使用 friend class boost::serialization::access 声明。
|
||||
|
||||
===== 通过基类指针转储派生类 =====
|
||||
|
||||
通过指针转储派生类是完全有可能的;但是,该类和派生类都应该有各自的已定义的 serialize 方法。还有,您需要在转储和恢复过程中调用以下方法。
|
||||
|
||||
<archive name>.register_type<derived-type name>( )
|
||||
|
||||
|
||||
假设 date 类是从一个名为 base 的类派生出来的。清单 14 显示了应该在 save 和 load 方法中加入哪些代码。
|
||||
|
||||
清单 14. 使用基类指针实现序列化
|
||||
|
||||
|
||||
void save()
|
||||
{
|
||||
std::ofstream file("archive.xml");
|
||||
boost::archive::xml_oarchive oa(file);
|
||||
oa.register_type<date>( );
|
||||
base* b = new date(15, 8, 1947);
|
||||
oa & BOOST_SERIALIZATION_NVP(b);
|
||||
}
|
||||
|
||||
void load()
|
||||
{
|
||||
std::ifstream file("archive.xml");
|
||||
boost::archive::xml_iarchive ia(file);
|
||||
ia.register_type<date>( );
|
||||
base *dr;
|
||||
ia >> BOOST_SERIALIZATION_NVP(dr);
|
||||
date* dr2 = dynamic_cast<date*> (dr);
|
||||
std::cout << dr2;
|
||||
}
|
||||
|
||||
|
||||
在这里,在转储和恢复过程中使用了 base 指针。但序列化的实际上是 date 对象。在转储和恢复之前,已经在这两个类中注册了 date 类型。
|
||||
|
||||
===== 在执行 “转储 - 恢复” 操作过程中使用对象指针 =====
|
||||
|
||||
可以使用对象指针进行转储和恢复。这样做很有意思。您期望什么样的 XML 归档文件的内容?当然,转储指针值不会有问题。您需要转储实际对象,然后将其还原。此外,指向同一个对象的多个指针又该如何呢?如果 XML 归档文件具有同一个已转储对象的多个副本,那么显然不太好。Boost Serialization 的一大优势是无论在何处(包括用于指针),所用的语法几乎都是一样的。以下的 清单15 是 清单 10 的修改版。
|
||||
|
||||
清单 15. 使用指针执行 “转储 - 恢复” 操作
|
||||
|
||||
|
||||
void save()
|
||||
{
|
||||
std::ofstream file("archive.xml");
|
||||
boost::archive::xml_oarchive oa(file);
|
||||
date* d = new date(15, 8, 1947);
|
||||
std::cout << d << std::endl;
|
||||
oa & BOOST_SERIALIZATION_NVP(d);
|
||||
// … other code follows
|
||||
}
|
||||
|
||||
void load()
|
||||
{
|
||||
std::ifstream file("archive.xml");
|
||||
boost::archive::xml_iarchive ia(file);
|
||||
date* dr;
|
||||
ia >> BOOST_SERIALIZATION_NVP(dr);
|
||||
std::cout << dr << std::endl;
|
||||
std::cout << *dr;
|
||||
}
|
||||
|
||||
|
||||
请注意,在此清单中,d 和 dr 的值是不同的,但内容相同。清单 16 显示了 清单 15 代码的 XML 归档文件。
|
||||
|
||||
清单 16. 使用指针的 XML 归档文件
|
||||
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<!DOCTYPE boost_serialization>
|
||||
<boost_serialization signature="serialization::archive" version="9">
|
||||
<d class_id="0" tracking_level="1" version="0" object_id="_0">
|
||||
<d.m_day>15</d.m_day>
|
||||
<d.m_month>8</d.m_month>
|
||||
<d.m_year>1947</d.m_year>
|
||||
</d>
|
||||
</boost_serialization>
|
||||
|
||||
|
||||
现在,请考虑以下情况:将两个指针转储到同一个对象并观察用于相同对象的归档文件是什么样子的。清单 17 对 清单 15 中的代码稍微进行了一些修改。
|
||||
|
||||
清单 17. 使用指针执行 “转储 - 恢复” 操作
|
||||
|
||||
|
||||
void save()
|
||||
{
|
||||
std::ofstream file("archive.xml");
|
||||
boost::archive::xml_oarchive oa(file);
|
||||
date* d = new date(15, 8, 1947);
|
||||
std::cout << d << std::endl;
|
||||
oa & BOOST_SERIALIZATION_NVP(d);
|
||||
date* d2 = d;
|
||||
oa & BOOST_SERIALIZATION_NVP(d2);
|
||||
// … other code follows
|
||||
}
|
||||
|
||||
void load()
|
||||
{
|
||||
std::ifstream file("archive.xml");
|
||||
boost::archive::xml_iarchive ia(file);
|
||||
date* dr;
|
||||
ia >> BOOST_SERIALIZATION_NVP(dr);
|
||||
std::cout << dr << std::endl;
|
||||
std::cout << *dr;
|
||||
date* dr2;
|
||||
ia >> BOOST_SERIALIZATION_NVP(dr2);
|
||||
std::cout << dr2 << std::endl;
|
||||
std::cout << *dr2;
|
||||
}
|
||||
|
||||
|
||||
下面的 清单 18 提供了 清单 17 中代码的 XML 归档文件。观察如何处理第二个指针;此外,只有一个对象被转储。
|
||||
|
||||
清单 18. 包含作为指针的 d2 的 XML 归档文件
|
||||
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<!DOCTYPE boost_serialization>
|
||||
<boost_serialization signature="serialization::archive" version="9">
|
||||
<d class_id="0" tracking_level="1" version="0" object_id="_0">
|
||||
<d.m_day>15</d.m_day>
|
||||
<d.m_month>8</d.m_month>
|
||||
<d.m_year>1947</d.m_year>
|
||||
</d>
|
||||
<d2 class_id_reference="0" object_id_reference="_0"></d2>
|
||||
</boost_serialization>
|
||||
|
||||
|
||||
对于引用的处理方法与用户应用程序代码中一样。尽管如此,请注意,在恢复过程中,创建了两个独特的对象。因此,归档文件应该保存两个具有相同值的对象。与使用指针时的情况不同,以下是归档文件的内容,其中的 d2 是清单 17 中的一个引用(参见下面的 清单 19)。
|
||||
|
||||
清单 19.包含作为 d 的引用 d2 的 XML 归档文件
|
||||
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<!DOCTYPE boost_serialization>
|
||||
<boost_serialization signature="serialization::archive" version="9">
|
||||
<d class_id="0" tracking_level="0" version="0">
|
||||
<d.m_day>15</d.m_day>
|
||||
<d.m_month>8</d.m_month>
|
||||
<d.m_year>1947</d.m_year>
|
||||
</d>
|
||||
<d2>
|
||||
<d.m_day>15</d.m_day>
|
||||
<d.m_month>8</d.m_month>
|
||||
<d.m_year>1947</d.m_year>
|
||||
</d2>
|
||||
</boost_serialization>
|
||||
|
||||
|
||||
===== 将 serialize 拆分成 save 和 load =====
|
||||
|
||||
有时候,您不想使用同样的 serialize 方法来转储和恢复对象。在这种情况下,您可以将 serialize 方法拆分成两个方法,即 save 和 load,它们具有类似的签名。这两个方法都是之前定义的 serialize 方法的一部分。此外,需要添加 BOOST_SERIALIZATION_SPLIT_MEMBER 宏作为类定义的一部分。清单 20 显示了这些方法是什么样子。
|
||||
|
||||
清单 20. 将 serialize 方法拆分成 save 和 load 方法
|
||||
|
||||
|
||||
template<class Archive>
|
||||
void save(Archive& archive, const unsigned int version) const
|
||||
{
|
||||
//…
|
||||
}
|
||||
|
||||
template<class Archive>
|
||||
void load(Archive& archive, const unsigned int version)
|
||||
{
|
||||
//…
|
||||
}
|
||||
|
||||
BOOST_SERIALIZATION_SPLIT_MEMBER( ) // must be part of class
|
||||
|
||||
|
||||
注意 save 方法签名后的 const。如果没有 const 限定符,该代码将无法编译。对于 date 类,清单 21 显示了这些方法现在是什么样子。
|
||||
|
||||
清单 21. date 类的 save 和 load 方法
|
||||
|
||||
|
||||
template<class Archive>
|
||||
void save(Archive& archive, const unsigned int version) const
|
||||
{
|
||||
archive << BOOST_SERIALIZATION_NVP(m_day);
|
||||
archive << BOOST_SERIALIZATION_NVP(m_month);
|
||||
archive << BOOST_SERIALIZATION_NVP(m_year)
|
||||
}
|
||||
|
||||
template<class Archive>
|
||||
void load(Archive& archive, const unsigned int version)
|
||||
{
|
||||
archive >> BOOST_SERIALIZATION_NVP(m_day);
|
||||
archive >> BOOST_SERIALIZATION_NVP(m_month);
|
||||
archive >> BOOST_SERIALIZATION_NVP(m_year)
|
||||
}
|
||||
|
||||
BOOST_SERIALIZATION_SPLIT_MEMBER( ) // must be part of class
|
||||
|
||||
|
||||
===== 了解版本控制 =====
|
||||
|
||||
serialize、save 和 load 的方法签名都使用无符号整数版本作为最后一个参数。这些数字有什么用?随着时间变化,类的内部变量名称可能发生变化,添加新的字段或移除已有字段,等等。这是软件开发过程中的自然进程,除了归档文件仍然保存着关于数据类型原有状态的信息。为了规避这个问题,需要使用版本号。
|
||||
|
||||
我们举一个 date 类的例子。假设您在 date 类中引入一个名为 m_tag、类型为 string 的字段。该类以前的版本以版本 0 的形式在归档文件中转储,如 清单 12 所示。下面的 清单 22 显示了该类的 load 方法(您可能已经使用了serialize,但这里使用 load 提供了更清晰的实现)。
|
||||
|
||||
清单 22. 使用版本控制处理更新的类字段
|
||||
|
||||
|
||||
template<class Archive>
|
||||
void load(Archive& archive, const unsigned int version)
|
||||
{
|
||||
archive >> BOOST_SERIALIZATION_NVP(m_day);
|
||||
archive >> BOOST_SERIALIZATION_NVP(m_month);
|
||||
archive >> BOOST_SERIALIZATION_NVP(m_year);
|
||||
if (version > 0)
|
||||
archive >> BOOST_SERIALIZATION_NVP(m_tag);
|
||||
}
|
||||
|
||||
|
||||
很明显,合理使用版本控制可以让该代码与早先版本软件中的旧归档文件一起使用。
|
||||
|
||||
|
||||
===== 使用共享指针 =====
|
||||
|
||||
共享指针是一项经常使用且功能极其强大的编程技术。再次强调,Boost Serialization 一个主要优势在于能轻松__将共享指针序列化__,并保持目前所知的语法不变。唯一要注意的是,必须在应用程序代码中包含 boost/serialization/shared_ptr.hpp 头文件。从修改 清单 15 并使用 boost::shared_ptr 指针代替普通指针开始。代码如下面的 清单 23 所示。
|
||||
|
||||
清单 23. 使用共享指针执行 “转储 - 恢复” 操作
|
||||
|
||||
|
||||
void save()
|
||||
{
|
||||
std::ofstream file("archive.xml");
|
||||
boost::archive::xml_oarchive oa(file);
|
||||
boost::shared_ptr<date> d (new date(15, 8, 1947));
|
||||
oa & BOOST_SERIALIZATION_NVP(d);
|
||||
// … other code follows
|
||||
}
|
||||
|
||||
void load()
|
||||
{
|
||||
std::ifstream file("archive.xml");
|
||||
boost::archive::xml_iarchive ia(file);
|
||||
boost::shared_ptr<date> dr;
|
||||
ia >> BOOST_SERIALIZATION_NVP(dr);
|
||||
std::cout << *dr;
|
||||
}
|
||||
|
||||
|
||||
序列化搞定了一切?还没有。关于 Serialization 支持什么,不支持什么,还有一些使用限制。例如,如果指向栈对象的指针的转储早于实际对象,那么 Boost Serialization 就会崩溃。需要先转储对象,然后才能转储指向对象的指针(请注意,指针可以单独转储,不需要先转储对象)。参见 清单 24 中的示例:
|
||||
|
||||
清单 24. 指向栈对象的指针需要在实际对象之后转储
|
||||
|
||||
|
||||
void save()
|
||||
{
|
||||
std::ofstream file("archive.xml");
|
||||
boost::archive::xml_oarchive oa(file);
|
||||
date d(15, 8, 1947);
|
||||
std::cout << d << std::endl;
|
||||
date* d2 = &d;
|
||||
oa & BOOST_SERIALIZATION_NVP(d);
|
||||
oa & BOOST_SERIALIZATION_NVP(d2);
|
||||
}
|
||||
|
||||
void load()
|
||||
{
|
||||
std::ifstream file("archive.xml");
|
||||
boost::archive::xml_iarchive ia(file);
|
||||
date dr;
|
||||
ia >> BOOST_SERIALIZATION_NVP(dr);
|
||||
std::cout << dr << std::endl;
|
||||
date* dr2;
|
||||
ia >> BOOST_SERIALIZATION_NVP(dr2);
|
||||
std::cout << dr2 << std::endl;
|
||||
}
|
||||
|
||||
|
||||
在此清单中,无法在 d 之前转储 d2。如果查看 XML 归档文件,就会很清楚这一点:d2 作为 d 的一个引用进行转储的(参见 清单 25)。
|
||||
|
||||
清单 25. 在 XML 归档文件中,对象及其指针均被转储
|
||||
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<!DOCTYPE boost_serialization>
|
||||
<boost_serialization signature="serialization::archive" version="9">
|
||||
<d class_id="0" tracking_level="1" version="0" object_id="_0">
|
||||
<d.m_day>15</d.m_day>
|
||||
<d.m_month>8</d.m_month>
|
||||
<d.m_year>1947</d.m_year>
|
||||
</d>
|
||||
<d2 class_id_reference="0" object_id_reference="_0"></d2>
|
||||
</boost_serialization>
|
||||
|
||||
|
||||
如果有多个指针指向同一个对象,Serialization 会使用 class_id_reference 将指针与原对象关联起来(每个对象都有一个唯一的类 ID)。原对象的每个后续指针都会将 object_id_reference 改成 _1、_2,以此类推。
|
||||
|
||||
|
||||
===== 结束语 =====
|
||||
|
||||
本文到这就结束了。您已经了解了 Boost Serialization 究竟是什么;如何创建和使用文本与 XML 归档文件;以及还有如何转储和恢复普通的旧的数据类型(STL 集合、类、类指针、共享指针和数组)。本文还简单介绍了如何使用序列化和版本控制来处理类的层次结构。序列化是一个功能强大的工具。在代码中,可以通过对序列化进行善加利用使调试变得更轻松。
|
||||
692
Zim/Programme/C++/boost/Boost_Thread学习笔记.txt
Normal file
@@ -0,0 +1,692 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-14T19:26:41+08:00
|
||||
|
||||
====== Boost Thread学习笔记 ======
|
||||
Created Tuesday 14 February 2012
|
||||
|
||||
http://www.blogjava.net/LittleDS/archive/2008/05/18/201236.html
|
||||
|
||||
thread自然是boost::thread库的主角,但thread类的实现总体上是比较简单的,前面已经说过,thread只是一个跨平台的线程封装库,其中按照所使用的编译选项的不同,分别决定使用 Windows线程API还是pthread,或者Macintosh Carbon平台的thread实现。以下只讨论Windows,即使用 BOOST_HAS_WINTHREADS的情况。
|
||||
thread类提供了两种构造函数:
|
||||
thread::thread()
|
||||
thread::thread(const function0<void>& threadfunc)
|
||||
第一种构造函数用于调用GetCurrentThread构造一个当前线程的thread对象,第二种则通过传入一个函数或者一个functor来创建一个新的线程。第二种情况下,thread类在其构造函数中间接调用CreateThread来创建线程,并将线程句柄保存到成员变量m_thread中,并执行传入的函数,或执行functor的operator ()方法来启动工作线程。
|
||||
|
||||
我们可以用以下三种方式启动一个新线程:
|
||||
1、传递一个工作函数来构造一个工作线程
|
||||
|
||||
1 #include <boost/thread/thread.hpp>
|
||||
2 #include <boost/thread/mutex.hpp>
|
||||
3 #include <iostream>
|
||||
4
|
||||
5 boost::mutex io_mutex;
|
||||
6
|
||||
7 void count() // worker function
|
||||
8 {
|
||||
9 for (int i = 0; i < 10; ++i)
|
||||
10 {
|
||||
11 boost::mutex::scoped_lock lock(io_mutex);
|
||||
12 std::cout << i << std::endl;
|
||||
13 }
|
||||
14 }
|
||||
15
|
||||
16 int main(int argc, char* argv[])
|
||||
17 {
|
||||
18 boost::thread thrd1(&count);
|
||||
19 boost::thread thrd2(&count);
|
||||
20 thrd1.join();
|
||||
21 thrd2.join();
|
||||
22
|
||||
23 return 0;
|
||||
24 }
|
||||
25
|
||||
|
||||
2、传递一个functor对象来构造一个工作线程
|
||||
|
||||
1 #include <boost/thread/thread.hpp>
|
||||
2 #include <boost/thread/mutex.hpp>
|
||||
3 #include <iostream>
|
||||
4
|
||||
5 boost::mutex io_mutex;
|
||||
6
|
||||
7 struct count
|
||||
8 {
|
||||
9 count(int id) : id(id) { }
|
||||
10
|
||||
11 void operator()()
|
||||
12 {
|
||||
13 for (int i = 0; i < 10; ++i)
|
||||
14 {
|
||||
15 boost::mutex::scoped_lock lock(io_mutex); // lock io, will be explained soon.
|
||||
16 std::cout << id << ": " << i << std::endl;
|
||||
17 }
|
||||
18 }
|
||||
19
|
||||
20 int id;
|
||||
21 };
|
||||
22
|
||||
23 int main(int argc, char* argv[])
|
||||
24 {
|
||||
25 boost::thread thrd1(count(1));
|
||||
26 boost::thread thrd2(count(2));
|
||||
27 thrd1.join();
|
||||
28 thrd2.join();
|
||||
29 return 0;
|
||||
30 }
|
||||
31
|
||||
|
||||
3、无需将类设计成一个functor,借助bind来构造functor对象以创建工作线程
|
||||
|
||||
1 #include <boost/thread/thread.hpp>
|
||||
2 #include <boost/thread/mutex.hpp>
|
||||
3 #include <boost/bind.hpp>
|
||||
4 #include <iostream>
|
||||
5
|
||||
6 boost::mutex io_mutex;
|
||||
7
|
||||
8 struct count
|
||||
9 {
|
||||
10 static int num;
|
||||
11 int id;
|
||||
12
|
||||
13 count() : id(num++) {}
|
||||
14
|
||||
15 int do_count(int n)
|
||||
16 {
|
||||
17 for (int i = 0; i < n; ++i)
|
||||
18 {
|
||||
19 boost::mutex::scoped_lock lock(io_mutex);
|
||||
20 std::cout << id << ": " << i << std::endl;
|
||||
21 }
|
||||
22 return id;
|
||||
23 }
|
||||
24 };
|
||||
25
|
||||
26 int count::num = 1;
|
||||
27
|
||||
28 int main(int argc, char* argv[])
|
||||
29 {
|
||||
30 count c1;
|
||||
31 boost::thread thrd1(boost::bind(&count::do_count, &c1, 10));
|
||||
32 thrd1.join();
|
||||
33 return 0;
|
||||
34 }
|
||||
|
||||
其中bind是一个函数模板,它可以根据后面的实例化参数构造出一个functor来,上面的boost::bind(&count::do_count, &c1, 10)其实等价于返回了一个functor:
|
||||
struct countFunctor
|
||||
{
|
||||
int operator() ()
|
||||
{
|
||||
(&c1)->do_count(10); // just a hint, not actual code
|
||||
}
|
||||
};
|
||||
因此,以后就跟2中是一样的了。
|
||||
|
||||
除了thread,boost::thread另一个重要组成部分是mutex,以及工作在mutex上的boost::mutex::scoped_lock、condition和barrier,这些都是为实现线程同步提供的。
|
||||
|
||||
mutex
|
||||
boost提供的mutex有6种:
|
||||
boost::mutex
|
||||
boost::try_mutex
|
||||
boost::timed_mutex
|
||||
boost::recursive_mutex
|
||||
boost::recursive_try_mutex
|
||||
boost::recursive_timed_mutex
|
||||
下面仅对boost::mutex进行分析。
|
||||
mutex类是一个CriticalSection(临界区)封装类,它在构造函数中新建一个临界区并InitializeCriticalSection,然后用一个成员变量
|
||||
void* m_mutex;
|
||||
来保存该临界区结构。
|
||||
除此之外,mutex还提供了do_lock、do_unlock等方法,这些方法分别调用EnterCriticalSection、 LeaveCriticalSection来修改成员变量m_mutex(CRITICAL_SECTION结构指针)的状态,但这些方法都是private的,以防止我们直接对mutex进行锁操作,所有的锁操作都必须通过mutex的友元类detail::thread::lock_ops<mutex>来完成,比较有意思的是,lock_ops的所有方法:lock、unlock、trylock等都是static的,如lock_ops<Mutex>::lock的实现:
|
||||
1 template <typename Mutex>
|
||||
2 class lock_ops : private noncopyable
|
||||
3 {
|
||||
4
|
||||
5 public:
|
||||
6 static void lock(Mutex& m)
|
||||
7 {
|
||||
8 m.do_lock();
|
||||
9 }
|
||||
10
|
||||
11 }
|
||||
boost::thread的设计者为什么会这么设计呢?我想大概是:
|
||||
1、boost::thread的设计者不希望被我们直接操作mutex,改变其状态,所以mutex的所有方法都是private的(除了构造函数,析构函数)。
|
||||
2、虽然我们可以通过lock_ops来修改mutex的状态,如:
|
||||
1 #include <boost/thread/thread.hpp>
|
||||
2 #include <boost/thread/mutex.hpp>
|
||||
3 #include <boost/thread/detail/lock.hpp>
|
||||
4
|
||||
5 int main()
|
||||
6 {
|
||||
7 boost::mutex mt;
|
||||
8 //mt.do_lock(); // Error! Can not access private member!
|
||||
9
|
||||
10 boost::detail::thread::lock_ops<boost::mutex>::lock(mt);
|
||||
11
|
||||
12 return 0;
|
||||
13 }
|
||||
但是,这是不推荐的,因为mutex、scoped_lock、condition、barrier是一套完整的类系,它们是相互协同工作的,像上面这么操作没有办法与后面的几个类协同工作。
|
||||
scoped_lock
|
||||
上面说过,不应该直接用lock_ops来操作mutex对象,那么,应该用什么呢?答案就是scoped_lock。与存在多种mutex一样,存在多种与mutex对应的scoped_lock:
|
||||
|
||||
scoped_lock
|
||||
scoped_try_lock
|
||||
scoped_timed_lock
|
||||
|
||||
这里我们只讨论scoped_lock。
|
||||
scoped_lock是定义在namespace boost::detail::thread下的,为了方便我们使用(也为了方便设计者),mutex使用了下面的typedef:
|
||||
typedef detail::thread::scoped_lock<mutex> scoped_lock;
|
||||
这样我们就可以通过:
|
||||
boost::mutex::scoped_lock
|
||||
来使用scoped_lock类模板了。
|
||||
由于scoped_lock的作用仅在于对mutex加锁/解锁(即使mutex EnterCriticalSection/LeaveCriticalSection),因此,它的接口也很简单,除了构造函数外,仅有lock/unlock/locked(判断是否已加锁),及类型转换操作符void*,一般我们不需要显式调用这些方法,因为scoped_lock的构造函数是这样定义的:
|
||||
|
||||
1 explicit scoped_lock(Mutex& mx, bool initially_locked=true)
|
||||
2 : m_mutex(mx), m_locked(false)
|
||||
3 {
|
||||
4 if (initially_locked) lock();
|
||||
5 }
|
||||
|
||||
注:m_mutex是一个mutex的引用。
|
||||
因此,当我们不指定initially_locked参数构造一个scoped_lock对象时,scoped_lock会自动对所绑定的mutex加锁,而析构函数会检查是否加锁,若已加锁,则解锁;当然,有些情况下,我们可能不需要构造时自动加锁,这样就需要自己调用lock方法。后面的condition、barrier也会调用scoped_lock的lock、unlock方法来实现部分方法。
|
||||
正因为scoped_lock具有可在构造时加锁,析构时解锁的特性,我们经常会使用局部变量来实现对mutex的独占访问。
|
||||
|
||||
1 #include <boost/thread/thread.hpp>
|
||||
2 #include <boost/thread/mutex.hpp>
|
||||
3 #include <iostream>
|
||||
4
|
||||
5 boost::mutex io_mutex;
|
||||
6
|
||||
7 void count() // worker function
|
||||
8 {
|
||||
9 for (int i = 0; i < 10; ++i)
|
||||
10 {
|
||||
11 boost::mutex::scoped_lock lock(io_mutex);
|
||||
12 std::cout << i << std::endl;
|
||||
13 }
|
||||
14 }
|
||||
15
|
||||
16 int main(int argc, char* argv[])
|
||||
17 {
|
||||
18 boost::thread thrd1(&count);
|
||||
19 boost::thread thrd2(&count);
|
||||
20 thrd1.join();
|
||||
21 thrd2.join();
|
||||
22
|
||||
23 return 0;
|
||||
24 }
|
||||
|
||||
在每次输出信息时,为了防止整个输出过程被其它线程打乱,通过对io_mutex加锁(进入临界区),从而保证了输出的正确性。
|
||||
在使用 scoped_lock时,我们有时候需要使用全局锁(定义一个全局mutex,当需要独占访问全局资源时,以该全局mutex为参数构造一个 scoped_lock对象即可。全局mutex可以是全局变量,也可以是类的静态方法等),有时候则需要使用对象锁(将mutex定义成类的成员变量),应该根据需要进行合理选择。
|
||||
Java的synchronized可用于对方法加锁,对代码段加锁,对对象加锁,对类加锁(仍然是对象级的),这几种加锁方式都可以通过上面讲的对象锁来模拟;相反,在Java中实现全局锁好像有点麻烦,必须将请求封装到类中,以转换成上面的四种 synchronized形式之一。
|
||||
|
||||
condition
|
||||
condition的接口如下:
|
||||
|
||||
1 class condition : private boost::noncopyable // Exposition only
|
||||
2 {
|
||||
3 public:
|
||||
4 // construct/copy/destruct
|
||||
5 condition();
|
||||
6 ~condition();
|
||||
7
|
||||
8 // notification
|
||||
9 void notify_one();
|
||||
10 void notify_all();
|
||||
11
|
||||
12 // waiting
|
||||
13 template<typename ScopedLock> void wait(ScopedLock&);
|
||||
14 template<typename ScopedLock, typename Pred> void wait(ScopedLock&, Pred);
|
||||
15 template<typename ScopedLock>
|
||||
16 bool timed_wait(ScopedLock&, const boost::xtime&);
|
||||
17 template<typename ScopedLock, typename Pred>
|
||||
18 bool timed_wait(ScopedLock&, Pred);
|
||||
19 };
|
||||
|
||||
其中wait用于等待某个condition的发生,而timed_wait则提供具有超时的wait功能,notify_one用于唤醒一个等待该condition发生的线程,notify_all则用于唤醒所有等待该condition发生的线程。
|
||||
|
||||
由于condition的语义相对较为复杂,它的实现也是整个boost::thread库中最复杂的(对Windows版本而言,对支持pthread的版本而言,由于pthread已经提供了pthread_cond_t,使得condition实现起来也十分简单),下面对wait和notify_one进行简要分析。
|
||||
condition内部包含了一个condition_impl对象,由该对象执行来处理实际的wait、notify_one...等操作。
|
||||
|
||||
下面先对condition_impl进行简要分析。
|
||||
condition_impl在其构造函数中会创建两个Semaphore(信号量):m_gate、m_queue,及一个Mutex(互斥体,跟boost::mutex类似,但boost::mutex是基于CriticalSection<临界区>的):m_mutex,其中:
|
||||
m_queue
|
||||
相当于当前所有等待线程的等待队列,构造函数中调用CreateSemaphore来创建Semaphore时,lMaximumCount参数被指定为(std::numeric_limits<long>::max)(),即便如此,condition的实现者为了防止出现大量等待线程的情况(以至于超过了long的最大值),在线程因执行condition::wait进入等待状态时会先:
|
||||
WaitForSingleObject(reinterpret_cast<HANDLE>(m_queue), INFINITE);
|
||||
以等待被唤醒,但很难想象什么样的应用需要处理这么多线程。
|
||||
m_mutex
|
||||
用于内部同步的控制。
|
||||
但对于m_gate我很奇怪,我仔细研究了一下condition_imp的实现,还是不明白作者引入m_gate这个变量的用意何在,既然已经有了用于同步控制的m_mutex,再引入一个m_gate实在让我有点不解。
|
||||
|
||||
以下是condition::wait调用的do_wait方法简化后的代码:
|
||||
|
||||
1 template <typename M>
|
||||
2 void do_wait(M& mutex)
|
||||
3 {
|
||||
4 m_impl.enter_wait();
|
||||
5 lock_ops::unlock(mutex, state); //对传入的scoped_lock对象解锁,以便别的线程可以对其进行加锁,并执行某些处理,否则,本线程等待的condition永远不会发生(因为没有线程可以获得访问资源的权利以使condition发生)
|
||||
6 m_impl.do_wait(); //执行等待操作,等待其它线程执行notify_one或notify_all操作以获得
|
||||
7 lock_ops::lock(mutex, state); //重新对scoped_lock对象加锁,获得独占访问资源的权利
|
||||
8 }
|
||||
condition::timed_wait的实现方法与此类似,而notify_one、notify_all仅将调用请求转发给m_impl,就不多讲了。
|
||||
|
||||
虽然condition的内部实现比较复杂,但使用起来还是比较方便的。下面是一个使用condition的多Producer-多Consumer同步的例子:
|
||||
1 #include <boost/thread/thread.hpp>
|
||||
2 #include <boost/thread/mutex.hpp>
|
||||
3 #include <boost/thread/condition.hpp>
|
||||
4 #include <boost/thread/xtime.hpp>
|
||||
5
|
||||
6 #include <iostream>
|
||||
7 #include <time.h> // for time()
|
||||
8
|
||||
9 #include <Windows.h> // for Sleep, change it for other platform, we can use
|
||||
10 // boost::thread::sleep, but it's too inconvenient.
|
||||
11
|
||||
12 typedef boost::mutex::scoped_lock scoped_lock;
|
||||
13 boost::mutex io_mutex;
|
||||
14
|
||||
15 class Product
|
||||
16 {
|
||||
17 int num;
|
||||
18 public:
|
||||
19 Product(int num) : num(num) {}
|
||||
20
|
||||
21 friend std::ostream& operator<< (std::ostream& os, Product& product)
|
||||
22 {
|
||||
23 return os << product.num;
|
||||
24 }
|
||||
25 };
|
||||
26
|
||||
27 class Mediator
|
||||
28 {
|
||||
29 private:
|
||||
30 boost::condition cond;
|
||||
31 boost::mutex mutex;
|
||||
32
|
||||
33 Product** pSlot; // product buffer/slot
|
||||
34 unsigned int slotCount, // buffer size
|
||||
35 productCount; // current product count
|
||||
36 bool stopFlag; // should all thread stop or not
|
||||
37
|
||||
38 public:
|
||||
39 Mediator(const int slotCount) : slotCount(slotCount), stopFlag(false), productCount(0)
|
||||
40 {
|
||||
41 pSlot = new Product*[slotCount];
|
||||
42 }
|
||||
43
|
||||
44 virtual ~Mediator()
|
||||
45 {
|
||||
46 for (int i = 0; i < static_cast<int>(productCount); i++)
|
||||
47 {
|
||||
48 delete pSlot[i];
|
||||
49 }
|
||||
50 delete [] pSlot;
|
||||
51 }
|
||||
52
|
||||
53 bool Stop() const { return stopFlag; }
|
||||
54 void Stop(bool) { stopFlag = true; }
|
||||
55
|
||||
56 void NotifyAll() // notify all blocked thread to exit
|
||||
57 {
|
||||
58 cond.notify_all();
|
||||
59 }
|
||||
60
|
||||
61 bool Put( Product* pProduct)
|
||||
62 {
|
||||
63 scoped_lock lock(mutex);
|
||||
64 if (productCount == slotCount)
|
||||
65 {
|
||||
66 {
|
||||
67 scoped_lock lock(io_mutex);
|
||||
68 std::cout << "Buffer is full. Waiting" << std::endl;
|
||||
69 }
|
||||
70 while (!stopFlag && (productCount == slotCount))
|
||||
71 cond.wait(lock);
|
||||
72 }
|
||||
73 if (stopFlag) // it may be notified by main thread to quit.
|
||||
74 return false;
|
||||
75
|
||||
76 pSlot[ productCount++ ] = pProduct;
|
||||
77 cond.notify_one(); // this call may cause *pProduct to be changed if it wakes up a consumer
|
||||
78
|
||||
79 return true;
|
||||
80 }
|
||||
81
|
||||
82 bool Get(Product** ppProduct)
|
||||
83 {
|
||||
84 scoped_lock lock(mutex);
|
||||
85 if (productCount == 0)
|
||||
86 {
|
||||
87 {
|
||||
88 scoped_lock lock(io_mutex);
|
||||
89 std::cout << "Buffer is empty. Waiting" << std::endl;
|
||||
90 }
|
||||
91 while (!stopFlag && (productCount == 0))
|
||||
92 cond.wait(lock);
|
||||
93 }
|
||||
94 if (stopFlag) // it may be notified by main thread to quit.
|
||||
95 {
|
||||
96 *ppProduct = NULL;
|
||||
97 return false;
|
||||
98 }
|
||||
99
|
||||
100 *ppProduct = pSlot[--productCount];
|
||||
101 cond.notify_one();
|
||||
102
|
||||
103 return true;
|
||||
104 }
|
||||
105 };
|
||||
106
|
||||
107 class Producer
|
||||
108 {
|
||||
109 private:
|
||||
110 Mediator* pMediator;
|
||||
111 static unsigned int num;
|
||||
112 unsigned int id; // Producer id
|
||||
113
|
||||
114 public:
|
||||
115 Producer(Mediator* pMediator) : pMediator(pMediator) { id = num++; }
|
||||
116
|
||||
117 void operator() ()
|
||||
118 {
|
||||
119 Product* pProduct;
|
||||
120 srand( (unsigned)time( NULL ) + id ); // each thread need to srand differently
|
||||
121 while (!pMediator->Stop())
|
||||
122 {
|
||||
123 pProduct = new Product( rand() % 100 );
|
||||
124 // must print product info before call Put, as Put may wake up a consumer
|
||||
125 // and cause *pProuct to be changed
|
||||
126 {
|
||||
127 scoped_lock lock(io_mutex);
|
||||
128 std::cout << "Producer[" << id << "] produces Product["
|
||||
129 << *pProduct << "]" << std::endl;
|
||||
130 }
|
||||
131 if (!pMediator->Put(pProduct)) // this function only fails when it is notified by main thread to exit
|
||||
132 delete pProduct;
|
||||
133
|
||||
134 Sleep(100);
|
||||
135 }
|
||||
136 }
|
||||
137 };
|
||||
138
|
||||
139 unsigned int Producer::num = 1;
|
||||
140
|
||||
141 class Consumer
|
||||
142 {
|
||||
143 private:
|
||||
144 Mediator* pMediator;
|
||||
145 static unsigned int num;
|
||||
146 unsigned int id; // Consumer id
|
||||
147
|
||||
148 public:
|
||||
149 Consumer(Mediator* pMediator) : pMediator(pMediator) { id = num++; }
|
||||
150
|
||||
151 void operator() ()
|
||||
152 {
|
||||
153 Product* pProduct = NULL;
|
||||
154 while (!pMediator->Stop())
|
||||
155 {
|
||||
156 if (pMediator->Get(&pProduct))
|
||||
157 {
|
||||
158 scoped_lock lock(io_mutex);
|
||||
159 std::cout << "Consumer[" << id << "] is consuming Product["
|
||||
160 << *pProduct << "]" << std::endl;
|
||||
161 delete pProduct;
|
||||
162 }
|
||||
163
|
||||
164 Sleep(100);
|
||||
165 }
|
||||
166 }
|
||||
167 };
|
||||
168
|
||||
169 unsigned int Consumer::num = 1;
|
||||
170
|
||||
171 int main()
|
||||
172 {
|
||||
173 Mediator mediator(2); // we have only 2 slot to put products
|
||||
174
|
||||
175 // we have 2 producers
|
||||
176 Producer producer1(&mediator);
|
||||
177 boost::thread thrd1(producer1);
|
||||
178 Producer producer2(&mediator);
|
||||
179 boost::thread thrd2(producer2);
|
||||
180 // and we have 3 consumers
|
||||
181 Consumer consumer1(&mediator);
|
||||
182 boost::thread thrd3(consumer1);
|
||||
183 Consumer consumer2(&mediator);
|
||||
184 boost::thread thrd4(consumer2);
|
||||
185 Consumer consumer3(&mediator);
|
||||
186 boost::thread thrd5(consumer3);
|
||||
187
|
||||
188 // wait 1 second
|
||||
189 Sleep(1000);
|
||||
190 // and then try to stop all threads
|
||||
191 mediator.Stop(true);
|
||||
192 mediator.NotifyAll();
|
||||
193
|
||||
194 // wait for all threads to exit
|
||||
195 thrd1.join();
|
||||
196 thrd2.join();
|
||||
197 thrd3.join();
|
||||
198 thrd4.join();
|
||||
199 thrd5.join();
|
||||
200
|
||||
201 return 0;
|
||||
202 }
|
||||
|
||||
barrier
|
||||
barrier类的接口定义如下:
|
||||
1 class barrier : private boost::noncopyable // Exposition only
|
||||
2 {
|
||||
3 public:
|
||||
4 // construct/copy/destruct
|
||||
5 barrier(size_t n);
|
||||
6 ~barrier();
|
||||
7
|
||||
8 // waiting
|
||||
9 bool wait();
|
||||
10 };
|
||||
|
||||
barrier类为我们提供了这样一种控制线程同步的机制:
|
||||
前n - 1次调用wait函数将被阻塞,直到第n次调用wait函数,而此后第n + 1次到第2n - 1次调用wait也会被阻塞,直到第2n次调用,依次类推。
|
||||
barrier::wait的实现十分简单:
|
||||
|
||||
1 barrier::barrier(unsigned int count)
|
||||
2 : m_threshold(count), m_count(count), m_generation(0)
|
||||
3 {
|
||||
4 if (count == 0)
|
||||
5 throw std::invalid_argument("count cannot be zero.");
|
||||
6 }
|
||||
7
|
||||
8 bool barrier::wait()
|
||||
9 {
|
||||
10 boost::mutex::scoped_lock lock(m_mutex); // m_mutex is the base of barrier and is initilized by it's default constructor.
|
||||
11 unsigned int gen = m_generation; // m_generation will be 0 for call 1~n-1, and 1 for n~2n - 1, and so on
|
||||
12
|
||||
13 if (--m_count == 0)
|
||||
14 {
|
||||
15 m_generation++; // cause m_generation to be changed in call n/2n/
|
||||
16 m_count = m_threshold; // reset count
|
||||
17 m_cond.notify_all(); // wake up all thread waiting here
|
||||
18 return true;
|
||||
19 }
|
||||
20
|
||||
21 while (gen == m_generation) // if m_generation is not changed, lock current thread.
|
||||
22 m_cond.wait(lock);
|
||||
23 return false;
|
||||
24 }
|
||||
|
||||
因此,说白了也不过是mutex的一个简单应用。
|
||||
以下是一个使用barrier的例子:
|
||||
|
||||
1 #include <boost/thread/thread.hpp>
|
||||
2 #include <boost/thread/barrier.hpp>
|
||||
3
|
||||
4 int i = 0;
|
||||
5 boost::barrier barr(3); // call barr.wait 3 * n times will release all threads in waiting
|
||||
6
|
||||
7 void thread()
|
||||
8 {
|
||||
9 ++i;
|
||||
10 barr.wait();
|
||||
11 }
|
||||
12
|
||||
13 int main()
|
||||
14 {
|
||||
15 boost::thread thrd1(&thread);
|
||||
16 boost::thread thrd2(&thread);
|
||||
17 boost::thread thrd3(&thread);
|
||||
18
|
||||
19 thrd1.join();
|
||||
20 thrd2.join();
|
||||
21 thrd3.join();
|
||||
22
|
||||
23 return 0;
|
||||
24 }
|
||||
|
||||
如果去掉其中thrd3相关的代码,将使得线程1、2一直处于wait状态,进而使得主线程无法退出。
|
||||
|
||||
xtime
|
||||
xtime是boost::thread中用来表示时间的一个辅助类,它是一个仅包含两个成员变量的结构体:
|
||||
|
||||
1 struct xtime
|
||||
2 {
|
||||
3 //
|
||||
4 xtime_sec_t sec;
|
||||
5 xtime_nsec_t nsec;
|
||||
6 };
|
||||
|
||||
condition::timed_wait、thread::sleep等涉及超时的函数需要用到xtime。
|
||||
需要注意的是,xtime表示的不是一个时间间隔,而是一个时间点,因此使用起来很不方便。为了方便使用xtime,boost提供了一些辅助的xtime操作函数,如xtime_get、xtime_cmp等。
|
||||
以下是一个使用xtime来执行sleep的例子(跟简单的一句Sleep比起来,实在是太复杂了),其中用到了xtime初始化函数xtime_get:
|
||||
1 #include <boost/thread/thread.hpp>
|
||||
2 #include <boost/thread/xtime.hpp>
|
||||
3 #include <iostream>
|
||||
4
|
||||
5 int main()
|
||||
6 {
|
||||
7 boost::xtime xt;
|
||||
8 boost::xtime_get(&xt, boost::TIME_UTC); // initialize xt with current time
|
||||
9 xt.sec += 1; // change xt to next second
|
||||
10 boost::thread::sleep(xt); // do sleep
|
||||
11
|
||||
12 std::cout << "1 second sleep over." << std::endl;
|
||||
13
|
||||
14 return 0;
|
||||
15 }
|
||||
|
||||
多线程编程中还有一个重要的概念:Thread Local Store(TLS,线程局部存储),在boost中,TLS也被称作TSS,Thread Specific Storage。
|
||||
boost::thread库为我们提供了一个接口简单的TLS的面向对象的封装,以下是tss类的接口定义:
|
||||
class tss
|
||||
{
|
||||
public:
|
||||
tss(boost::function1<void, void*>* pcleanup);
|
||||
void* get() const;
|
||||
void set(void* value);
|
||||
void cleanup(void* p);
|
||||
};
|
||||
|
||||
分别用于获取、设置、清除线程局部存储变量,这些函数在内部封装了TlsAlloc、TlsGetValue、TlsSetValue等API操作,将它们封装成了OO的形式。
|
||||
但boost将该类信息封装在detail名字空间内,即不推荐我们使用,当需要使用tss时,我们应该使用另一个使用更加方便的类:thread_specific_ptr,这是一个智能指针类,该类的接口如下:
|
||||
|
||||
1 class thread_specific_ptr : private boost::noncopyable // Exposition only
|
||||
2 {
|
||||
3 public:
|
||||
4 // construct/copy/destruct
|
||||
5 thread_specific_ptr();
|
||||
6 thread_specific_ptr(void (*cleanup)(void*));
|
||||
7 ~thread_specific_ptr();
|
||||
8
|
||||
9 // modifier functions
|
||||
10 T* release();
|
||||
11 void reset(T* = 0);
|
||||
12
|
||||
13 // observer functions
|
||||
14 T* get() const;
|
||||
15 T* operator->() const;
|
||||
16 T& operator*()() const;
|
||||
17 };
|
||||
|
||||
即可支持get、reset、release等操作。
|
||||
thread_specific_ptr类的实现十分简单,仅仅为了将tss类“改装”成智能指针的样子,该类在其构造函数中会自动创建一个tss对象,而在其析构函数中会调用默认参数的reset函数,从而引起内部被封装的tss对象被析构,达到“自动”管理内存分配释放的目的。
|
||||
|
||||
以下是一个运用thread_specific_ptr实现TSS的例子:
|
||||
1 #include <boost/thread/thread.hpp>
|
||||
2 #include <boost/thread/mutex.hpp>
|
||||
3 #include <boost/thread/tss.hpp>
|
||||
4 #include <iostream>
|
||||
5
|
||||
6 boost::mutex io_mutex;
|
||||
7 boost::thread_specific_ptr<int> ptr; // use this method to tell that this member will not shared by all threads
|
||||
8
|
||||
9 struct count
|
||||
10 {
|
||||
11 count(int id) : id(id) { }
|
||||
12
|
||||
13 void operator()()
|
||||
14 {
|
||||
15 if (ptr.get() == 0) // if ptr is not initialized, initialize it
|
||||
16 ptr.reset(new int(0)); // Attention, we pass a pointer to reset (actually set ptr)
|
||||
17
|
||||
18 for (int i = 0; i < 10; ++i)
|
||||
19 {
|
||||
20 (*ptr)++;
|
||||
21 boost::mutex::scoped_lock lock(io_mutex);
|
||||
22 std::cout << id << ": " << *ptr << std::endl;
|
||||
23 }
|
||||
24 }
|
||||
25
|
||||
26 int id;
|
||||
27 };
|
||||
28
|
||||
29 int main(int argc, char* argv[])
|
||||
30 {
|
||||
31 boost::thread thrd1(count(1));
|
||||
32 boost::thread thrd2(count(2));
|
||||
33 thrd1.join();
|
||||
34 thrd2.join();
|
||||
35
|
||||
36 return 0;
|
||||
37 }
|
||||
此外,thread库还提供了一个很有趣的函数,call_once,在tss::init的实现中就用到了该函数。
|
||||
该函数的声明如下:
|
||||
void call_once(void (*func)(), once_flag& flag);
|
||||
该函数的Windows实现通过创建一个Mutex使所有的线程在尝试执行该函数时处于等待状态,直到有一个线程执行完了func函数,该函数的第二个参数表示函数func是否已被执行,该参数往往被初始化成BOOST_ONCE_INIT(即0),如果你将该参数初始化成1,则函数func将不被调用,此时call_once相当于什么也没干,这在有时候可能是需要的,比如,根据程序处理的结果决定是否需要call_once某函数func。
|
||||
call_once在执行完函数func后,会将flag修改为1,这样会导致以后执行call_once的线程(包括等待在Mutex处的线程和刚刚进入call_once的线程)都会跳过执行func的代码。
|
||||
|
||||
需要注意的是,该函数不是一个模板函数,而是一个普通函数,它的第一个参数1是一个函数指针,其类型为void (*)(),而不是跟boost库的很多其它地方一样用的是function模板,不过这样也没有关系,有了boost::bind这个超级武器,想怎么绑定参数就随你的便了,根据boost的文档,要求传入的函数不能抛出异常,但从实现代码中好像不是这样。
|
||||
|
||||
以下是一个典型的运用call_once实现一次初始化的例子:
|
||||
|
||||
1 #include <boost/thread/thread.hpp>
|
||||
2 #include <boost/thread/once.hpp>
|
||||
3 #include <iostream>
|
||||
4
|
||||
5 int i = 0;
|
||||
6 int j = 0;
|
||||
7 boost::once_flag flag = BOOST_ONCE_INIT;
|
||||
8
|
||||
9 void init()
|
||||
10 {
|
||||
11 ++i;
|
||||
12 }
|
||||
13
|
||||
14 void thread()
|
||||
15 {
|
||||
16 boost::call_once(&init, flag);
|
||||
17 ++j;
|
||||
18 }
|
||||
19
|
||||
20 int main(int argc, char* argv[])
|
||||
21 {
|
||||
22 boost::thread thrd1(&thread);
|
||||
23 boost::thread thrd2(&thread);
|
||||
24 thrd1.join();
|
||||
25 thrd2.join();
|
||||
26
|
||||
27 std::cout << i << std::endl;
|
||||
28 std::cout << j << std::endl;
|
||||
29
|
||||
30 return 0;
|
||||
31 }
|
||||
结果显示,全局变量i仅被执行了一次++操作,而变量j则在两个线程中均执行了++操作。
|
||||
116
Zim/Programme/C++/boost/Boost_中的智能指针.txt
Normal file
@@ -0,0 +1,116 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-19T21:13:13+08:00
|
||||
|
||||
====== Boost 中的智能指针 ======
|
||||
Created Sunday 19 February 2012
|
||||
http://www.cnblogs.com/sld666666/archive/2010/12/16/1908265.html
|
||||
|
||||
这篇文章主要介绍 boost中的智能指针的使用。
|
||||
|
||||
内存管理是一个比较繁琐的问题,C++中有两个实现方案: __垃圾回收机制和智能指针__。垃圾回收机制因为性能等原因不被C++的大佬们推崇, 而智能指针被认为是解决C++内存问题的最优方案。
|
||||
|
||||
===== 1. 定义 =====
|
||||
一个智能指针就是__一个C++的对象, 这对象的行为像一个指针__,但是它却可以在其不需要的时候自动删除。注意这个“其不需要的时候”, 这可不是一个精确的定义。这个不需要的时候可以指好多方面:局部变量退出函数作用域、类的对象被析构……。所以boost定义了多个不同的智能指针来管理不同的场景。
|
||||
|
||||
shared_ptr<T> 内部维护一个引用计数器来判断此指针是不是需要被释放。是boost中最常用的智能指针了。
|
||||
scoped_ptr<t> 当这个指针的__作用域消失__之后自动释放
|
||||
intrusive_ptr<T> 也维护一个引用计数器,比shared_ptr有更好的性能。但是__要求T自己提供这个计数器__。
|
||||
weak_ptr<T> 弱指针,要**和shared_ptr 结合**使用
|
||||
shared_array<T> 和shared_ptr相似,但是访问的是数组
|
||||
scoped_array<T> 和scoped_ptr相似,但是访问的是数组
|
||||
|
||||
===== 2. Boost::scoped_ptr<T> =====
|
||||
|
||||
scoped_ptr 是boost中最简单的智能指针。scoped_ptr的目的也是很简单, 当一个__指针离开其作用域时候,释放相关资源__。特别注意的一定就是scoped_ptr __不能共享指针的所有权也不能转移所有权__。也就是说这个内存地址就只能给的声明的变量用,不能给其他使用。
|
||||
|
||||
下面是scoped_ptr的几个特点:
|
||||
|
||||
* scoped_ptr的效率和空间的消耗内置的指针差不多。
|
||||
* scoped_ptr__不能用在标准库的容器上__。(用shared_ptr代替, **因为放入标准容器的对象需要能被复制和赋值**)
|
||||
* scoped_ptr 不能指向一块能够动态增长的内存区域(用scoped_array代替)
|
||||
|
||||
1 class test
|
||||
2 {
|
||||
3 public:
|
||||
4 void print()
|
||||
5 {
|
||||
6 cout << "test print now" <<endl;
|
||||
7 }
|
||||
8 };
|
||||
9 int _tmain(int argc, _TCHAR* argv[])
|
||||
10 {
|
||||
11 boost::scoped_ptr<__test__> x(new test);
|
||||
12 x->print();
|
||||
13 return 0;
|
||||
14 }
|
||||
|
||||
===== 3.Boost::shared_ptr<T> =====
|
||||
|
||||
shared_ptr 具有如下几个特点:
|
||||
|
||||
* 在内部维护一个引用计数器, 当有一个指针指向这块内存区域是引用计数+1, 反之-1, 如果没有任何指针指向这块区域, **引用计数器为0,释放内存区域**。
|
||||
* 可以__共享和转移所有权__。
|
||||
* 可以被标准库的容器所使用
|
||||
* 不能指向一块动态增长的内存(用share_array代替)
|
||||
|
||||
我们可以看下如下例子:
|
||||
|
||||
1 int _tmain(int argc, _TCHAR* argv[])
|
||||
2 {
|
||||
3 boost::shared_ptr<test> ptr_1(new test);
|
||||
4 ptr_1->print();//引用计数为1
|
||||
5 boost::shared_ptr<test> ptr_2 = ptr_1; //共享所有权
|
||||
6 ptr_2->print();//引用计数为2
|
||||
7 ptr_1->print();// 引用计数还是为2
|
||||
8 return 0;
|
||||
9 }
|
||||
|
||||
===== 4. Boost::intrusive_ptr<T> =====
|
||||
|
||||
intrusive_ptr 的主要和share_ptr一样, 对比share_ptr,其效率更高,但是需要自己维护一个引用计数器, 这里不做详细介绍。
|
||||
|
||||
===== 5. Boost::weak_ptr<T> =====
|
||||
|
||||
* weak_ptr 就是一个弱指针。__weak_ptr 被shared_ptr控制__, 它可以通过share_ptr的构造函数或者lock成员函数转化为share_ptr。
|
||||
* weak_ptr的一个最大特点就是它共享一个share_ptr的内存,但是无论是构造还是析构一个weak_ptr 都__不会影响引用计数器__。
|
||||
|
||||
1 int _tmain(int argc, _TCHAR* argv[])
|
||||
2 {
|
||||
3 boost::shared_ptr<test> sharePtr(new test);;
|
||||
4 boost::weak_ptr<test> weakPtr(sharePtr); //**不增加**sharPtr的计数
|
||||
5 //weakPtr 就是用來保存指向這塊內存區域的指針的
|
||||
6 //干了一大堆其他事情
|
||||
7 boost::shared_ptr<test> sharePtr_2 = __weakPtr.lock()__;
|
||||
8 if (sharePtr_2)
|
||||
9 sharePtr_2->print();
|
||||
10 return 0;
|
||||
11 }
|
||||
|
||||
===== 6. Boost::shared_array<T> 和Boost::scoped_array<T> =====
|
||||
|
||||
前面提到过shared_ptr和scoped_ptr不能用于数组的内存(new [ ]),所以shared_array和scoped_array就是他们的代替品。我们可以看下shared_array的用法
|
||||
|
||||
1 int _tmain(int argc, _TCHAR* argv[])
|
||||
2 {
|
||||
3 const int size = 10;
|
||||
4 boost::shared_array<test> a(new test[size]);
|
||||
5 for (int i = 0; i < size; ++i)
|
||||
6 a[i].print();
|
||||
7 return 0;
|
||||
8 }
|
||||
|
||||
===== 7. 使用智能指针的几个注意点 =====
|
||||
|
||||
下面是几个使用智能指针需要注意的地方:
|
||||
|
||||
* 声明一个智能指针的时候要__立即给它实例化, 而且一定不能手动释放它__。
|
||||
* …_ptr<T> 不是T* 类型。所以:
|
||||
a: 声明的时候要…_ptr<T> 而不是….._ptr<T*>
|
||||
b:不能把T* 型的指针赋值给它
|
||||
c: 不能写ptr=NULl, 而用__ptr.reset()__代替。
|
||||
* 不能循环引用。
|
||||
* 不要声明临时的share_ptr, 然后把这个指针传递给一个函数
|
||||
|
||||
8. 总结
|
||||
智能指针使用上还是比较简单的, 而且能比较有效得解决C++内存泄露的问题,各位使用C++的童鞋赶快用起来吧。
|
||||
6
Zim/Programme/C++/boost/Boost学习系列.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-14T19:30:46+08:00
|
||||
|
||||
====== Boost学习系列 ======
|
||||
Created Tuesday 14 February 2012
|
||||
313
Zim/Programme/C++/boost/Smart_Pointers_in_Boost.txt
Normal file
@@ -0,0 +1,313 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-14T14:27:48+08:00
|
||||
|
||||
====== Smart Pointers in Boost ======
|
||||
Created Tuesday 14 February 2012
|
||||
|
||||
http://drdobbs.com/cpp/184401507
|
||||
|
||||
Welcome to Boost, the peerless community of C++ innovators. If you can't find what you need in the Standard C++ library, chances are Boost has it production-ready.
|
||||
|
||||
===== Introducing Boost =====
|
||||
|
||||
According to the Boost website, Boost is "a repository of free, portable, peer-reviewed C++ libraries. Boost acts as a proving ground for new libraries, particularly those that work well with the** ISO C++ Standard library**." But Boost is more than a collection of libraries. It is also the focus of a rapidly growing community of developers who create, use, and discuss the Boost libraries. Beyond making these excellent libraries available, Boost also offers a place to learn. The libraries are examples of __solid library design__ in a world that seldom seems to look further than the next release. Joining the discussions on the Boost mailing list (either actively or just by listening) is a good way to improve your understanding of library design problems and solutions. Boost also provides a rapidly growing Boost-Users mailing list that focuses on questions related to using the libraries.
|
||||
|
||||
The quality and the technological standard of the Boost libraries are __amazing__. And the Boost portability standard ensures that when you move your code to another platform, you'll know the libraries will __still work__. The current release, Boost 1.25.0, consists of libraries ranging from **smart pointers **to **regular expressions** to **a portable threading library**. Boost currently supports 35 libraries, all of which have been thoroughly reviewed by community members. These libraries can be freely used, and many of them are being used in commercial applications.
|
||||
|
||||
Boost is one of the strongest C++ communities. Among the 2,000 members are many of the __world's top C++ programmers__. Members stay involved because they like working with some of the best minds in programming. They also know their efforts are bound to have an impact on the C++ community, since much of what you see in Boost is a strong candidate for inclusion in the next C++ Standard.
|
||||
|
||||
The best way to get acquainted with Boost is **to tour the Boost libraries**. In this article, I'll introduce you to the **Boost smart pointer** library smart_ptr. smart_ptr is a good example of the innovation and sound design you'll find in Boost. I encourage you to visit the Boost website (www.boost.org) for more on the other 34 libraries in the Boost collection.
|
||||
|
||||
===== Smart Pointers =====
|
||||
|
||||
One of the smaller (size-wise) Boost libraries is smart_ptr. smart_ptr is one of the libraries that I think is going to end up in the C++ Standard. This article discusses the Boost smart_ptr library. But first, I'll begin with a brief introduction to smart pointers.
|
||||
|
||||
===== 30-Second Introduction to Smart Pointers =====
|
||||
|
||||
Smart pointers are __classes __that store pointers to dynamically allocated __(heap) objects__. They behave much like built-in C++ pointers except that they __automatically delete the object__ pointed to at the appropriate time. Smart pointers are particularly useful in the face of exceptions as they ensure proper destruction of dynamically allocated objects. They can also be used to__ keep track__ of dynamically allocated objects shared by multiple owners.
|
||||
|
||||
Actually, smart pointers can do more, such as __handle thread safety__, provide copy-on-write, ensure protocols, and provide remote communication services. There are ways to create generic smart pointers for these ESPs (Extremely Smart Pointers), but those won't be covered here. (See [1] for in-depth coverage of this topic. By the way, Alexandrescu is currently considering submitting his C++ library Loki to Boost.)
|
||||
|
||||
Most uses of smart pointers are __for lifetime control__, period. They implement **operator-> and operator*** to yield** the raw pointer**, allowing the smart pointer to look like an ordinary pointer.
|
||||
|
||||
One such class comes with the standard library:** std::auto_ptr**. It is designed to handle ownership of a resource, but lacks support for **reference counting** and **arrays**. Also, std::auto_ptr transfers ownership when it is copied. In many cases, you need more and/or different functionality. Enter the smart_ptr classes.
|
||||
|
||||
===== smart_ptr Classes =====
|
||||
|
||||
The smart pointers in Boost are:
|
||||
|
||||
* **scoped_ptr**, which handles** sole ownership** of single objects; unlike std::auto_ptr, scoped_ptr __cannot be copied__
|
||||
* scoped_array, which is similar to scoped_ptr, but handles arrays
|
||||
* shared_ptr, which allows object ownership to __be shared__
|
||||
* shared_array, which allows sharing of ownership for arrays
|
||||
|
||||
===== • scoped_ptr =====
|
||||
|
||||
The scoped_ptr smart pointer differs from std::auto_ptr in that it__ doesn't transfer ownership__. In fact, it explicitly **forbids** any attempt to do so! This is important for any scenario where you need to be sure that there is only ever one owner to a pointer. Without scoped_ptr, you would probably be inclined to use std::auto_ptr, but take a look at the following code:
|
||||
|
||||
auto_ptr<string> MyOwnString
|
||||
(new string("This is mine to keep!"));
|
||||
auto_ptr<string> NoItsMine(MyOwnString);
|
||||
|
||||
cout << *MyOwnString << endl; // Boom
|
||||
|
||||
This code will __obviously fail__, since ownership of the string has been transferred to NoItsMine. This is not a design flaw in std::auto_ptr — it is a feature. However, when you need MyOwnString to be just that, you'll want to use scoped_ptr:
|
||||
|
||||
scoped_ptr<string> MyOwnString
|
||||
(new string("This is mine to keep for real!"));
|
||||
// __Compiler error__ - there is no copy constructor.
|
||||
scoped_ptr<string> TryingToTakeItAnyway
|
||||
(MyOwnString);
|
||||
|
||||
The scoped_ptr accomplishes this behavior by **inheriting **from__ boost::noncopyable__ (found in the library **Boost.utility**). The non-copyable class declares the copy constructor and the assignment operator as__ private__.
|
||||
|
||||
===== • scoped_array =====
|
||||
|
||||
scoped_array is obviously the equivalent of scoped_ptr, but __for arrays.__ You'll get no help from the standard library here — unless of course you're using std::vector, which is the right way to go in most cases.
|
||||
|
||||
typedef __tuples::tuple<string, int>__ ArrayTuple;
|
||||
|
||||
scoped_array<ArrayTuple> MyArray(new ArrayTuple[10]);
|
||||
tuples::get<0>(MyArray[5]) =
|
||||
"The library Tuples is also part of Boost";
|
||||
|
||||
Tuples are collections of elements — for example **pairs, triples, and quadruples**. A typical use of tuples is__ returning multiple values from a function__. The __Boost Tuple Library __can be thought of as an extended generalization of the **standard library's pair**, and it currently works with up to 10 tuple elements. It supports tuple streaming, comparison, assignment, unpacking, and more. For more information about the Boost Tuple Library, see [3].
|
||||
|
||||
As the scoped_array goes out of scope, delete[] will be properly called. This eliminates a common mistake, namely invoking the wrong operator delete.
|
||||
|
||||
===== • shared_ptr =====
|
||||
|
||||
Here's an offering that you won't find in the standard library — __a reference-counted smart pointer.__ Most of you have rolled your own smart pointers, and there is plenty of literature regarding the issues involved in reference counting. One of the most important details is how the reference count is implemented — __intrusive__, which means that you add support to the classes being reference counted, or __non-intrusive__, which means you don't. __The Boost shared_ptr is non-intrusive__, and the implementation uses a reference counter allocated from the **heap**. There has been much discussion about providing parameterized strategies to best suit any given situation, but in the end it was decided against to focus on usability. Don't expect the discussions to go away though.
|
||||
|
||||
The usage of shared_ptr does what you'd expect: it takes responsibility of deleting the pointee when **there are no more users of the instance**, and it shares its pointee freely.
|
||||
|
||||
void PrintIfString(const __any__& Any) {
|
||||
if (const shared_ptr<string>__* __s =
|
||||
__any_cast__<shared_ptr<string> >(__&Any__)) {
|
||||
cout << **s << endl;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
std::vector<__boost::any__> Stuff;
|
||||
|
||||
shared_ptr<string> SharedString1
|
||||
(new string("Share me. By the way,
|
||||
Boost.any is another useful Boost
|
||||
library"));
|
||||
|
||||
shared_ptr<string> SharedString2
|
||||
(SharedString1);
|
||||
shared_ptr<int> SharedInt1
|
||||
(new int(42));
|
||||
shared_ptr<int> SharedInt2
|
||||
(SharedInt1);
|
||||
|
||||
Stuff.push_back(SharedString1);
|
||||
Stuff.push_back(SharedString2);
|
||||
Stuff.push_back(SharedInt1);
|
||||
Stuff.push_back(SharedInt2);
|
||||
|
||||
// Print the strings
|
||||
__for_each__(Stuff.begin(), Stuff.end(),
|
||||
PrintIfString);
|
||||
|
||||
**Stuff.clear();**
|
||||
|
||||
// The pointees of the shared_ptr's
|
||||
// will be released on leaving scope
|
||||
return 0;
|
||||
}
|
||||
|
||||
The __any library __offers a way to store just about anything [2] [4]. The requirements on contained types are that they are __CopyConstructible__, the destructor must not throw, and they should typically be __Assignable__. How do we store and pass "anything"? Indiscriminate types (read void*) can refer to just about anything, but that means throwing type safety (and knowledge) out the door. Using any, **type safety is preserved**. All types satisfying the requirements for any can be assigned, but** extraction demands knowing the exact type**. any_cast is the key for unlocking the value held by any. __any_cast works like dynamic_cast__ — casts to pointer types succeed or fail __by returning a null pointer__ whereas casts to reference types throw an exception (bad_any_cast) on failure.
|
||||
|
||||
* ===== shared_array =====
|
||||
|
||||
|
||||
Again, shared_array is the equivalent of shared_ptr, but it is used with arrays.
|
||||
|
||||
shared_array<Base> MyStrings( new Base[20] );
|
||||
|
||||
|
||||
===== Diving into the shared_ptr Implementation =====
|
||||
|
||||
Creating a simple smart pointer is fairly easy. Creating a smart pointer that will work with most compilers is more difficult. Creating a smart pointer with exception safety in mind is a lot more difficult.** Boost::shared_ptr** does it all, and here's how. (Note: includes, broken compiler fixes, and parts of the implementation are omitted, but you'll find them in Boost.smart_ptr).
|
||||
|
||||
First, the class definition: obviously, smart pointers are (almost always) templates.
|
||||
|
||||
template<typename T> class shared_ptr {
|
||||
|
||||
The public interface:
|
||||
|
||||
explicit shared_ptr(T* p =0) : px(p) {
|
||||
// fix: prevent leak if new throws
|
||||
try { pn = new long(1); }
|
||||
catch (...) { checked_delete(p); throw; }
|
||||
}
|
||||
|
||||
Now, in the constructor there are two things that are easily forgotten. The constructor is explicit, just like most constructors that can take a single argument. The other important thing to note is that the heap allocation of the reference counter is protected by a try-catch block. Without it, you would have a flawed smart pointer that wouldn't do its only job if the reference counter can't be allocated.
|
||||
|
||||
|
||||
~shared_ptr() { dispose(); }
|
||||
|
||||
The destructor has another important task: if the reference count goes down to zero, it should safely delete the pointee. The destructor delegates this task to another method: dispose.
|
||||
|
||||
|
||||
void dispose() { if (—*pn == 0)
|
||||
{ checked_delete(px); delete pn; } }
|
||||
|
||||
As you can see, the reference count (pn) is decremented. If it comes down to zero, checked_delete is called on the pointee (px), and then the reference count (pn) is also deleted.
|
||||
|
||||
So, what does checked_delete do? This handy function (which you'll find in Boost.utility) makes sure that the pointer represents a complete type. Do you have that in your own smart pointer classes?
|
||||
|
||||
Now, here's the first assignment operator:
|
||||
|
||||
template<typename Y> shared_ptr& operator=
|
||||
(const shared_ptr<Y>& r) {
|
||||
share(r.px,r.pn);
|
||||
return *this;
|
||||
}
|
||||
|
||||
This is a member template, and if it had not been, there are two scenarios:
|
||||
|
||||
If there is no parameterized copy constructor, assignment of the type Base = Derived won't work.
|
||||
If there is a parameterized copy constructor, assignments will work, but it involves creating an unnecessary temporary smart_ptr.
|
||||
|
||||
Again, this shows you a very good reason why you shouldn't roll your own smart pointers — these are not obvious issues.
|
||||
|
||||
The actual work of this assignment operator is done by share:
|
||||
|
||||
void share(T* rpx, long* rpn) {
|
||||
if (pn != rpn) { // Q: why not px != rpx?
|
||||
// A: fails when both == 0
|
||||
++*rpn; // done before dispose() in case
|
||||
// rpn transitively dependent on
|
||||
// *this (bug reported by Ken Johnson)
|
||||
dispose();
|
||||
px = rpx;
|
||||
pn = rpn;
|
||||
}
|
||||
}
|
||||
|
||||
Note that the comparison for self assignment (or rather self sharing) is done by comparing the reference counters, not the pointers. Why? They both can be zero, but not necessarily the same.
|
||||
|
||||
template<typename Y> shared_ptr
|
||||
(const shared_ptr<Y>& r) : px(r.px) { // never throws
|
||||
++*(pn = r.pn);
|
||||
}
|
||||
|
||||
This version is a templated copy constructor. See above for a good reason why.
|
||||
|
||||
The assignment operator and copy constructor also have non-templated versions:
|
||||
|
||||
|
||||
shared_ptr(const shared_ptr& r) :
|
||||
// never throws
|
||||
px(r.px) { ++*(pn = r.pn); }
|
||||
shared_ptr& operator=
|
||||
(const shared_ptr& r) {
|
||||
share(r.px,r.pn);
|
||||
return *this;
|
||||
}
|
||||
|
||||
The reset function is true to its name by resetting the pointee. Very handy if you need to destroy a pointee before leaving scope, or simply to invalidate some cached values.
|
||||
|
||||
|
||||
__void reset__(T* p=0) {
|
||||
// fix: self-assignment safe
|
||||
if ( px == p ) return;
|
||||
if (—*pn == 0)
|
||||
{ checked_delete(px); }
|
||||
else { // allocate new reference
|
||||
// counter
|
||||
// fix: prevent leak if new throws
|
||||
try { pn = new long; }
|
||||
catch (...) {
|
||||
// undo effect of —*pn above to
|
||||
// meet effects guarantee
|
||||
++*pn;
|
||||
checked_delete(p);
|
||||
throw;
|
||||
} // catch
|
||||
} // allocate new reference counter
|
||||
*pn = 1;
|
||||
px = p;
|
||||
} // reset
|
||||
|
||||
Again, note the care that has been taken to avoid potential leaks and to maintain exception safety.
|
||||
|
||||
Then you have the operators that enable the "smart" of the smart pointer:
|
||||
|
||||
|
||||
// never throws
|
||||
__T& operator*() __const { return *px; }
|
||||
// never throws
|
||||
__T* operator->()__ const { return px; }
|
||||
// never throws
|
||||
__T* get() __const { return px; }
|
||||
|
||||
Just a note: there are smart pointers that implement a cast operator to T*. This is not a good idea, and more often than not, you'll burn your fingers. The inconvenience of having to write get disables the compiler from playing games with your sanity.
|
||||
|
||||
I think it was Andrei Alexandrescu who said "If your smart pointer behaves exactly like dumb pointers, they are dumb pointers." True enough.
|
||||
|
||||
In closing, here are a couple of convenient functions.
|
||||
|
||||
long use_count() const
|
||||
{ return *pn; } // never throws
|
||||
bool unique() const
|
||||
{ return *pn == 1; } // never throws
|
||||
|
||||
The names are self-explanatory, right?
|
||||
|
||||
There is a lot more to mention about Boost.smart_ptr (such as specializations for std::swap and std::less, members for compatibility and convenience together with std::auto_ptr, etc.), but I'm running out of space. See smart_ptr.hpp in the Boost distribution (<www.boost.org>) for the gory details. Even without those extras, isn't this indeed a very smart pointer?
|
||||
|
||||
===== Conclusion =====
|
||||
|
||||
This has been a short introduction to the world of Boost. Anyone is welcome to join, and frankly, I think that most C++ programmers have very good reasons to do so. I'd like to thank Beman Dawes, David Abrahams, and Jens Maurer for their help in answering questions and sharing their opinions (see sidebar, "Answers from the Original Boosters") on the next page.
|
||||
|
||||
See you at Boost!
|
||||
|
||||
Notes and References
|
||||
|
||||
[1] Andrei Alexandrescu. Modern C++ Design (Addison-Wesley, 2001).
|
||||
|
||||
[2] Boost, www.boost.org.
|
||||
|
||||
[3] Jaako Jarvi. Tuple Types and Multiple Return Values, C/C++ Users Journal, August 2001.
|
||||
|
||||
[4] Jim Hyslop and Herb Sutter.I'd Hold Anything for You,C/C++ Users Journal C++ Experts Forum, December 2001.
|
||||
|
||||
[5] Boost mailing list, http://groups.yahoo.com/group/Boost.
|
||||
|
||||
[6] Boost-Users mailing list, http://groups.yahoo.com/group/Boost-Users
|
||||
|
||||
[7] C++ Standard, International Standard ISO/IEC 14882.
|
||||
|
||||
|
||||
The three moderators of the Boost mailing list are Beman Dawes, Dave Abrahams, and Jens Maurer. (If you're not familiar with these names, see their biographies on www.boost.org). I interviewed Beman, Dave, and Jens on their views of Boost. Following is a summarized version of what they had to say:
|
||||
|
||||
Why do you think Boost is important?
|
||||
|
||||
Boost is one of the only communities working on the process and practice of library design. There are lots of libraries available elsewhere whose design pre-dates the ISO C++ Standard. These libraries often fail to exploit the potential of compile-time evaluation (i.e., templates) and thus miss type-checking and optimization opportunities. The license requirements allow the use of Boost libraries in commercial and non-commercial projects free of charge, thereby helping them to produce better programs.
|
||||
|
||||
In today's professional software development world, it can be hard to make the case for long-term investment in reusable components. Developers are (often rightly) expected to do the simplest thing that could possibly work, under the assumption that the generalization won't be needed. As the problems they need to solve become more complex, however, they need library components that can help them keep their solutions simple. The C++ Standard library goes some distance towards filling that role, but programmers will continue to need more than it provides. What should be the design, documentation, and coding practices for the libraries that programmers need? Boost's emergent collaborative process provides one answer.
|
||||
|
||||
What will happen to the libraries when (if) they become a part of Standard C++? Will they still be available in Boost? Will compiler vendors be able to use the implementation of Boost libraries in their packages?
|
||||
|
||||
It is likely that the libraries that become a part of Standard C++ will still be available in Boost for quite some time after the release of the respective standard. Experience with C99 and C++98 shows that compiler vendors require some time to catch up with the latest standard. Compiler vendors are certainly allowed to use the Boost-provided implementation of the specified library interfaces, by virtue of Boost's license requirements. However, some Boost libraries could benefit from compiler support or manual adaptation to the target platform.
|
||||
|
||||
Why should C++ users (rather than those that submit libraries) join Boost?
|
||||
|
||||
A new mailing list called boost-users@yahoogroups.com has been set up, specifically targeted at Boost users. However, Boost users are always welcome to join the main mailing list as well. Since Boost provides C++ libraries, Boost users are C++ programmers. I believe all C++ programmers can learn a lot from following discussions on the Boost mailing list. It's also a chance to see how world-class developers approach and solve problems.
|
||||
|
||||
If companies were to specialize in supporting the Boost libraries, would that be a problem?
|
||||
|
||||
Not at all, that would be a welcome step. It would be even better if such companies would contribute any bug fixes or changes they make back to the community.
|
||||
|
||||
David adds: I'd like to start such a company myself.
|
||||
|
||||
Does Boost aim to be widely used by developers before libraries have been included in the Standard, or should it be regarded as research work?
|
||||
|
||||
Boost is absolutely not to be regarded as research work, though many of the techniques are cutting-edge. During the formal review process, membership relentlessly pursues issues of practical importance to real-world software. After all, experience can only be gained by using it, not by waiting until the paperwork is done.
|
||||
54
Zim/Programme/C++/boost/boost_format_格式控制输出(学习笔记).txt
Normal file
@@ -0,0 +1,54 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-14T19:28:43+08:00
|
||||
|
||||
====== boost format 格式控制输出(学习笔记) ======
|
||||
Created Tuesday 14 February 2012
|
||||
http://blog.csdn.net/wzq9706/article/details/6168430
|
||||
|
||||
#include <boost/format.hpp>
|
||||
#include <iostream>
|
||||
using namespace boost;
|
||||
using namespace std;
|
||||
|
||||
// 格式输出控制,(类似c++准准库中的sprintf)
|
||||
//
|
||||
int main()
|
||||
{
|
||||
// format库的几种用法
|
||||
//------------------------------------------
|
||||
// 第一种:
|
||||
// 最接近printf()
|
||||
cout<< "First: " << format("%s: %d + % d = %d/n")%"sum"%1%2%(1+2); // format 重载了%操作符类似<<操作符
|
||||
// result: sum: 1 + 2 =3
|
||||
// 第二种:
|
||||
// 在format对象中用%X%指定X是第几个参数
|
||||
format fmt1("(%1% + %2%)*%2% = %3%/n"); // %1% 接收第一个参数, %2% 接收第二个参数, %3%接收第三个参数
|
||||
fmt1 %2 %5; // 第一,二个参数
|
||||
fmt1 %((2+5)*5); // 第三个参数,参数不一定像第一种方法直接植入,可以分次完成但参数的总数量不能少
|
||||
cout<< "Second: " << fmt1.str(); // str()是获取格式化后的字符串,不负责清空
|
||||
// result: (2 + 5)*5 = 50;
|
||||
// 第三种:
|
||||
// 带格式化选项(和printf一样)
|
||||
format fmt3("%05d %-8.3f %10s %05X/n");
|
||||
cout<< "Third: " <<fmt3 %62 %3.14159f %123456789 %48;
|
||||
// 第四种:
|
||||
// 在对象构造函数字符串添加管道符,用于美观
|
||||
format fmt4("%|05d| %|-8.3f| %|10s| %|05X|/n");
|
||||
cout<< "Fourth: " << fmt4 %62 %3.14159f %123456789 %48;
|
||||
// 高级应用:
|
||||
format fmtOther("%1% %2% %3% %2% %1%");
|
||||
cout<< "Other normal: " << fmtOther %1 %2 %3 <<endl; // 插入值 %1%对应第一个参数,%2%第二个, %3%第三个
|
||||
fmtOther.bind_arg(2, 10); // 将第二个参数固定为10,即使使用clear也一样
|
||||
cout<< "Other bind_arg: " << fmtOther %1 %3 <<endl; // 插入值时忽略%2%,直接跳到下一个%X%
|
||||
// group, modify_item 略
|
||||
//-----------------------------
|
||||
// 与printf的比较:
|
||||
//-----------------------------
|
||||
// printf是函数,format是类
|
||||
// printf不进行类形检查,format进行类型安全检查
|
||||
// printf速度是format的2到5倍
|
||||
// format个人还不太习惯
|
||||
getchar();
|
||||
return 0;
|
||||
}
|
||||
131
Zim/Programme/C++/boost/boost_学习计划.txt
Normal file
@@ -0,0 +1,131 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-14T14:44:56+08:00
|
||||
|
||||
====== boost 学习计划 ======
|
||||
Created Tuesday 14 February 2012
|
||||
http://remonstrate.wordpress.com/2010/10/27/boost-%E5%AD%A6%E4%B9%A0%E8%AE%A1%E5%88%92/
|
||||
|
||||
嗯,最近看到一本 Beyond C++ standard library: An Introduction to Boost,感觉还不错。正好拿来学习一下,这里会摘录一些笔记,跟原先的 blog 一起。那本书似乎是针对 1.33 版本的,而现在已经 release 到 1.44 版本了。
|
||||
|
||||
===== 字符串和文本处理 =====
|
||||
* boost.regex 为 c++ 提供了正则表达式;
|
||||
* boost.spirit 是 functional recursive-decent parser framework,支持使用 C++ 描述类似 EBNF 语法,可用于创建命令行 parser、预处理器。boost.wave 就是利用这个 framework 实现的 C++ 预处理器;
|
||||
* boost.string_algo 提供了 std::string 以外的__字符串处理程序__,包括 trimming、case conversion、splitting、finding、replacing 等功能;
|
||||
* boost.tokenizer 用于将字符串类型的容器__切分__成为一个一个 token;
|
||||
* (1.44)boost.iostreams 用于自定义 stream;
|
||||
* (1.44)boost.wave 是利用 spirit 写的 C++ 的预处理程序,可以利用它实现很多 c++ 0x 的特性;
|
||||
* (1.44)boost.xpressive 也是一个 regex 的库,但是使用 context free 语法;
|
||||
|
||||
===== 数据结构、容器、迭代器(iterator)和算法 =====
|
||||
|
||||
* boost.any 的目标是__存储异构数据__。一般来说异构数据需要使用不同的指针,而一种解决方案是使用 **void* 指针**(但这种方式并不是 type safe 的);另外一种是使用**串行化**,将所有异构的数据转换成为某种格式(如 string),要求所有的数据提供 serialization 和 deserialization 的方法。而 any 实现的类型安全的(value-based variant type with an unbounded set of possible types);当我们在容器里面需要存储异构数据的时候,any 是一个非常好的选择;
|
||||
* boost.array 是对 C++ __数组的封装__,由于 std::vector 等容器是动态的,性能上相对静态的数组是有一定的差距的,如果性能上需要使用数组但希望使用封装的更好的对象的时候,可以使用 array;
|
||||
* boost.compressed_pair 是与 std::pair 相近的功能;在 std::map 这类关联容器中我们常常使用 std::pair 创建需要的一对 key/value;boost 的版本允许在 pair 中存在空对象时利用 empty base optimization **压缩这个 pair 的内存占有率**;
|
||||
* boost.dynamic_bitset 是对 std::bitset 的扩展,后者是一个静态的数据结构,而 dynamic_bitset 允许在运行时确定 bitset 的大小;
|
||||
* boost.graph 是对__图结构、相关算法的封装__,提供了 adjacency matrix、adjacency list、edge list 这些方式表示图,同时提供了最短路径、minimum spanning tree、topological sort 等算法的实现;
|
||||
* boost.iterator 用于创建符合 stl 标准的 iterator,提供**自动的 typedef**,并且可以将已有的 iterator 的行为改变,创建 indirect iterator adaptor 可以将放在容器里面的智能指针 deference,这样可以像一般的容器(里面盛放的是正常的对象)一样使用;
|
||||
* boost.multiarray 是对 C++ 中**多维数组**的封装;
|
||||
* boost.multi-index 允许一个容器存在多个 index,这意味着可以__让同一个容器使用不同的序__,这样抽取其中元素的时候能够更加便捷;特别在 std::set、std::map 不能够提供需要的多重序关系访问时,multi-index 就会产生作用了;
|
||||
* boost.range 提供的主要是一种概念性的东西:一般的 algorithm 输入都是两个 iterator 表示操作的范围;ranges 提供了更好的表达方式,这样使用 algorithm 会更加方便,另外也使得用户代码更加抽象;
|
||||
* boost.tuple 是对 std::pair 的另一种扩展,也就是提供了所谓的__ n-tuple(n 元组)结构__;与直接使用 struct、class 创建 n-tuple 不同之处在于可以 direct declaration、作为函数返回值或者参数以及一致的方法访问 tuple 的任意成员;
|
||||
* boost.variant 是对 __union 结构的封装__,主要为了解决一般 union 结构为了判断其类型而写的类型转换代码;variant 提供的是 type safe visitation;
|
||||
* (1.44)boost.bimap 提供了**双向关联容器**;
|
||||
* (1.44)boost.circular_buffer 提供了循环的 buffer;
|
||||
* (1.44)boost.gil 提供的是图片处理的封装;
|
||||
intrusive [in'tru:siv] adj.打搅的, 侵入的
|
||||
* (1.44)boost.intrusive 提供了所谓的 intrusive container;intrusive container 是相对于 non-intrusive container 来说的,STL 的容器都是 non-intrusive,被存储的对象不需要有特别的代码上的修改,任何对象都可以存下来,代价是只能**复制一份**存放在容器里面;intrusive 容器需要对象本身提供互相连接的机制,因此需要修改原来的对象,好处是进入容器的**是原来的对象**,而不是其复制体;
|
||||
* (1.44)boost.pointer_container 用于在堆上的对象需要的指针;
|
||||
* (1.44)**boost.property_tree** 用于__存放配置__;
|
||||
* (1.44)boost.unordered 是用于存放无序的数据;
|
||||
* (1.44)boost.foreach 是一个 macro,可以方便的写遍历代码,使用 foreach 类型的语法;
|
||||
|
||||
===== functor 和 higher-order programming =====
|
||||
|
||||
* boost.bind 是非常有用的工具,是对 std::bind1st 和 std::bind2nd 的扩展;bind __允许用统一的接口__对函数、函数指针、函数对象、成员函数指针进行封装,产生 functor 以供后面的 algorithm 使用;定义的 functor 提供了 stl 中需要的 typedef;这使得我们没有必要使用 std::ptr_fun、std::mem_fun 这类产生 functor 的 helper function;
|
||||
* boost.function 提供的是一种包装函数指针、函数对象以及成员函数的机制,由此能够产生 stateful callback function(估计就是将产生函数对象时的相关信息存放在函数对象中)等用例;
|
||||
* boost.functional 主要是对 STL 中存在的一个问题进行了处理:当函数存在引用类型的参数的时候会产生“引用的引用”这种非法的结果导致的错误;使用 functional 将使得使用 std::ptr_fun 失去使用的意义;
|
||||
* boost.lambda 是 Boost 的__ lambda 算子__,用于方便的创建 functor,并且与 STL 中的 algorithm 一起使用将非常方便;避免写非常多的 functors;
|
||||
* boost.ref 是用来创建引用,这个引用和 C++ 自身的引用不同的是它**可以被复制**;与指针的用例不同之处在于,引用允许你一直用 . 而不是 -> 来访问成员、方法;
|
||||
* boost.signals(2) 用于创建 __signal/slot 的通讯机制__,这在 design pattern 中对应着 observer 或者 publisher/subscriber 这种,GUI 开发中非常常见;
|
||||
* (1.44)boost.mem_fun 产生成员函数的 functor;
|
||||
* (1.44)boost.functional/factory 用于动态或者静态的生成对象;
|
||||
* (1.44)boost.functional/forward 允许使用任意个参数的 generic programming;
|
||||
* (1.44)boost.functional/hash 可扩展到用户定义的数据上的 hash;
|
||||
* (1.44)result_of 用于确定函数的返回类型;
|
||||
|
||||
===== generic programming 与 template metaprogramming =====
|
||||
|
||||
关于这些诡异的概念见上一篇 blog。
|
||||
|
||||
* boost.call_traits 提供了自动推理函数传递参数时最合适的方法,如果函数不修改参数本身,可以传值也可以传引用,在写 generic function 的时候需要这种机制选择合适的类型,一个例子比如我希望写一个串行化的东西,要求传入对象都支持 operator<< 插入到 std::ostream,可是 int 和 std::string 很显然前者希望传值,后者希望传引用,call_traits 的作用就在于可以让编译器自动选择最合适的方式生成对应的 template;
|
||||
* boost.concept_check 用于检查 concept,我们知道 generic programming 里面最重要的就是搞清楚什么样的东西能够被传递给 template 而不会出错(满足某种 concept),那么这个 concept_check 的用途就是在编译时能够检测出来;另外也提供了 30 多个 concept 用于检查;
|
||||
* boost.enable_if 用于根据某些条件特例化或者排除某种特例化;
|
||||
* boost.in_place_factory 实现的是 in-place 创建对象,这对于将一些不能进行 copy 的对象放在容器里面是非常有用的;
|
||||
* boost.mpl 本身就可以写一本书了,这是用来做 template metaprogramming 的非常有用的库(而在此之前几乎没有);它实现了很多 C++ STL 的 algorithm,但是都是在编译时执行的;
|
||||
* boost.property_map 提供了用于创建 mapping 机制的 concept,与 concept_check 一起可以用于有效的检查 generic programming 的正确性;
|
||||
* boost.static_assert 嗯这个就是进行编译时检查所用的技术,和 assert 宏类似,后者是运行时检查;
|
||||
* boost.type_traits 提供编译时判断 template 的类型,如是否指针/引用等等;
|
||||
* (1.44)boost.function_type 可调用(callable)对象的 traits;
|
||||
* (1.44)boost.typed_in_place_factory?
|
||||
* (1.44)boost.fusion 用于与 tuple 一起使用的一些工具;
|
||||
|
||||
===== 数学与数值计算 =====
|
||||
|
||||
* boost.integer 提供了编译时关于整数的信息,对应于 C99 的 stdint.h;
|
||||
* boost.interval 提供了对区间的封装;
|
||||
* boost.math 提供了对复数的推广 quaternion 和 octonion 的封装以及 LCD 和 GCM 的计算;
|
||||
* boost.minmax 是对 std::min 和 std::max 的补充;
|
||||
* boost.numeric 给出了 numeric_cast 保证类型转换的时候不会出现溢出;
|
||||
* boost.operator 提供了常用的比较 operator;
|
||||
* boost.random 用于产生__随机数__;
|
||||
* boost.rational 封装了有理数;
|
||||
* boost.ublas 是对 BLAS 的 C++ 实现,不同之处是 BLAS 原来是 fortran 写的 columnwise 的,而 uBLAS 是 rowwise 的;
|
||||
* (1.44)boost.accumulator 用于处理__增量式问题__,比如求和,求最大值等;
|
||||
* (1.44)boost.math/special_function 包括了许多特殊函数;
|
||||
|
||||
===== 输入输出 =====
|
||||
|
||||
* boost.assign 重载了逗号和括号 operator,可以更方便的给容器赋值;
|
||||
* boost.filesystem 提供了__对基本文件操作的封装__;
|
||||
* boost.format 提供了__与 printf 类似的功能__,但是是 type safe 的;
|
||||
* boost.io_state_savers 允许对 std::istream 和 std::ostream 进行保存,并在后面 undo ;
|
||||
* boost.serialization 用于__对数据进行串行化__,并给出了对 STL 某些容器串行化的封装;
|
||||
|
||||
===== 并行处理 =====
|
||||
|
||||
* **boost.thread** 是一种 portable 的线程实现;
|
||||
* (1.44)boost.asio 用于低级__网络通讯__;
|
||||
* (1.44)boost.interprocess 用于__进程之间的通讯__;
|
||||
* (1.44)boost.MPI 是一个 MPI 实现;
|
||||
|
||||
===== 其他 =====
|
||||
|
||||
* boost.conversion 提供了另外几种 cast 如 polymorphic_cast、__polymorphic_downcast__ 等;
|
||||
* boost.crc 用于计算 CRC;
|
||||
* boost.date_time 用于__时间方面的操作__;
|
||||
* boost.optional 用于返回值可能存在需要表达“错误”或者“空”但是原来的类型可能不存在这种值的情况;
|
||||
* boost.pool 提供了__内存池__;
|
||||
* boost.preprocessor 提供了预处理程序,支持 recursion 和容器等;
|
||||
* **boost.program_option **用于__ parse 输入的命令行参数__;
|
||||
* boost.python 和 SWIG 做的类似,提供 C++ 的 python binding,当然 boost 这个仅仅面向 python 一个语言;
|
||||
* **boost.smart_ptr** 提供了几种__智能指针__;
|
||||
* boost.test 提供了__ unit test __的 framework;
|
||||
* **boost.timer **用于__计算时间__,尽量提供最高精度的时间;
|
||||
* boost.tribool 提供三值(bool 是两值);
|
||||
* boost.utility 包括很多小工具,如 check_deleted、noncopyable 等;
|
||||
* boost.value_initialized 用于处理各种不同方式的初始化,避免之间可能存在的不一致;
|
||||
* (1.44)boost.uuid 用于**计算 UUID**;
|
||||
* (1.44)boost.base_from_member 提供了从成员初始化基类的方法;
|
||||
* (1.44)boost.exception 允许在 exception 中携带别的对象;
|
||||
* (1.44)boost.flyweight 用于处理含有大量冗余的大规模数据的 design pattern;
|
||||
* (1.44)boost.scope_exit 提供在__退出环境时执行的函数__;
|
||||
* (1.44)boost.statechart 用于实现有限状态机;
|
||||
* (1.44)**boost.swap** **交换对象**;
|
||||
* (1.44)**boost.system **提供对__操作系统方面__的支持;
|
||||
* (1.44)boost.TR1 是 C++ 标准的 technical report;
|
||||
* (1.44)boost.typeof 是 typeof 的一种模拟;
|
||||
* (1.44)boost.units 是用来做 dimensional analysis 和 unit/quantity manipulation 的;
|
||||
|
||||
许多 boost 的库已经存在 C++ technical report,这意味着它们已经进入 C++ 标准委员会审查进入 STL 的阶段,有望在 C++0x 版本中成为新的标准。
|
||||
|
||||
75
Zim/Programme/C++/boost/boost_的_system_和_filesystem.txt
Normal file
@@ -0,0 +1,75 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-14T15:45:10+08:00
|
||||
|
||||
====== boost 的 system 和 filesystem ======
|
||||
Created Tuesday 14 February 2012
|
||||
|
||||
http://remonstrate.wordpress.com/2011/06/29/boost-%E7%9A%84-system-%E5%92%8C-filesystem/
|
||||
|
||||
boost 的 system 为一些__常见的系统调用__提供了非全局变量(errno)的解决方案,主要是提供了一个 std::runtime_error 的子类 boost::system::system_error,这是一个跨平台的头文件 boost/system/system_error.hpp 里面包含的。
|
||||
|
||||
boost 的 filesystem 就是建立在 system 上面的一个工具,方便在 C++ 程序里面访问文件系统(跨平台)。下面是一个简单的例子:
|
||||
|
||||
01 #include <iostream>
|
||||
02 #include <boost/filesystem.hpp>
|
||||
03 using namespace boost::filesystem;
|
||||
04
|
||||
05 int
|
||||
06 main (int argc, char* argv[]) {
|
||||
07 if (argc < 2) {
|
||||
08 std::cout << "Usage: tut1 path\n";
|
||||
09 return 1;
|
||||
10 }
|
||||
11 std::cout << argv[1] << " " << file_size(argv[1]) << '\n';
|
||||
12 return 0;
|
||||
13 }
|
||||
|
||||
这里的 boost::filesystem::filesize 就是一个用来获得文件大小的函数。
|
||||
|
||||
事实上,有更多的所谓的 operational function:
|
||||
|
||||
* absolute 取绝对路径
|
||||
* copy、copy_file、copy_directory 和 copy_symlink 进行复制
|
||||
* create_directory/create_directories、create_hard_link 和 create_symlink 用于创建需要的目录和连接
|
||||
* current_path 返回当前路径
|
||||
* exists 判断 path 是否存在
|
||||
* equivalent 判断两个文件的 file_status 是否相同
|
||||
* hard_link_count 硬连接数
|
||||
* is_directory 判断是否为目录,is_empty 是否空文件,is_other 别的类型,is_regular_file 是否为正常文件,is_symlink 是否符号链接
|
||||
* read_symlink 读符号链接
|
||||
* remove 删除和 remove_all 删除所有文件
|
||||
* rename 重命名
|
||||
* resize_file 改变文件大小
|
||||
* space 返回占用空间信息,status 返回 file_status
|
||||
* temp_directory_path 获得临时文件路径
|
||||
* unique_path 返回一个 UUID
|
||||
|
||||
另外有一些支持的类,比如:
|
||||
|
||||
* path 用来创建一个路径,比起一般的字符串,将有更多的功能,如判断是否绝对路径 is_absolute/is_relative 等函数,转换(make_absolute)或者替换扩展名(replace_extension)等;
|
||||
* filesystem_error __将对应的 errno 转换成为了 exception 机制__,前面的例子只需要换一下参数访问一个不存在的文件就会抛出异常;
|
||||
* 用于访问目录的 directory_entry 和__遍历目录__的 directory_iterator、directory_recursive_iterator。
|
||||
* file_status 用于获得文件的信息。
|
||||
* fstream 和 std::fstream 的区别在于使用 path 作为 constructor 的输入
|
||||
|
||||
下面是一个稍微复杂一点的例子:
|
||||
01 #include <iostream>
|
||||
02 #include <boost/filesystem.hpp>
|
||||
03
|
||||
04 namespace bfs = boost::filesystem ;
|
||||
05
|
||||
06 int
|
||||
07 main (int, char**) {
|
||||
08 for (bfs::recursive_directory_iterator it =
|
||||
09 bfs::recursive_directory_iterator (bfs::current_path()) ;
|
||||
10 it != bfs::recursive_directory_iterator() ; ++ it) {
|
||||
11 const bfs::path& cp = it -> path() ;
|
||||
12 std::cout << cp << '\t'
|
||||
13 << (bfs::is_regular_file (cp) ?
|
||||
14 static_cast<int> (bfs::file_size (cp)) : -1) << std::endl ;
|
||||
15 }
|
||||
16 return 0;
|
||||
17 }
|
||||
|
||||
这个还是很不错的,写代码都很简单了…
|
||||
276
Zim/Programme/C++/boost/了解_Boost_Filesystem_Library.txt
Normal file
@@ -0,0 +1,276 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-14T15:56:06+08:00
|
||||
|
||||
====== 了解 Boost Filesystem Library ======
|
||||
Created Tuesday 14 February 2012
|
||||
http://www.ibm.com/developerworks/cn/aix/library/au-boostfs/
|
||||
|
||||
简介: 缺乏定义良好的、用于处理文件系统操作的库,这一直是 C++ 语言存在的一个问题。过去,程序员必须使用__本机 API__ 来解决此问题。通过本文您将了解一个提供安全、可移植且易用的 C++ 接口来促进文件系统操作的库:Boost Filesystem Library。
|
||||
|
||||
发布日期: 2008 年 6 月 17 日
|
||||
级别: 中级
|
||||
其他语言版本: 英文
|
||||
访问情况 : 4575 次浏览
|
||||
评论: 0 (查看 | 添加评论 - 登录)
|
||||
平均分 4 星 共 5 个评分 平均分 (5个评分)
|
||||
为本文评分
|
||||
|
||||
C++ 语言(实际上是 C++ 标准)的最常见问题之一是,缺乏定义良好的库来帮助处理__文件系统查询和操作__。由于这个原因,程序员不得不使用本机操作系统提供的应用程序编程接口(Application Program Interfaces,API),而这使得代码不能在平台之间移植。以下面的简单情况为例:您需要确定某个文件是否是 Directory 类型。在 Microsoft® Windows® 平台中,可以通过调用 GetAttributes 库函数(在 windows.h 头文件中定义)进行此操作:
|
||||
|
||||
DWORD GetFileAttributes (LPCTSTR lpFileName);
|
||||
|
||||
|
||||
对于目录,所得到的结果应该为 FILE_ATTRIBUTE_DIRECTORY,而您的代码必须检查是否为此结果。在 UNIX® 和 Linux® 平台上,可以通过使用 **stat 或 fstat **函数及 sys/stat.h 中定义的 **S_ISDIR 宏**来实现相同的功能。您还必须理解 stat 结构。下面是对应的代码:
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <stdio.h>
|
||||
int main()
|
||||
{
|
||||
struct stat s1;
|
||||
int status = stat(<const char* denoting pathname>, &s1);
|
||||
printf(“Path is a directory : %d\n”, S_ISDIR(s1.st_mode));
|
||||
return 0;
|
||||
}
|
||||
|
||||
对于 I/O 操作较多的程序,这样的不一致就意味着需要进行大量的工程工作才能__在平台间移植代码__。正是因为这个原因,我们才引入了 Boost Filesystem Library。这个广泛使用的库提供了安全、可移植且易用的 C++ 接口,用于执行文件系统操作。可以从 Boost 站点免费下载此库。
|
||||
|
||||
===== 使用 boost::filesystem 的第一个程序 =====
|
||||
|
||||
在深入研究 Boost Filesystem Library 的更多细节之前,请看一下清单 1 中所示的代码;此代码使用 Boost API 确定某个文件的类型是否为 Directory。
|
||||
|
||||
**清单 1. 用于确定某个文件的类型是否为 Directory 的代码**
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include “boost/filesystem.hpp”
|
||||
int main()
|
||||
{
|
||||
**boost::filesystem::path** path("/usr/local/include"); // random pathname
|
||||
bool result = boost::filesystem::is_directory(path);
|
||||
printf(“Path is a directory : %d\n”, result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
此代码非常明了易懂,您并__不需要了解任何系统特定的例程__。此代码经过验证,能在不用修改的情况下在 gcc-3.4.4 和 cl-13.10.3077 上成功编译。
|
||||
|
||||
===== 了解 Boost path 对象 =====
|
||||
|
||||
了解 Boost Filesystem Library 的关键是 __path 对象__,因为 Filesystem Library 中定义的多个例程都要对相应的 path 对象操作。**文件系统路径通常依赖于操作系统**。例如,众所周知,UNIX 和 Linux 系统使用正斜杠 ( /) 字符作为目录分隔符,而 Windows 将反斜杠 (\) 字符用于类似的用途。boost::filesystem::path 旨在__准确地抽象此特性__。path 对象可以通过多种方式进行初始化,最常见的方式是使用 char* 或 std::string 进行初始化,如清单 2 中所示。
|
||||
|
||||
**清单 2. 创建 Boost path 对象的方法**
|
||||
|
||||
path(); // empty path
|
||||
path(const char* pathname);
|
||||
path(const std::string& pathname);
|
||||
path(const char* pathname, boost::filesystem::path::name_check checker);
|
||||
path(const char* pathname, boost::filesystem::path::name_check checker);
|
||||
|
||||
在初始化 path 对象时,可以采用本机格式或可移植操作系统接口(Portable Operating System Interface,POSIX)委员会定义的可移植格式提供 PATHNAME 变量。这两种方法在实际中各有优缺点。考虑以下情况:您希望操作软件所创建的目录,此目录在 UNIX 和 Linux 系统上位于 /tmp/mywork,而在 Windows 上位于 C:\tmp\mywork。可以采用多种方法处理问题。清单 3 显示了面向本机格式的方法。
|
||||
|
||||
清单 3. 使用本机格式初始化 path
|
||||
|
||||
**#ifdef UNIX**
|
||||
boost::filesystem::path path("/tmp/mywork");
|
||||
#else
|
||||
boost::filesystem::path path("C:\\tmp\\mywork ");
|
||||
#endif
|
||||
|
||||
需要单个 #ifdef 来按操作系统初始化 path 对象。不过,如果您喜欢使用可移植格式,请参见清单 4。
|
||||
|
||||
清单 4. 使用可移植格式初始化 path
|
||||
|
||||
boost::filesystem::path path("/tmp/mywork");
|
||||
|
||||
|
||||
请注意,path::name_check 指的是一个**名称检查函数原型**。如果其参数输入 PATHNAME __对于特定的操作系统或文件系统有效__,名称检查函数将返回“True”。Boost Filesystem Library __提供了多个名称检查函数__,而且也欢迎您提供自己的变体。常用的名称检查函数是 Boost 提供的 **portable_posix_name 和 windows_name**。
|
||||
|
||||
===== path 成员函数概述 =====
|
||||
|
||||
path 对象提供了多个成员方法。这些成员例程并不会修改文件系统,但会根据 path 名称提供有用的信息。此部分提供了其中几个例程的概述:
|
||||
|
||||
* const std::string& string( ):此例程会返回用于初始化 path 的字符串的副本,其格式符合 path 语法规则。
|
||||
* std::string root_directory( ):在提供了路径的情况下,此 API 将返回__根目录__,否则将返回空字符串。例如,如果路径包含 /tmp/var1,则此例程将返回 /,即 UNIX 文件系统的根。不过,如果路径是相对路径,如 ../mywork/bin,此例程将返回空字符串。
|
||||
* std::string root_name( ):在给定从文件系统根目录开始的路径的情况下,此例程将返回包含 PATHNAME 的第一个字符的字符串。
|
||||
* std::string leaf( ):在给定绝对路径名称(例如,/home/user1/file2)的情况下,此例程将提供__与文件名称对应的字符串__(即 file2)。
|
||||
* std::string branch_path( ):这是__与 leaf 互补的例程__。在给定路径的情况下,将会返回其构造所用的所有元素(除了最后一个元素)。例如,对于使用 /a/b/c 初始化的 path,path.branch_path( ) 将返回 /a/b。对于包含单个元素的路径,如 c,此例程将返回空字符串。
|
||||
* bool empty( ):如果 path 对象包含空字符串(例如 path path1("")),则此例程将返回 True。
|
||||
* boost::filesystem::path::iterator:此例程__用于遍历 path 的各个元素__。请看清单 5 所示的代码。
|
||||
|
||||
清单 5. 使用 path::iterator(begin 和 end 接口)
|
||||
|
||||
#include <iostream>
|
||||
#include “boost/filesystem.hpp”
|
||||
int main()
|
||||
{
|
||||
boost::filesystem::path path1("/usr/local/include"); // random pathname
|
||||
boost::filesystem::path::iterator pathI = path1.begin();
|
||||
while (pathI != path1.end())
|
||||
{
|
||||
std::cout << *pathI << std::endl;
|
||||
++pathI;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// result: 1
|
||||
|
||||
|
||||
上述程序的输出依次是 /、usr、local、include,代表了该目录的层次结构。
|
||||
path operator / (char* lhs, const path& rhs):此例程是 path 的非成员函数。它将返回使用 lhs 和 rhs 形成的路径的串联值。它将自动插入 / 作为路径分隔符,如清单 6 中所示。
|
||||
|
||||
清单 6. 路径字符串的串联
|
||||
|
||||
#include <iostream>
|
||||
#include “boost/filesystem.hpp”
|
||||
int main()
|
||||
{
|
||||
boost::filesystem::path path1("/usr/local/include"); // random pathname
|
||||
boost::filesystem::path::iterator pathI = path1.begin();
|
||||
while (pathI != path1.end())
|
||||
{
|
||||
std::cout << *pathI << std::endl;
|
||||
++pathI;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// result: 1
|
||||
|
||||
===== 错误处理 =====
|
||||
|
||||
文件系统操作经常遇到意外的问题,Boost Filesystem Library 将__使用 C++ 异常报告运行时错误__。boost::filesystem_error 类派生自 std::runtime_error 类。库中的函数使用 filesystem_error 异常报告操作错误。与不同的可能错误类型对应,Boost 头文件定义了相应的错误代码。用户代码通常驻留在 try...catch 块内,使用 filesystem_error 异常来报告相关错误消息。清单 7 提供了重命名文件的小示例,在 from 路径中的文件不存在时引发异常。
|
||||
|
||||
清单 7. Boost 中的错误处理
|
||||
|
||||
|
||||
#include <iostream>
|
||||
#include “boost/filesystem.hpp”
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
boost::filesystem::path path("C:\\src\\hdbase\\j1");
|
||||
boost::filesystem::path path2("C:\\src\\hdbase\\j2");
|
||||
boost::filesystem::rename(path, path2);
|
||||
}
|
||||
catch(boost::filesystem::filesystem_error e) {
|
||||
// do the needful
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
===== Boost Filesystem Library 中的函数类别 =====
|
||||
|
||||
boost::filesystem 提供了不同类别的函数:有些函数(如 is_directory)用于查询文件系统,而其他函数(如 create_directory)则主动对文件系统进行修改。根据各自功能的不同,这些函数可以大略归入以下类别:
|
||||
|
||||
* __属性函数__:**提供杂项信息**,如文件大小、磁盘使用量等。
|
||||
* 文件系统__操作函数__:用于创建常规文件、目录和符号链接;复制和重命名文件;提供删除功能。
|
||||
* **实用工具**:测试文件的扩展名等。
|
||||
* 杂项常规函数:以编程方式更改文件扩展名等。
|
||||
|
||||
==== 属性函数 ====
|
||||
|
||||
Boost Filesystem Library 包括以下属性函数:
|
||||
|
||||
* uintmax_t file_size(const path&):返回**常规文件的大小**(以字节为单位)
|
||||
* boost::filesystem::space_info space(const path&):接受路径作为输入,并返回定义如下的 space_info 结构:
|
||||
|
||||
struct space_info {
|
||||
uintmax_t capacity;
|
||||
uintmax_t free;
|
||||
uintmax_t available;
|
||||
};
|
||||
|
||||
根据文件系统所属的磁盘分区,此流程将对该分区的所有目录返回相同的__磁盘使用量统计数据__(以字节为单位)。例如,对于 C:\src\dir1 和 C:\src\dir2,都会返回相同的磁盘使用数据。
|
||||
* std::time_t last_write_time(const path&):返回文件的最后修改时间。
|
||||
* void last_write_time(const path&, std::time_t new_time):__修改__文件的最后修改时间。
|
||||
* const path& current_path( ):返回程序的__当前工作目录的完整路径__(注意,此路径与最初运行程序的路径可能不同,因为可能采用编程方式更改目录)。
|
||||
|
||||
===== 文件系统操作函数 =====
|
||||
|
||||
这组函数负责进行新文件和目录创建、文件删除等操作:
|
||||
|
||||
* bool create_directory(const path&):此函数使用给定的路径名称__创建目录__。(请注意,如果 PATHNAME 本身包含无效字符,则结果经常是由平台定义的。例如,在 UNIX 和 Windows 系统中,星号 (*)、问号 (?) 及其他此类字符视为无效,不能出现在目录名称中。)
|
||||
* bool create_directories(const path&):与创建单个目录相对,您可以使用此 API__ 创建目录树__。例如,以目录树 /a/b/c 为例,必须在 /tmp 文件夹内创建此目录树。可调用此 API 完成任务,但使用相同的参数调用 create_directory 时将引发异常。
|
||||
* bool create_hard_link (const path& frompath, const path& topath):此函数在 frompath 和 topath 间__创建硬链接__。
|
||||
* bool create_symlink(const path& frompath, const path& topath):此函数在 frompath 和 topath 间__创建符号(软)链接__。
|
||||
* void copy_file(const path& frompath, const path& topath):将 frompath 引用的文件的内容和属性复制到 topath 引用的文件中。例程expects a destination file to be absent;如果存在目标文件,则会引发异常。因此,此函数与 UNIX 中系统指定的 cp 命令并不等效。另外,此函数还预期 frompath 变量将引用正确的常规文件。请看以下示例:frompath 引用符号链接 /tmp/file1,而后者反过来引用文件 /tmp/file2;而 topath 可以为 /tmp/file3。在这种情况下,copy_file 将失败。这是此 API 与 cp 命令相比的另一个差别。
|
||||
* void rename(const path& frompath, const path& topath):此函数是用于__重命名文件__的 API。可以通过在 topath 参数中指定完整路径名来同时重命名和更改文件的位置,如清单 8 中所示。
|
||||
|
||||
清单 8. Boost 中的重命名功能
|
||||
|
||||
#include <stdio.h>
|
||||
#include “boost/filesystem.hpp”
|
||||
int main()
|
||||
{
|
||||
boost::filesystem::path path("/home/user1/abc");
|
||||
boost::filesystem::rename(path, "/tmp/def");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// abc is renamed def and moved to /tmp folder
|
||||
|
||||
* bool remove(const path& p):此例程将尝试__删除__路径 p 所引用的文件或目录。对于目录的情况,如果目录的内容不为空,则此例程将引发异常。警告:此例程并不考虑所删除的内容,即使其他程序在访问同一文件也如此!
|
||||
* unsigned long remove_all(const path& p):此 API 尝试删除路径 p 所引用的__文件或目录__。与 remove 不同,此函数并不会特殊考虑不为空的目录。此函数是 UNIX rm –rf 命令的 Boost 对等项。
|
||||
|
||||
===== 实用工具 =====
|
||||
|
||||
Boost Filesystem Library 包含以下实用工具:
|
||||
|
||||
* bool exists(const path&):此函数检查文件的__扩展名__。文件可以为任何类型:常规文件、目录、符号链接等等。
|
||||
* bool is_directory(const path&):此函数检查路径是否与目录对应。
|
||||
* bool is_regular(const path&):此函数检查普通文件(即此文件不是目录、符号链接、套接字或设备文件)。
|
||||
* bool is_other(const path&):通常,此函数检查设备文件(如 /dev/tty0)或套接字文件。
|
||||
* bool is_empty(const path&):如果路径与文件夹对应,此函数将检查文件夹__是否为空__,并据此返回“True”或“False”。如果路径与文件对应,此函数将检查文件的大小是否等于 0。对于文件的硬链接或符号链接的情况,此 API 将检查原始文件是否为空。
|
||||
* bool equivalent(const path1& p1, const path2& p2):此 API 非常实用,可用于比较相对路径和绝对路径名。请看清单 9:
|
||||
|
||||
清单 9. 测试两个路径是否等效
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include “boost/filesystem.hpp”
|
||||
int main()
|
||||
{
|
||||
boost::filesystem::path path1("/usr/local/include"); // random pathname
|
||||
boost::filesystem::path path2("/tmp/../usr/local/include");
|
||||
bool result = boost::filesystem::is_equivalent(path1, path2);
|
||||
printf(“Paths are equivalent : %d\n”, result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// result: 1
|
||||
|
||||
|
||||
* path system_complete(const path&):此函数是与 bool equivalent(const path1& p1, const path2& p2) 同一系列的另一个 API。在给定当前工作目录中任意文件路径的情况下,此 API 将__返回该文件的绝对路径__。例如,如果用户位于目录 /home/user1 并查询文件 ../user2/file2,此函数将返回 /home/user2/file2,即文件 file2 的完整路径名。
|
||||
|
||||
===== 杂项函数 =====
|
||||
|
||||
Boost Filesystem Library 包括以下杂项函数:
|
||||
|
||||
* std::string extension(const path&):此函数以前面带句点 (.) 的形式返__回给定文件名的扩展名__。例如,对于文件名为 test.cpp 的文件,extension 将返回 .cpp。对于文件没有扩展名的情况,此函数将返回空字符串。对于隐藏文件(即 UNIX 系统中文件名以 . 开始的文件),此函数将相应地计算扩展名类型或返回空字符串(因此,对于 .test.profile,此例程将返回 .profile)。
|
||||
* std::string basename(const path&):这是与 extension 互补的例程。它将__返回文件名中 . 之前的字符串__。请注意,即使提供了绝对文件名,此 API 仍然仅会返回属于文件名的直接部分,如清单 10 中所示。
|
||||
|
||||
清单 10. 使用 boost::basename
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <cstring>
|
||||
#include “boost/filesystem.hpp”
|
||||
use namespace std;
|
||||
int main()
|
||||
{
|
||||
boost::filesystem::path path1("/tmp/dir1/test1.c ");
|
||||
boost::filesystem::path path2("/tmp/dir1/.test1.profile");
|
||||
string result1 = boost::filesystem::basename (path1);
|
||||
string result2 = boost::filesystem::basename (path2);
|
||||
printf(“Basename 1: %s Basename2 : %s\n”, result1.c_str(), result2.c_str());
|
||||
return 0;
|
||||
}
|
||||
// result: Basename1: test1 Basename2: .test1
|
||||
|
||||
* std::string change_extension(const path& oldpath, const std::string new_extension):此 API 将返回反__映更改后的名称的新字符串__。请注意,与 oldpath 对应的文件保持不变。这只是一个常规函数。另请注意,您必须显式地在扩展名中指定点。例如,change_extension("test.c", "so") 会得到 testso,而不是 test.so。
|
||||
|
||||
===== 结束语 =====
|
||||
|
||||
本文提供了 Boost Filesystem Library 的简单概述。不应将本文视为 Boost 中的整个文件系统接口的综合文档。并未讨论此 API 集的内部情况,也没有讨论这些 API 在非 UNIX 或 Windows 平台(如 VMS)中的细节。有关文件系统的更多信息,请参见参考资料。
|
||||
33
Zim/Programme/C++/c++备忘.txt
Normal file
@@ -0,0 +1,33 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-07T10:08:42+08:00
|
||||
|
||||
====== c++备忘 ======
|
||||
Created Sunday 07 August 2011
|
||||
|
||||
===== 1. =====
|
||||
C++通过引进四个新的**类型转换操作符**克服了C风格类型转换的缺点,这四个操作符是, __static_cast, const_cast, dynamic_cast, 和reinterpret_cast__。例如,假设你想把一个int转换成double,以便让包含int类型变量的表达式产生出浮点数值的结果。如果用C风格的类型转换,你能这样写:
|
||||
int firstNumber, secondNumber;
|
||||
double result = ((double)firstNumber)/secondNumber;
|
||||
如果用上述新的类型转换方法,你应该这样写:
|
||||
double result = static_cast <double> (firstNumber)/secondNumber;
|
||||
|
||||
===== 2. =====
|
||||
函数参数的传递分为值传递和引用传递,引用时一种特殊类型的变量,可以认为是变量的另一个别名,__声明一个引用时必须对它初始化,一旦引用被初始化后,就不能在指向其他对象了__。例如,int i,j; int &r = i; j = 10 ; r = j; // 相当于i=j;
|
||||
|
||||
|
||||
===== 3. =====
|
||||
内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入到每一个调用处,这样就节省了参数传递、控制转移等开销。例如,inline double cal(){}
|
||||
|
||||
===== 4. =====
|
||||
C语言只有结构体而没有类,C语言的结构体只允许定义数据成员,不允许定义函数成员,而且C语言没有访问控制属性的概念,结构体的全部数据成员是公有的。C++为C语言的结构体引入了成员函数、访问权限控制、继承、包含多态等面向对象的特性。C++把class作为定义抽象数据类型的首选关键字,并保留struct关键字。
|
||||
|
||||
|
||||
===== 5. =====
|
||||
C语言中用指针作为函数参数,可以在减少函数调用数据时的开销的前提下共享数据。在C++中已经可以通过引用来实现了,使用引用可以是程序的可读性更好些。
|
||||
|
||||
===== 6. =====
|
||||
在C语言中可以用#define来定义带参数宏,以实现简单的函数计算,提高程序的运行效率,但是在C++中这一功能已内联函数所取代。
|
||||
scanf("%d%d",&a,&b)==2;scanf("%d%d",&a,&b)!=EOF;C语言中,EOF常被作为文件结束的标志。还有很多文件处理函数处错误后的返回值也是EOF,因此常被
|
||||
来判断调用一个函数是否成功。scanf("%d %d",&a,&b)返回输入的数据和格式字符串中匹配次数,当dos或windows中输入ctrl+z(模拟文件结束符EOF)时,scanf
|
||||
返回EOF 所以当输入终止符ctrl+z时,退出while循环。
|
||||
34
Zim/Programme/C++/const在函数中间的作用.txt
Normal file
@@ -0,0 +1,34 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T21:27:50+08:00
|
||||
|
||||
====== const在函数中间的作用 ======
|
||||
Created Saturday 06 August 2011
|
||||
|
||||
最近发现,在qt里面有很多类的成员函数声明中都加上了const这个限制符,不仅向看一看这个加上了const的函数和普通函数到底有什么区别,于是产生了下面这篇博文~~~
|
||||
|
||||
首先,const类型指明了变量或对象的值是不能被更新,引入目的是为了取代预编译指令(#define)。 在C++中引入const主要是为了程序的健壮性,减少程序的错误。如果我们不想修改某个常量的值,那么我们就可以把这个常量声明成const成员。同理,我们不想让某个函数修改成员变量的值,那么也可以把这个函数声明成const成员函数。这样的作用主要是为了保护数据成员。
|
||||
|
||||
举个例子来说,我们有这样一个类:
|
||||
01 class A
|
||||
02 {
|
||||
03 public:
|
||||
04 void area(int x,int y){length=x;width=y;}
|
||||
05 void print(){cout<<"两数相乘为:"<<length*width<<endl;}
|
||||
06
|
||||
07 private:
|
||||
08 int length;
|
||||
09 int width;
|
||||
10 };
|
||||
|
||||
对于print()函数,我们发现,其实它的作用只是把length和width的乘积打印到屏幕上,并不会修改类A里面的私有变量length和width的值,针对这一点,我们就可以把print()函数修饰成为const成员函数,即
|
||||
1 void print()const{cout<<"两数相乘为:"<<length*width<<endl;}
|
||||
|
||||
在这里const的作用是禁止在print()中对length和width进行修改操作。而对于area函数来说,我们就不能将其修饰成const成员函数,因为在这个函数里面,我们定义了两个整型变量x和y来改变length和width的值。如果我们非要把其声明成是const的成员函数,编译器就会报错,说这个东东是不合法的,或者说在const前面缺少;号(自我认为:报缺少;号这个错误最无语了,对找错误一点帮助也没有,囧~~)
|
||||
|
||||
==============================================================总结============================================================
|
||||
|
||||
从上面这个小例子我们就可以看出const的作用有两点:
|
||||
|
||||
限制对象的成员函数改变对象的数据成员,保护了程序的健壮性和稳定性;
|
||||
如果该成员函数试图去修改对象的数据成员时,编译器就会提示错误,从而达到帮助我们寻找错误的目的。
|
||||
37
Zim/Programme/C++/iostream_与iostream.h的区别.txt
Normal file
@@ -0,0 +1,37 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T09:42:48+08:00
|
||||
|
||||
====== iostream 与iostream.h的区别 ======
|
||||
Created Saturday 06 August 2011
|
||||
|
||||
iostream.h它是一个非标准的输入输出流,这个.h的头文件时C语言格式的,由于当时没有命名空间这个说法,所以也就不存在std这个命名空间标识符。自然用iostream.h也就用不着std或者using namespace std了;
|
||||
|
||||
iostream 是标准输入输出流,它是C++规范的带有名称空间的头文件,它包含在std命名空间中。而iostream流里又包含cin和cout输入输出对象,所以使用cout的时候必须加std,假如我们不加的话,编译就会报错。
|
||||
|
||||
通过上面的分析呢,我们可以看出iostream和iostream.h的区别是否引用命名空间std的区别
|
||||
|
||||
所谓namespace,是指__标识符的各种可见范围__。C++标准程序库中的所有标识符都被定义于一个名为std的namespace中。
|
||||
|
||||
|
||||
==== 一 、<iostream>和<iostream.h>格式不一样 ====
|
||||
前者没有后缀,实际上,在你的编译器include文件夹里面可以看到,二者是两个文件,打开文件就会发现,里面的代码是不一样的。__ 后缀为.h的头文件c++标准已经明确提出不支持了__,早些的实现将标准库功能定义在__全局空间__里,声明在带.h后缀的头文件里,c++标准为了和C区别开,也为了正确使用命名空间,规定头文件不使用后缀.h。 因 此,当使用<iostream.h>时,相当于在c中调用库函数,使用的是全局命名空间,也就是__早期的c++实现__;当使用< iostream>的时候,该头文件__没有__定义全局命名空间,必须使用namespace std;这样才能正确使用cout。
|
||||
|
||||
===== 二、namespace是指标识符的各种可见范围 =====
|
||||
**C++标准程序库中的所有标识符都被定义于一个名为std的namespace中。** 由于namespace的概念,使用C++标准程序库的任何标识符时,可以有三种选择:
|
||||
1、直接指定标识符。
|
||||
例如std::ostream而不是ostream。完整语句如下: std::cout << std::hex << 3.4 << std::endl;
|
||||
2、使用using关键字。
|
||||
using std::cout; using std::endl; using std::cin; 以上程序可以写成 cout << std::hex << 3.4 << endl;
|
||||
3、最方便的就是使用using namespace std
|
||||
例如: using namespace std; 这样__命名空间std内定义的所有标识符都有效(曝光)__。就好像它们被声明为__全局变量__一样。那么以上语句可以如下写: cout << hex << 3.4 << endl; 因为标准库非常的庞大,所以程序员在选择的类的名称或函数名时就很有可能和标准库中的某个名字相同。所以为了避免这种情况所造成的名字冲突,就把标准库中的一切都放在名字空间std中。但这又会带来了一个新问题。无数原有的C++代码都依赖于使用了多年的伪标准库中的功能,他们都是在全局空间下的。 所以就有了<iostream>和<iostream.h>等等这样的头文件,一个是为了兼容以前的C++代码,一个是为了支持新的标准。 命名空间std封装的是标准程序库的名称,标准程序库为了和以前的头文件区别,一般不加".h"
|
||||
简便理解
|
||||
98年以后的c++语言提供一个全局的命名空间namespace,可以避免导致全局命名冲突问题。举一个实例,请注意以下两个头文件:
|
||||
// one.h
|
||||
char func(char);
|
||||
class String { ... };
|
||||
// somelib.h
|
||||
class String { ... };
|
||||
如果按照上述方式定义,那么这两个头文件不可能包含在同一个程序中,因为String类会发生冲突。
|
||||
|
||||
所谓命名空间,是一种将程序库名称封装起来的方法,它就像在各个程序库中立起一道道围墙。
|
||||
28
Zim/Programme/C++/iostream_与iostream.h的区别/Std是什么?.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T09:45:43+08:00
|
||||
|
||||
====== Std是什么? ======
|
||||
Created Saturday 06 August 2011
|
||||
|
||||
“std::”是名称空间标识符,C++标准库中函数或者对象都是在命名空间std中定义的,所以我们要使用标准库中函数或者对象都要用std来限定。
|
||||
|
||||
接下来就很好理解,对象cout是标准库所提供的一个对象,而标准库在命名空间中被指定为std,所以在coutd时候前面要加上std::。这样编译器就会明白我们调用的cout是命名空间中的std中的cout。
|
||||
|
||||
至于为什么将cout放到命名空间std中,是因为像cout这样的对象在实际操作中或许会有许多个,比如说你自己也可能会不小心定义了一个对象叫cout,那么这两个cout对象就会产生冲突,关于这个问题,我会在其他篇幅讲解,这里不再多说了。
|
||||
|
||||
那么std是什么时候使用?
|
||||
|
||||
一般来说,std都是要调用C++标准库时使用。如:使用标准库头文件iostream,要写上std,使用非标准库头文件iostream.h,不用写。
|
||||
|
||||
最后,有没有一种简单的方法不用重复输入std::?
|
||||
|
||||
当然有,如果在使用标准库头文件iostream时,不喜欢重复地使用std,我们可以用一种类似通告的形式告诉编译器我们将使用标准库函数cout和endl。
|
||||
|
||||
可以使用using std::cout;将cout释放出来。
|
||||
|
||||
同样使用using std::endl;将endl释放出来。
|
||||
|
||||
这样在cout和endl前面就不要加std::。
|
||||
|
||||
其实我们还有种更加简便的方法,就是直接使用using namespace std来代替using std::cout和using std::endl。using namespace std告诉编译器我们将要使用命名空间std中的函数或者对象,所以cout和endl前面不用注明它们是std这个命名空间中的cout何endl。
|
||||
372
Zim/Programme/C++/list与linkedlist、arrylist、Vector、Map区别.txt
Normal file
@@ -0,0 +1,372 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-11-13T16:26:32+08:00
|
||||
|
||||
====== list与linkedlist、arrylist、Vector、Map区别 ======
|
||||
Created Sunday 13 November 2011
|
||||
http://wxg6203.iteye.com/blog/763483
|
||||
|
||||
===== List与LinkedList =====
|
||||
* List是__数组__链表
|
||||
* LinkedList是__指针__链表
|
||||
|
||||
选择List还是LinkedList要看你的使用特点.
|
||||
|
||||
* 数组链表访问快,复杂度O(1),但是添加删除复杂度O(n)
|
||||
* 指针链表访问复杂度是O(n),但是添加删除很快O(1)
|
||||
|
||||
只不过一般有习惯而已,比如二叉树,一般都是用指针实现,你想用数组实现也没有任何问题.而且有的时候算法需要数组实现.
|
||||
|
||||
你需要了解一个数据结构特点,进行算法复杂度分析,就能够针对你的应用程序选择合适的方法.
|
||||
LinkedList和ArrayList分别是list最常用的两个子类, LinkedList善于频繁的增,删操作 ;ArrayList善于快速查找; 线性表,链表,哈希表是常用的数据结构.
|
||||
|
||||
|
||||
===== ArrayList Vector LinkedList 区别与用法 =====
|
||||
|
||||
ArrayList 和Vector是采用__数组__方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,都允许直接序号索引元素,但是插入数据要设计到数组元素移动等内存操作,所以索引数据快插入数据慢,Vector由于使用了synchronized方法(线程安全)所以性能上比ArrayList要差,LinkedList使用__双向链表__实现存储,按序号索引数据需要进行向前或向后遍历,但是插入数据时只需要记录本项的前后项即可,所以插入数度较快!
|
||||
|
||||
线性表,链表,哈希表是常用的数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构。这些类均在java.util包中。本文试图通过简单的描述,向读者阐述各个类的作用以及如何正确使用这些类。
|
||||
|
||||
Collection
|
||||
├List
|
||||
│├LinkedList
|
||||
│├ArrayList
|
||||
│└Vector
|
||||
│ └Stack
|
||||
└Set
|
||||
Map
|
||||
├Hashtable
|
||||
├HashMap
|
||||
└WeakHashMap
|
||||
|
||||
===== Collection接口 =====
|
||||
|
||||
Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。
|
||||
所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。
|
||||
如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下:
|
||||
Iterator it = collection.iterator(); // 获得一个迭代子
|
||||
while(it.hasNext()) {
|
||||
Object obj = it.next(); // 得到下一个元素
|
||||
}
|
||||
由Collection接口派生的两个接口是List和Set。
|
||||
|
||||
List接口
|
||||
List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。
|
||||
和下面要提到的Set不同,List允许有相同的元素。
|
||||
除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。
|
||||
实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。
|
||||
|
||||
LinkedList类
|
||||
LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
|
||||
注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:
|
||||
List list = Collections.synchronizedList(new LinkedList(...));
|
||||
|
||||
ArrayList类
|
||||
ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。
|
||||
size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。
|
||||
每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。
|
||||
和LinkedList一样,ArrayList也是非同步的(unsynchronized)。
|
||||
|
||||
Vector类
|
||||
Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。
|
||||
|
||||
Stack 类
|
||||
Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。
|
||||
|
||||
Set接口
|
||||
Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。
|
||||
很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。
|
||||
请注意:必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。
|
||||
|
||||
Map接口
|
||||
请注意,Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。
|
||||
|
||||
Hashtable类
|
||||
Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。
|
||||
添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。
|
||||
Hashtable通过initial capacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大,这会影响像get和put这样的操作。
|
||||
使用Hashtable的简单示例如下,将1,2,3放到Hashtable中,他们的key分别是”one”,”two”,”three”:
|
||||
Hashtable numbers = new Hashtable();
|
||||
numbers.put(“one”, new Integer(1));
|
||||
numbers.put(“two”, new Integer(2));
|
||||
numbers.put(“three”, new Integer(3));
|
||||
要取出一个数,比如2,用相应的key:
|
||||
Integer n = (Integer)numbers.get(“two”);
|
||||
System.out.println(“two = ” + n);
|
||||
由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。hashCode和equals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。
|
||||
如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。
|
||||
Hashtable是同步的。
|
||||
|
||||
HashMap类
|
||||
HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。,但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。
|
||||
|
||||
WeakHashMap类
|
||||
WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。
|
||||
|
||||
总结
|
||||
如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。
|
||||
如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。
|
||||
要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。
|
||||
尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。
|
||||
|
||||
同步性
|
||||
Vector是同步的。这个类中的一些方法保证了Vector中的对象是线程安全的。而ArrayList则是异步的,因此ArrayList中的对象并不是线程安全的。因为同步的要求会影响执行的效率,所以如果你不需要线程安全的集合那么使用ArrayList是一个很好的选择,这样可以避免由于同步带来的不必要的性能开销。
|
||||
数据增长
|
||||
从内部实现机制来讲ArrayList和Vector都是使用数组(Array)来控制集合中的对象。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。
|
||||
使用模式
|
||||
在ArrayList和Vector中,从一个指定的位置(通过索引)查找数据或是在集合的末尾增加、移除一个元素所花费的时间是一样的,这个时间我们用O(1)表示。但是,如果在集合的其他位置增加或移除元素那么花费的时间会呈线形增长:O(n-i),其中n代表集合中元素的个数,i代表元素增加或移除元素的索引位置。为什么会这样呢?以为在进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行位移的操作。这一切意味着什么呢?
|
||||
这意味着,你只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。如果是其他操作,你最好选择其他的集合操作类。比如,LinkList集合类在增加或移除集合中任何位置的元素所花费的时间都是一样的?O(1),但它在索引一个元素的使用缺比较慢-O(i),其中i是索引的位置.使用ArrayList也很容易,因为你可以简单的使用索引来代替创建iterator对象的操作。LinkList也会为每个插入的元素创建对象,所有你要明白它也会带来额外的开销。
|
||||
最后,在《Practical Java》一书中Peter Haggar建议使用一个简单的数组(Array)来代替Vector或ArrayList。尤其是对于执行效率要求高的程序更应如此。因为使用数组(Array)避免了同步、额外的方法调用和不必要的重新分配空间的操作。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
最后说一下比较器
|
||||
|
||||
|
||||
|
||||
1.自定义一个比较器comp实现Comparator接口的compare方法
|
||||
|
||||
public int compare(Person o1, Person o2) ...
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
2.类直接实现Comparable 接口
|
||||
|
||||
public int compareTo(Object o) {
|
||||
|
||||
return this.age-((UserPo)o).getAge();
|
||||
|
||||
}
|
||||
|
||||
// 执行排序方法
|
||||
|
||||
Collections.sort(array);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
这两个接口的比较:
|
||||
1:Comparable是在集合内部定义的方法实现的排序,Comparator是在集合外部实现的排序
|
||||
2:一个类实现了Camparable接口则表明这个类的对象之间是可以相互比较的,这个类对象组成的集合就可以直接使用sort方法排序。一般我们写的bean都要实现这一接口,这也是标准javabean的规范。
|
||||
3:Comparator可以看成一种算法的实现,将算法和数据分离,Comparator也可以在下面两种环境下使用:
|
||||
1、类的设计师没有考虑到比较问题而没有实现Comparable,可以通过Comparator来实现排序而不必改变对象本身
|
||||
2、可以使用多种排序标准,比如升序、降序等。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
此外需要注意的Java中HashMap,LinkedHashMap,TreeMap的区别
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
HashMap,LinkedHashMap,TreeMap都属于Map
|
||||
|
||||
Map 主要用于存储键(key)值(value)对,根据键得到值,因此键不允许键重复,但允许值重复。
|
||||
HashMap 是一个最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度。HashMap最多只允许一条记录的键为Null;允许多条记录的值为Null;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步,可以用Collections的synchronizedMap方法使HashMap具有同步的能力。它的顺序是随机的。
|
||||
|
||||
LinkedHashMap LinkedHashMap也是一个HashMap,但是内部维持了一个双向链表,可以保持顺序(按照写入的顺序)
|
||||
|
||||
TreeMap 不仅可以保持顺序,而且可以用于排序
|
||||
|
||||
|
||||
|
||||
具体看代码
|
||||
Java代码 收藏代码
|
||||
|
||||
Map<String, String> map = new TreeMap<String, String>(
|
||||
new Comparator<Object>() {
|
||||
Collator collator = Collator.getInstance();
|
||||
|
||||
public int compare(Object o1, Object o2) {
|
||||
CollationKey key1 = collator.getCollationKey(o1
|
||||
.toString());
|
||||
System.out.println(key1);
|
||||
CollationKey key2 = collator.getCollationKey(o2
|
||||
.toString());
|
||||
System.out.println(key2);
|
||||
return key1.compareTo(key2);
|
||||
// return collator.compare(o1, o2);
|
||||
}
|
||||
});
|
||||
map.put("a3", "aa");
|
||||
map.put("a2", "bb");
|
||||
map.put("b1", "cc");
|
||||
for (Iterator iterator = map.values().iterator(); iterator
|
||||
.hasNext();) {
|
||||
String name = (String) iterator.next();
|
||||
System.out.println(name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
比较结果是:bb aa cc
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Set 排序
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param args
|
||||
* TreeSet通过比较hashcode的值来排序.String的hashcode的值是将String拆成n个Character
|
||||
* 然后用这个公式:s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]来算hashcode的值.
|
||||
* 而每个Character的hashcode的值,就是Character转换成int得到的值.这个int就UTF-8编码
|
||||
* 中的字符对应的int值("众":20247)
|
||||
*/
|
||||
Java代码 收藏代码
|
||||
|
||||
String[] s = { "文", "中", "排", "序" };
|
||||
|
||||
Set<String> set = new TreeSet<String>();
|
||||
for (int i = 0; i < s.length; i++) {
|
||||
set.add(s[i]);
|
||||
// 打印出字符在UTF-8中对应的int
|
||||
System.out.println((int) s[i].charAt(0));
|
||||
}
|
||||
for (Iterator iterator = set.iterator(); iterator.hasNext();) {
|
||||
String string = (String) iterator.next();
|
||||
// 打印顺序
|
||||
System.out.print(string + " : ");
|
||||
// 打印该字符对应的hashcode值
|
||||
System.out.println(string.hashCode());
|
||||
}
|
||||
|
||||
|
||||
|
||||
运行结果:
|
||||
|
||||
|
||||
|
||||
中 : 20013
|
||||
序 : 24207
|
||||
排 : 25490
|
||||
文 : 25991
|
||||
|
||||
|
||||
|
||||
如果改成LinkedHashSet 则按照插入的顺序来排序。TreeSet可以实现一个内部的接口来做到
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 执行排序方法
|
||||
|
||||
Collections.sort(array,comp);
|
||||
@@ -0,0 +1,16 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-11-13T17:12:42+08:00
|
||||
|
||||
====== STL中List,Vector,Map,Set的理解 ======
|
||||
Created Sunday 13 November 2011
|
||||
|
||||
List封装了链表,Vector封装了数组, list和vector得最主要的区别在于vector使用连续内存存储的,他支持[]运算符,而list是以链表形式实现的,不支持[]。
|
||||
|
||||
Vector对于随机访问的速度很快,但是对于插入尤其是在头部插入元素速度很慢,在尾部插入速度很快。List对于随机访问速度慢得多,因为可能要遍历整个链表才能做到,但是对于插入就快的多了,不需要拷贝和移动数据,只需要改变指针的指向就可以了。另外对于新添加的元素,Vector有一套算法,而List可以任意加入。
|
||||
Map,Set属于标准关联容器,使用了非常高效的平衡检索二叉树:红黑树,他的插入删除效率比其他序列容器高是因为不需要做内存拷贝和内存移动,而直接替换指向节点的指针即可。
|
||||
Set和Vector的区别在于Set不包含重复的数据。Set和Map的区别在于Set只含有Key,而Map有一个Key和Key所对应的Value两个元素。
|
||||
Map和Hash_Map的区别是Hash_Map使用了Hash算法来加快查找过程,但是需要更多的内存来存放这些Hash桶元素,因此可以算得上是采用空间来换取时间策略。
|
||||
|
||||
|
||||
转载:http://blog.csdn.net/hbhhww/archive/2009/05/07/4157799.aspx
|
||||
@@ -0,0 +1,105 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-11-13T17:13:25+08:00
|
||||
|
||||
====== stl提供了三个最基本的容器 ======
|
||||
Created Sunday 13 November 2011
|
||||
http://topic.csdn.net/t/20011226/08/442147.html
|
||||
|
||||
|
||||
我在学习使用stl的过程中,觉得用list和用vector没什么区别,所能使用的方法都差不多,只是觉得vector会稍微会点。究竟list和vector各自适用在什么场合,我却没有一点头绪。
|
||||
|
||||
|
||||
#2楼 得分:0回复于:2001-12-26 09:21:35
|
||||
vector是向量,list是链表。
|
||||
|
||||
|
||||
#4楼 得分:0回复于:2001-12-26 09:46:14
|
||||
vector 内部实现-分配内存方式与list不同 no "insert "
|
||||
|
||||
|
||||
#7楼 得分:0回复于:2001-12-26 09:53:49
|
||||
vector内部使用线性内存分配,可以随机访问,支持operator[]
|
||||
list内部使用练是数据结构,不支持随机访问和operator[]
|
||||
|
||||
|
||||
#9楼 得分:0回复于:2001-12-26 09:59:17
|
||||
就象数组和链表的关系,vector一个是__连续存储__,跟数组的访问方法差不多,list就跟链表一样,靠连接关系,可以__连续存储与访问,也可以不连续__。不连续存储的叫__Linked List.__
|
||||
另外list由于链接关系,不能用[ ]随机访问。
|
||||
|
||||
#11楼 得分:8回复于:2001-12-26 10:17:37
|
||||
从操作方法上看,二者没有多少区别,但在执行不同操作时性能有差别:
|
||||
vector对任意元素的存取是等时的(const time),所以适合随机访问,对插入和删除则比较慢
|
||||
list比较适合遍历访问,随机访问比较慢,但插入和删除速度比较快
|
||||
|
||||
我感觉,二者的差别主要是语义上的。一般情况下,当需要频繁地删除和插入操作,应采用list,否则用vector.
|
||||
|
||||
|
||||
#12楼 得分:36回复于:2001-12-26 10:34:15
|
||||
|
||||
**我来谈谈我的看法:**
|
||||
stl提供了三个最基本的容器:vector,list,deque。
|
||||
|
||||
vector和built-in数组类似,它拥有一段**连续**的内存空间,并且起始地址不变,因此
|
||||
它能非常好的支持__随机存取__,即[]操作符,但由于它的内存空间是连续的,所以在中间
|
||||
进行插入和删除会造成内存块的拷贝,另外,当该数组后的内存空间不够时,需要重新
|
||||
申请一块足够大的内存并进行内存的拷贝。这些都大大影响了vector的效率。
|
||||
|
||||
list就是数据结构中的双向链表(根据sgi stl源代码),因此它的内存空间**可以(不是一定)**是不连续
|
||||
的,通过指针来进行数据的访问,这个特点使得它的随即存取变的非常没有效率,因此它
|
||||
没有提供[]操作符的重载。但由于链表的特点,它可以以很好的效率支持任意地方的删除
|
||||
和插入。
|
||||
|
||||
deque是一个double-ended queue,它的具体实现不太清楚,但知道它具有以下两个特点:
|
||||
__它支持[ ]操作符,也就是支持随即存取__,并且和vector的效率相差无几,它支持在两端的
|
||||
操作:push_back,push_front,pop_back,pop_front等,并且在两端操作上与list的效率
|
||||
也差不多。
|
||||
|
||||
因此在实际使用时,如何选择这三个容器中哪一个,应根据你的需要而定,一般应遵循下面
|
||||
的原则:
|
||||
1、如果你需要高效的随即存取,而不在乎插入和删除的效率,使用vector
|
||||
2、如果你需要大量的插入和删除,而不关心随即存取,则应使用list
|
||||
3、如果你需要随即存取,而且关心两端数据的插入和删除,则应使用deque。
|
||||
|
||||
个人见解,如有不同意见,欢迎讨论!
|
||||
|
||||
|
||||
#14楼 得分:0回复于:2001-12-26 10:45:17
|
||||
基本同意云龙的看法,在使用过程中vector确实没有pop_front,push_front的。
|
||||
但我在简单测试vector和list的时候,在连续插入一百万条数据时,vector的速度会比list稍微会一点点。
|
||||
|
||||
非常感谢大家!
|
||||
|
||||
|
||||
#15楼 得分:0回复于:2001-12-26 10:47:19
|
||||
还有其它很多种容器,如queue,stack,map等,分别适用于不同的需要,如要全面掌握,
|
||||
建议看the C++ Programming Language。
|
||||
|
||||
|
||||
|
||||
|
||||
#16楼 得分:0回复于:2001-12-26 10:50:23
|
||||
请问在哪可以找到the C++ Programming Language
|
||||
|
||||
对我有用[0]
|
||||
丢个板砖[0]
|
||||
引用
|
||||
举报
|
||||
管理
|
||||
TOP
|
||||
|
||||
ylong用户头像
|
||||
ylong
|
||||
(云龙)
|
||||
等 级:
|
||||
|
||||
|
||||
#17楼 得分:0回复于:2001-12-26 10:58:16
|
||||
faint,当然是到书店了!Stroustrup所著C++第一经典著作,当然现在只有
|
||||
英文版的,中文版估计三个月之内能够上市。
|
||||
|
||||
另外,如果想更深入的了解stl,建议看看sgi stl的源代码!
|
||||
|
||||
#20楼 得分:0回复于:2002-02-05 11:41:34
|
||||
SGI STL的源代码及文档下载:
|
||||
http://www.sgi.com/tech/stl/download.html
|
||||
101
Zim/Programme/C++/临时对象不能被绑定到非const引用参数上.txt
Normal file
@@ -0,0 +1,101 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T16:28:54+08:00
|
||||
|
||||
====== 临时对象不能被绑定到非const引用参数上 ======
|
||||
Created Saturday 06 August 2011
|
||||
http://blog.csdn.net/liuxialong/article/details/6539717
|
||||
|
||||
概括一下:
|
||||
|
||||
__不能把临时对象作为实参传给非const引用。__
|
||||
|
||||
例如:
|
||||
void conv(string &str){}
|
||||
int main()
|
||||
{
|
||||
conv("dasd");//这里错了,编译器自动生成一个string(“dasd”)__临时对象__,不能将该临时对象传给非const引用
|
||||
}
|
||||
|
||||
因此,需要将其改为
|
||||
|
||||
void conv(string str){} //__值传递__
|
||||
|
||||
或者
|
||||
|
||||
void conv(const string &str){} //const引用,因为__标准规定临时对象是不能更改的__,所以要加上const修饰
|
||||
|
||||
那么这里涉及到一个问题,什么时候会出现临时对象呢????
|
||||
|
||||
==== 临时对象的定义: ====
|
||||
|
||||
来看一个例子,在c++里,这个tmp是局部对象(变量),而不是临时对象
|
||||
|
||||
template <T>
|
||||
void swap(T &obj1, T&obj2)
|
||||
{
|
||||
obj tmp=obj1;//__这里的tmp不能被称为临时对象,它只是局部对象__
|
||||
obj1=obj2;
|
||||
obj2=tmp;
|
||||
}
|
||||
|
||||
在C++中__真正的临时对象是看不见的__,它们不出现在你的源代码中。
|
||||
__PS:__这倒不一定,例如:
|
||||
ration t = a + ration(3);
|
||||
上面的a是一个ration实例,ration(3)就产生一个没有名字的临时对像,同时 a + ration(3)的结果也是一个临时对像(这个对像是编译器自动生成,不可见)。
|
||||
|
||||
==== 建立一个没有命名的非堆(non-heap)对象会产生临时对象。 ====
|
||||
|
||||
这种未命名的对象通常在两种条件下产生:__为了使函数成功调用而进行隐式类型转换和函数返回对象时__。
|
||||
|
||||
理解如何和为什么建立这些临时对象是很重要的,因为构造和释放它们的开销对于程序的性能来说有不可忽视的影响。
|
||||
|
||||
=== 1、首先考虑为使函数成功调用而建立临时对象这种情况。 ===
|
||||
|
||||
当__传送给函数的对象类型与参数类型不匹配时会产生这种情况__。
|
||||
|
||||
例如文章开头的那个例子,conv函数的参数是string型对像的引用,可是传入的实参却是“dasd”,因此,编译器会自动进行隐式转换,生成一个string(“dasd”)的临时对象,再把这个对象传入,这个没有命名的临时对象当然不能修改了,因此必须加上const修饰。
|
||||
|
||||
这就是文章一开头的那个问题产生的原因。
|
||||
|
||||
=== 2、建立临时对象的第二种环境是函数返回对象时。 ===
|
||||
|
||||
例如operator+必须返回一个对象,以表示它的两个操作数的和(参见Effective C++ 条款23)。
|
||||
|
||||
给定一个类型Number,这种类型的operator+被这样声明:
|
||||
|
||||
__const__ Number operator+(const Number& lhs,
|
||||
|
||||
const Number& rhs);
|
||||
|
||||
这个函数的返回值是临时的,因为它没有被命名;它只是函数的返回值。
|
||||
|
||||
你必须为每次调用operator+构造和释放这个对象而付出代价。
|
||||
PS:这个开头的const可加也可以不加。
|
||||
|
||||
==== 关于临时对象的总结: ====
|
||||
|
||||
临时对象是有开销的,所以你应该尽可能地去除它们,然而更重要的是训练自己寻找可能建立临时对象的地方。
|
||||
1、在任何时候只要见到常量引用(reference-to-const)参数,就存在建立临时对象而绑定在参数上的可能性。====》就是文章一开头的那个问题
|
||||
2、在任何时候只要见到函数返回对象(而非对像的引用),就会有一个临时对象被建立(以后被释放)。
|
||||
3. 非const 引用只能绑定到同一个、同类型的对像,即绑定到传入或赋值号右边的同一个对像。
|
||||
4 . const引用可以绑定到不是同一个但相关的类型变量或右值,这种情况下__其实const引用绑定到一个临时对象__,若让非const对象绑定到临时对象,则对其的改变只是改变了临时对象,而欲绑定的对象并没有改变,所以临时对象只能绑定到只读的const引用
|
||||
|
||||
如int i=12;
|
||||
|
||||
const double &j=i;
|
||||
|
||||
其实编译器做会把上述代码转换为如下形式
|
||||
|
||||
double temp=jl
|
||||
|
||||
const double &j=temp;
|
||||
|
||||
因为const 引用是只读类型,所以可以绑定到temp引用
|
||||
|
||||
ration a1(2, 3);
|
||||
ration &a2 = a1 + ration(2, 3); //这是错误的,因为加号生成的是一个临时对像,a2为非const引用,故不能绑定到临时对像。
|
||||
const ration &a2 = a1 + ration(2, 3) ;// 这是正确的。
|
||||
|
||||
__函数参数或返回值类型为类对象时,在传参和返回时就有可能生成临时对象。这时就会调用复制构造函数,由于临时对象的引用只能是常引用。__
|
||||
__因此复制构造函数的参数类型固定为常引用类型。__
|
||||
260
Zim/Programme/C++/临时对象不能被绑定到非const引用参数上/C++临时对象.txt
Normal file
@@ -0,0 +1,260 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T17:15:28+08:00
|
||||
|
||||
====== C++临时对象 ======
|
||||
Created Saturday 06 August 2011
|
||||
http://space.itpub.net/10697500/viewspace-660896
|
||||
|
||||
书归正传,我们知道在C++的创建对象是一个费时,费空间的一个操作。有些固然是必不可少,但还有一些对象却在我们不知道的情况下被创建了。通常以下三种情况会产生临时对象:
|
||||
1,以值的方式给函数传参;
|
||||
2,类型转换;
|
||||
3,函数需要返回一个对象时;
|
||||
|
||||
现在我们依次看这三种情况:
|
||||
|
||||
一,以值的方式给函数传参。
|
||||
|
||||
我们知道给函数传参有两种方式。1,按值传递;2,按引用传递。按值传递时,首先将需要传给函数的参数,调用拷贝构造函数创建一个副本,所有在函数里的操作都是针对这个副本的,也正是因为这个原因,在函数体里对该副本进行任何操作,都不会影响原参数。我们看以下例子:
|
||||
|
||||
|
||||
/*-----------------------
|
||||
Platform.:WinXp + VC6
|
||||
-----------------------*/
|
||||
#include <stdio.h>
|
||||
class CTemp
|
||||
{
|
||||
public:
|
||||
int a;
|
||||
int b;
|
||||
public:
|
||||
CTemp(CTemp& t){ printf("Copy function!\n");a = t.a;b = t.b;};
|
||||
CTemp(int m = 0,int n = 0);
|
||||
virtual ~CTemp(){};
|
||||
public:
|
||||
int GetSum(CTemp ts);
|
||||
};
|
||||
CTemp::CTemp(int m , int n)
|
||||
{
|
||||
printf("Construct function!\n");
|
||||
a = m;b=n;
|
||||
printf("a = %d\n",a);
|
||||
printf("b = %d\n",b);
|
||||
}
|
||||
int CTemp::GetSum(CTemp ts)
|
||||
{
|
||||
int tmp = ts.a + ts.b;
|
||||
ts.a = 1000; //此时修改的是tm的一个副本
|
||||
|
||||
return tmp;
|
||||
}
|
||||
//--------------Main函数-----------------
|
||||
void main()
|
||||
{
|
||||
CTemp tm(10,20);
|
||||
printf("Sum = %d \n",tm.GetSum(tm));
|
||||
printf("tm.a = %d \n",tm.a);
|
||||
}
|
||||
|
||||
--------------------------------------------------------
|
||||
Output:
|
||||
Construct function!
|
||||
a = 10
|
||||
b = 20
|
||||
Copy function!
|
||||
Sum = 30
|
||||
tm.a = 10
|
||||
------------------------------------------------------
|
||||
我们看到有调用了拷贝构造函数,这是tm在传给GetSum做参数时:
|
||||
1,调用拷贝构造函数来创建一个副本为GetSum函数体内所用。
|
||||
2,在GetSum函数体内对tm副本进行的修改并没有影响到tm本身。
|
||||
|
||||
解决办法:
|
||||
针对第一种情况的解决办法是传入对象引用(记住:引用只是原对象的一个别名(Alias)),我们将GetSum代码修改如下:
|
||||
|
||||
|
||||
int CTemp::GetSum(CTemp& ts)
|
||||
{
|
||||
int tmp = ts.a + ts.b;
|
||||
ts.a = 1000; //此时通过ts这个引用参考(refer to)对象本身
|
||||
return tmp;
|
||||
}
|
||||
----------------------------------------------------------
|
||||
Output:
|
||||
Construct function!
|
||||
a = 10
|
||||
b = 20
|
||||
Sum = 30
|
||||
tm.a = 1000
|
||||
--------------------------------------------------------
|
||||
可以通过输出看本,通过传递常量引用,减少了一次临时对象的创建。这个改动也许很小,但对多继承的对象来说在构建时要递归调用所有基类的构造函数,这对于性能来说是个很大的消耗,而且这种消耗通常来说是没有必要的。
|
||||
|
||||
二,类型转换生成的临时对象。
|
||||
|
||||
我们在做类型转换时,转换后的对象通常是一个临时对象。编译器为了通过编译会创建一起我们不易察觉的临时对象。再次修改如上main代码:
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
CTemp tm(10,20),sum;
|
||||
sum = 1000; //调用CTemp(int m = 0,int n = 0)构造函数
|
||||
printf("Sum = %d \n",tm.GetSum(sum));
|
||||
}
|
||||
|
||||
-----------------------------------------------------------
|
||||
Output:
|
||||
Construct function!
|
||||
a = 10
|
||||
b = 20
|
||||
Construct function!
|
||||
a = 0
|
||||
b = 0
|
||||
Construct function!
|
||||
a = 1000
|
||||
b = 0
|
||||
Sum = 1000
|
||||
----------------------------------------------------------
|
||||
main函数创建了两个对象,但输出却调用了三次构造函数,这是为什么呢?
|
||||
关键在 sum = 1000;这段代码。本身1000和sum类型不符,但编译器为了通过编译以1000为参调用构造函数创建了一下临时对象。
|
||||
|
||||
解决办法:
|
||||
我们对main函数中的代码稍作修改,将sum申明推迟到“=”号之前:
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
CTemp tm(10,20);
|
||||
CTemp sum = 1000;
|
||||
printf("Sum = %d \n",tm.GetSum(sum));
|
||||
}
|
||||
----------------------------------------------------------
|
||||
Output:
|
||||
Construct function!
|
||||
a = 10
|
||||
b = 20
|
||||
Construct function!
|
||||
a = 1000
|
||||
b = 0
|
||||
Sum = 1000
|
||||
----------------------------------------------------------
|
||||
只作了稍稍改动,就减少了一次临时对象的创建。
|
||||
1,此时的“=”号由原本的赋值变为了构造。
|
||||
2,对Sum的构造推迟了。当我们定义CTmep sum时,在main的栈中为sum对象创建了一个预留的空间。而我们用1000调用构造时,此时的构造是在为sum预留的空间中进行的。因此也减少了一次临时对象的创建。
|
||||
|
||||
三,函数返回一个对象。
|
||||
|
||||
当函数需要返回一个对象,他会在栈中创建一个临时对象,存储函数的返回值。看以下代码:
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
class CTemp
|
||||
{
|
||||
public:
|
||||
int a;
|
||||
public:
|
||||
CTemp(CTemp& t) //Copy Ctor!
|
||||
{
|
||||
printf("Copy Ctor!\n");
|
||||
a = t.a;
|
||||
};
|
||||
CTemp& operator=(CTemp& t) //Assignment Copy Ctor!
|
||||
{
|
||||
printf("Assignment Copy Ctor!\n");
|
||||
a = t.a;
|
||||
return *this;
|
||||
}
|
||||
CTemp(int m = 0);
|
||||
virtual ~CTemp(){};
|
||||
};
|
||||
CTemp::CTemp(int m) //Copy Ctor!
|
||||
{
|
||||
printf("Construct function!\n");
|
||||
a = m;
|
||||
printf("a = %d\n",a);
|
||||
}
|
||||
CTemp Double(CTemp& ts)
|
||||
{
|
||||
CTemp tmp; //构建一个临时对象
|
||||
tmp.a = ts.a*2;
|
||||
return tmp;
|
||||
|
||||
}
|
||||
//-------------Main函数-----------------
|
||||
void main()
|
||||
{
|
||||
CTemp tm(10),sum;
|
||||
printf("\n\n");
|
||||
|
||||
sum = Double(tm);
|
||||
|
||||
|
||||
printf("\n\nsum.a = %d \n",sum.a);
|
||||
}
|
||||
---------------------------------------------------------
|
||||
Output:
|
||||
Construct function!
|
||||
a = 10
|
||||
Construct function!
|
||||
a = 0
|
||||
|
||||
Construct function!
|
||||
a = 0
|
||||
Copy Ctor!
|
||||
Assignment Copy Ctor!
|
||||
|
||||
sum.a = 20
|
||||
--------------------------------------------------------
|
||||
我特地加宽了语句:
|
||||
sum = Double(tm);
|
||||
这条语句竟生成了两个对象,Horrible! 我们现在将这条语句逐步分解一下:
|
||||
1,我们显式创建一个tmp临时对象,
|
||||
语句:CTemp tmp;
|
||||
2,将temp对象返回,返回过程中调用Copy cotr创建一个返回对象,
|
||||
语句:return tmp;
|
||||
|
||||
3,将返回结果通过调用赋值拷贝函数,赋给sum
|
||||
语句: sum = 函数返回值;(该步并没有创建对象,只是给sum赋值)
|
||||
|
||||
tm.Double返回一个用拷贝构造函数生成的临时对象,并用该临时对象给sum赋值.
|
||||
上面的第1步创建对象可以不用创建,我们可以直接对返回值进行操作,有些C++编译器中会有一种优化,叫做(NRV,named return value).不过本人使用的VC++6.0并没有这个启用这个优化。
|
||||
第2步创建的返回对象是难以避免的,你或许想可以返回一个引用,但你别忘记了在函数里创建的局部对象,在返回时就被销毁了。这时若再引用该对象会产生未预期的行为。(C#中解决了这个问题)。
|
||||
|
||||
解决方法:
|
||||
我们将对象直接操作(Manipulate)返回对象,再结合上面的减少临时对象的方法,将函数Double的代码,及main函数中的代码修改如下:
|
||||
|
||||
|
||||
CTemp Double(CTemp& ts)
|
||||
{
|
||||
return ts.a*2;
|
||||
}
|
||||
/*--------上面的代码相当于-------
|
||||
CTemp _ret
|
||||
void Double(CTemp& ts)
|
||||
{
|
||||
_ret.a = ts.a*2;
|
||||
}
|
||||
---------------*/
|
||||
|
||||
|
||||
//---------Main函数-----------
|
||||
void main()
|
||||
{
|
||||
CTemp tm(10);
|
||||
printf("\n\n");
|
||||
|
||||
CTemp sum = Double(tm);
|
||||
|
||||
printf("\n\nsum.a = %d \n",sum.a);
|
||||
}
|
||||
--------------------------------------------------------
|
||||
Output:
|
||||
Construct function!
|
||||
a = 10
|
||||
|
||||
Construct function!
|
||||
a = 20
|
||||
|
||||
sum.a = 20
|
||||
-------------------------------------------------------
|
||||
发现减少了一次构造函数调用(tmp),一次拷贝构造函数(tmp拷贝给返回对象)调用和一次赋值拷贝函数调用.(Assignment Copy Ctor),这是因为:
|
||||
返回对象直接使用为sum预留的空间,所以减少了返回临时对象的生成——返回对象即是sum,返回对象的创建即是sum对象的创建.多么精妙!
|
||||
86
Zim/Programme/C++/临时对象不能被绑定到非const引用参数上/临时对像的思考.txt
Normal file
@@ -0,0 +1,86 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T16:47:10+08:00
|
||||
|
||||
====== 临时对像的思考 ======
|
||||
Created Saturday 06 August 2011
|
||||
http://www.cnblogs.com/welkinwalker/archive/2011/03/10/1979745.html
|
||||
|
||||
由无名对象(临时对象)引发的关于“引用”的思考
|
||||
|
||||
预备知识:
|
||||
|
||||
* __无名对象,也叫临时对象__。指的是**直接由构造函数产生**,但是__没有被任何符号所引用的对象__。例如:string("abc"),这句话产生的就是一个无名对象,这个对象产生以后,没有什么办法使用它。但是对于string str("abc")来说,则产生的是一个有名字的对象,他的名字就是 str。
|
||||
* __任何引用必须初始化。__
|
||||
* __const(对象)变量只能传递给const引用,不能传递给非const引用__。假如说把一个const的对象传递给了非const引用,那么修改这个引用就相当于修改了原来的const对象,这个违反了const约束。这种方式不能通过编译,会报error: passing `const ref' as `this' argument of `void ref::change()' discards qualifiers。
|
||||
|
||||
请观察下面的代码的微妙差别,特别留意注释中的说明
|
||||
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
|
||||
class ref
|
||||
{
|
||||
public:
|
||||
ref(int input);
|
||||
~ref();
|
||||
int i;
|
||||
void change();
|
||||
**void change_const() const;**
|
||||
|
||||
};
|
||||
|
||||
void ref::change_const() const
|
||||
{
|
||||
cout<<"in change_const"<<endl;
|
||||
}
|
||||
void ref::change()
|
||||
{
|
||||
i=3;
|
||||
cout<<"in change"<<endl;
|
||||
}
|
||||
ref::ref(int input)
|
||||
{
|
||||
i=input;
|
||||
}
|
||||
ref::~ref()
|
||||
{
|
||||
cout<<"tear down"<<endl;
|
||||
}
|
||||
|
||||
|
||||
void test_const(const string & str)
|
||||
{
|
||||
cout<<str<<endl;
|
||||
}
|
||||
|
||||
void test(string & str)
|
||||
{
|
||||
cout<<str<<endl;
|
||||
}
|
||||
|
||||
main()
|
||||
{
|
||||
// int &i=1; //__不能用非对象去初始化一个引用, 其实就是不能用一个常量来初始化一个引用。__
|
||||
注意: 其实上面的更深层原因是,编译器调用 int(1)生成一个__临时对像__,而临时对像只能用const同类型的引用。
|
||||
|
||||
const int &j=1;//这样可以,但是没什么实际意义
|
||||
|
||||
string str("haha"); // 这里创建的str是一个__有名的局部对像。__
|
||||
test(str);
|
||||
test_const(str);
|
||||
|
||||
// test(string("haha"));
|
||||
//报invalid initialization of non-const reference of type 'std::string&' from a temporary of type 'std::string'。使用__临时对象不能初始化test的string & 引用__。
|
||||
test_const(string("haha"));//__使用临时对象初始化函数形参的时候,函数形参必须是有const限定。__
|
||||
|
||||
|
||||
ref const obj(1);
|
||||
cout<<obj.i<<endl;
|
||||
obj.change_const();
|
||||
cout<<obj.i<<endl;
|
||||
|
||||
ref(2).change();__//无名对象调用非const函数,这说明无名对象(临时对象)并不能等同于用const修饰的有名对象,用const修饰的有名对象,是不允许调用非const方法的(不管该方法是否真的修改了对像的状态),因为那样会修改对象的成员。这可以从下面的例子看到。__
|
||||
__// obj.change();//const对象调用非const函数,报passing `const ref' as `this' argument of `void ref::change()' discards qualifiers(限定符)__
|
||||
//上面的意思是,obj对像是一个const ref类型的实例,但是调用的方法change()是非const型的(即丢弃了const限定符)。
|
||||
}
|
||||
58
Zim/Programme/C++/临时对象不能被绑定到非const引用参数上/临时对象的生存期.txt
Normal file
@@ -0,0 +1,58 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-10-28T17:08:05+08:00
|
||||
|
||||
====== 临时对象的生存期 ======
|
||||
Created Friday 28 October 2011
|
||||
|
||||
http://tech.e800.com.cn/articles/2009/1010/1255136951349_1.html
|
||||
|
||||
* 今天看了c++发明人的the c++ programming language(special edition)关于临时对象的说明,虽然是一小节,但感觉c++的书籍,论内容还是这本最权威,里面很多东西都直接确定的表达了出来,权威、确定而令人信服。
|
||||
|
||||
临时对象的生存时限在这本书中只花了不长的篇章,但说的很清楚:
|
||||
__临时对象的生存时限限制在其出现的“完整”的表达式中,“完整”的表达式结束了,临时对象也就销毁了。例外是把临时对象被引用或者初始化给具名对象,临时对象的生存周期会加长到引用或者具名对象的生存周期。__
|
||||
|
||||
底下的简短程序测试了临时对象的生存时限,vs2005很好的说明了临时对象在其“完整”表达式结束后销毁。
|
||||
*/
|
||||
|
||||
#include
|
||||
#include
|
||||
using namespace std;
|
||||
|
||||
/* 打印 字符串 */
|
||||
void print(const string& str)
|
||||
{
|
||||
cout << str.c_str() << endl;
|
||||
}
|
||||
|
||||
/* 打印 字符串2 */
|
||||
void display(string str)
|
||||
{
|
||||
cout << "xxxxxxxx" << endl;
|
||||
cout << str.c_str() << endl;
|
||||
cout << "xxxxxxxx" << endl;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
string str1 = "xiaocui";
|
||||
string str2 = ",nihao";
|
||||
const char* s = NULL;
|
||||
|
||||
if ( s = (str1 + str2).c_str() ) //此处s1+s2产生一个临时string对象,它的生存周期是临时对象所出现的“完整”的表达式的生存周期
|
||||
{
|
||||
cout << s << endl; //上面的临时对象已经在其“完整”的表达式结束后析构了,那么s指向的那块内存已经不存在了,此处引用s是未定义的,危险的
|
||||
}
|
||||
|
||||
string ss = str1 + str2; //str1+str2同样产生临时对象,但其用来初始化具名对象,所以临时对象的生存周期延长到具名对象的周期
|
||||
cout << ss.c_str() << endl; //正确输出,说明临时对象仍然存在,并没有销毁,存放字符串的存储正常存在
|
||||
|
||||
print(str1 + str2); //把str1+str2产生的临时对象传递给cosnt 引用,临时对象的生命周期延长到const引用的生命周期,所以正常打印
|
||||
display(str1 + str2); //函数传递具有初始化语义,所以str1+str2产生的临时对象初始化str,生命周期延长,正常打印
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
如果不深究,下面这句话可作为总体印象区别:
|
||||
* __临时对象只在当前行有效__
|
||||
* __局部对象到函数结束都有效__
|
||||
102
Zim/Programme/C++/临时对象不能被绑定到非const引用参数上====》扩展到临时对象问题.txt
Normal file
@@ -0,0 +1,102 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T16:28:54+08:00
|
||||
|
||||
====== 临时对象不能被绑定到非const引用参数上====》扩展到临时对象问题 ======
|
||||
Created Saturday 06 August 2011
|
||||
http://blog.csdn.net/liuxialong/article/details/6539717
|
||||
|
||||
概括一下:
|
||||
|
||||
__不能把临时对象作为实参传给非const引用。__
|
||||
|
||||
例如:
|
||||
void conv(string &str){}
|
||||
int main()
|
||||
{
|
||||
conv("dasd");//这里错了,编译器自动生成一个string(“dasd”)__临时对象__,不能将该临时对象传给非const引用
|
||||
}
|
||||
|
||||
因此,需要将其改为
|
||||
|
||||
void conv(string str){} //__值传递__
|
||||
|
||||
或者
|
||||
|
||||
void conv(const string &str){} //const引用,因为__标准规定临时对象是不能更改的__,所以要加上const修饰
|
||||
|
||||
那么这里涉及到一个问题,什么时候会出现临时对象呢????
|
||||
|
||||
==== 临时对象的定义: ====
|
||||
|
||||
来看一个例子,在c++里,这个tmp是局部对象(变量),而不是临时对象
|
||||
|
||||
template <T>
|
||||
void swap(T &obj1, T&obj2)
|
||||
{
|
||||
obj tmp=obj1;//__这里的tmp不能被称为临时对象,它只是局部对象__
|
||||
obj1=obj2;
|
||||
obj2=tmp;
|
||||
}
|
||||
|
||||
在C++中__真正的临时对象是看不见的__,它们不出现在你的源代码中。
|
||||
__PS:__这倒不一定,例如:
|
||||
ration t = a + ration(3);
|
||||
上面的a是一个ration实例,ration(3)就产生一个没有名字的临时对像,同时 a + ration(3)的结果也是一个临时对像(这个对像是编译器自动生成,不可见)。
|
||||
|
||||
==== 建立一个没有命名的非堆(non-heap)对象会产生临时对象。 ====
|
||||
|
||||
这种未命名的对象通常在两种条件下产生:__为了使函数成功调用而进行隐式类型转换和函数返回对象时__。
|
||||
|
||||
理解如何和为什么建立这些临时对象是很重要的,因为构造和释放它们的开销对于程序的性能来说有不可忽视的影响。
|
||||
|
||||
=== 1、首先考虑为使函数成功调用而建立临时对象这种情况。 ===
|
||||
|
||||
当__传送给函数的对象类型与参数类型不匹配时会产生这种情况__。
|
||||
|
||||
例如文章开头的那个例子,conv函数的参数是string型对像的引用,可是传入的实参却是“dasd”,因此,编译器会自动进行隐式转换,生成一个string(“dasd”)的临时对象,再把这个对象传入,这个没有命名的临时对象当然不能修改了,因此必须加上const修饰。
|
||||
|
||||
这就是文章一开头的那个问题产生的原因。
|
||||
|
||||
=== 2、建立临时对象的第二种环境是函数返回对象时。 ===
|
||||
|
||||
例如operator+必须返回一个对象,以表示它的两个操作数的和(参见Effective C++ 条款23)。
|
||||
|
||||
给定一个类型Number,这种类型的operator+被这样声明:
|
||||
|
||||
__const__ Number operator+(const Number& lhs,
|
||||
|
||||
const Number& rhs);
|
||||
|
||||
这个函数的返回值是临时的,因为它没有被命名;它只是函数的返回值。
|
||||
|
||||
你必须为每次调用operator+构造和释放这个对象而付出代价。
|
||||
PS:这个开头的const可加也可以不加。
|
||||
|
||||
==== 关于临时对象的总结: ====
|
||||
|
||||
临时对象是有开销的,所以你应该尽可能地去除它们,然而更重要的是训练自己寻找可能建立临时对象的地方。
|
||||
|
||||
1、在任何时候只要见到常量引用(reference-to-const)参数,就存在建立临时对象而绑定在参数上的可能性。====》就是文章一开头的那个问题
|
||||
2、在任何时候只要见到函数返回对象(而非对像的引用),就会有一个临时对象被建立(以后被释放)。
|
||||
3. 非const 引用只能绑定到同一个、同类型的对像,即绑定到传入或赋值号右边的同一个对像。
|
||||
4 . const引用可以绑定到不是同一个但相关的类型变量或右值,这种情况下__其实const引用绑定到一个临时对象__,若让非const对象绑定到临时对象,则对其的改变只是改变了临时对象,而欲绑定的对象并没有改变,所以临时对象只能绑定到只读的const引用
|
||||
|
||||
如int i=12;
|
||||
|
||||
const double &j=i;
|
||||
|
||||
其实编译器做会把上述代码转换为如下形式
|
||||
|
||||
double temp=jl
|
||||
|
||||
const double &j=temp;
|
||||
|
||||
因为const 引用是只读类型,所以可以绑定到temp引用
|
||||
|
||||
ration a1(2, 3);
|
||||
ration &a2 = a1 + ration(2, 3); //这是错误的,因为加号生成的是一个临时对像,a2为非const引用,故不能绑定到临时对像。
|
||||
const ration &a2 = a1 + ration(2, 3) ;// 这是正确的。
|
||||
|
||||
__函数参数或返回值类型为类对象时,在传参和返回时就有可能生成临时对象。这时就会调用复制构造函数,由于临时对象的引用只能是常引用。__
|
||||
__因此复制构造函数的参数类型固定为常引用类型。__
|
||||
@@ -0,0 +1,260 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T17:15:28+08:00
|
||||
|
||||
====== C++临时对象 ======
|
||||
Created Saturday 06 August 2011
|
||||
http://space.itpub.net/10697500/viewspace-660896
|
||||
|
||||
书归正传,我们知道在C++的创建对象是一个费时,费空间的一个操作。有些固然是必不可少,但还有一些对象却在我们不知道的情况下被创建了。通常以下三种情况会产生临时对象:
|
||||
1,以值的方式给函数传参;
|
||||
2,类型转换;
|
||||
3,函数需要返回一个对象时;
|
||||
|
||||
现在我们依次看这三种情况:
|
||||
|
||||
一,以值的方式给函数传参。
|
||||
|
||||
我们知道给函数传参有两种方式。1,按值传递;2,按引用传递。按值传递时,首先将需要传给函数的参数,调用拷贝构造函数创建一个副本,所有在函数里的操作都是针对这个副本的,也正是因为这个原因,在函数体里对该副本进行任何操作,都不会影响原参数。我们看以下例子:
|
||||
|
||||
|
||||
/*-----------------------
|
||||
Platform.:WinXp + VC6
|
||||
-----------------------*/
|
||||
#include <stdio.h>
|
||||
class CTemp
|
||||
{
|
||||
public:
|
||||
int a;
|
||||
int b;
|
||||
public:
|
||||
CTemp(CTemp& t){ printf("Copy function!\n");a = t.a;b = t.b;};
|
||||
CTemp(int m = 0,int n = 0);
|
||||
virtual ~CTemp(){};
|
||||
public:
|
||||
int GetSum(CTemp ts);
|
||||
};
|
||||
CTemp::CTemp(int m , int n)
|
||||
{
|
||||
printf("Construct function!\n");
|
||||
a = m;b=n;
|
||||
printf("a = %d\n",a);
|
||||
printf("b = %d\n",b);
|
||||
}
|
||||
int CTemp::GetSum(CTemp ts)
|
||||
{
|
||||
int tmp = ts.a + ts.b;
|
||||
ts.a = 1000; //此时修改的是tm的一个副本
|
||||
|
||||
return tmp;
|
||||
}
|
||||
//--------------Main函数-----------------
|
||||
void main()
|
||||
{
|
||||
CTemp tm(10,20);
|
||||
printf("Sum = %d \n",tm.GetSum(tm));
|
||||
printf("tm.a = %d \n",tm.a);
|
||||
}
|
||||
|
||||
--------------------------------------------------------
|
||||
Output:
|
||||
Construct function!
|
||||
a = 10
|
||||
b = 20
|
||||
Copy function!
|
||||
Sum = 30
|
||||
tm.a = 10
|
||||
------------------------------------------------------
|
||||
我们看到有调用了拷贝构造函数,这是tm在传给GetSum做参数时:
|
||||
1,调用拷贝构造函数来创建一个副本为GetSum函数体内所用。
|
||||
2,在GetSum函数体内对tm副本进行的修改并没有影响到tm本身。
|
||||
|
||||
解决办法:
|
||||
针对第一种情况的解决办法是传入对象引用(记住:引用只是原对象的一个别名(Alias)),我们将GetSum代码修改如下:
|
||||
|
||||
|
||||
int CTemp::GetSum(CTemp& ts)
|
||||
{
|
||||
int tmp = ts.a + ts.b;
|
||||
ts.a = 1000; //此时通过ts这个引用参考(refer to)对象本身
|
||||
return tmp;
|
||||
}
|
||||
----------------------------------------------------------
|
||||
Output:
|
||||
Construct function!
|
||||
a = 10
|
||||
b = 20
|
||||
Sum = 30
|
||||
tm.a = 1000
|
||||
--------------------------------------------------------
|
||||
可以通过输出看本,通过传递常量引用,减少了一次临时对象的创建。这个改动也许很小,但对多继承的对象来说在构建时要递归调用所有基类的构造函数,这对于性能来说是个很大的消耗,而且这种消耗通常来说是没有必要的。
|
||||
|
||||
二,类型转换生成的临时对象。
|
||||
|
||||
我们在做类型转换时,转换后的对象通常是一个临时对象。编译器为了通过编译会创建一起我们不易察觉的临时对象。再次修改如上main代码:
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
CTemp tm(10,20),sum;
|
||||
sum = 1000; //调用CTemp(int m = 0,int n = 0)构造函数
|
||||
printf("Sum = %d \n",tm.GetSum(sum));
|
||||
}
|
||||
|
||||
-----------------------------------------------------------
|
||||
Output:
|
||||
Construct function!
|
||||
a = 10
|
||||
b = 20
|
||||
Construct function!
|
||||
a = 0
|
||||
b = 0
|
||||
Construct function!
|
||||
a = 1000
|
||||
b = 0
|
||||
Sum = 1000
|
||||
----------------------------------------------------------
|
||||
main函数创建了两个对象,但输出却调用了三次构造函数,这是为什么呢?
|
||||
关键在 sum = 1000;这段代码。本身1000和sum类型不符,但编译器为了通过编译以1000为参调用构造函数创建了一下临时对象。
|
||||
|
||||
解决办法:
|
||||
我们对main函数中的代码稍作修改,将sum申明推迟到“=”号之前:
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
CTemp tm(10,20);
|
||||
CTemp sum = 1000;
|
||||
printf("Sum = %d \n",tm.GetSum(sum));
|
||||
}
|
||||
----------------------------------------------------------
|
||||
Output:
|
||||
Construct function!
|
||||
a = 10
|
||||
b = 20
|
||||
Construct function!
|
||||
a = 1000
|
||||
b = 0
|
||||
Sum = 1000
|
||||
----------------------------------------------------------
|
||||
只作了稍稍改动,就减少了一次临时对象的创建。
|
||||
1,此时的“=”号由原本的赋值变为了构造。
|
||||
2,对Sum的构造推迟了。当我们定义CTmep sum时,在main的栈中为sum对象创建了一个预留的空间。而我们用1000调用构造时,此时的构造是在为sum预留的空间中进行的。因此也减少了一次临时对象的创建。
|
||||
|
||||
三,函数返回一个对象。
|
||||
|
||||
当函数需要返回一个对象,他会在栈中创建一个临时对象,存储函数的返回值。看以下代码:
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
class CTemp
|
||||
{
|
||||
public:
|
||||
int a;
|
||||
public:
|
||||
CTemp(CTemp& t) //Copy Ctor!
|
||||
{
|
||||
printf("Copy Ctor!\n");
|
||||
a = t.a;
|
||||
};
|
||||
CTemp& operator=(CTemp& t) //Assignment Copy Ctor!
|
||||
{
|
||||
printf("Assignment Copy Ctor!\n");
|
||||
a = t.a;
|
||||
return *this;
|
||||
}
|
||||
CTemp(int m = 0);
|
||||
virtual ~CTemp(){};
|
||||
};
|
||||
CTemp::CTemp(int m) //Copy Ctor!
|
||||
{
|
||||
printf("Construct function!\n");
|
||||
a = m;
|
||||
printf("a = %d\n",a);
|
||||
}
|
||||
CTemp Double(CTemp& ts)
|
||||
{
|
||||
CTemp tmp; //构建一个临时对象
|
||||
tmp.a = ts.a*2;
|
||||
return tmp;
|
||||
|
||||
}
|
||||
//-------------Main函数-----------------
|
||||
void main()
|
||||
{
|
||||
CTemp tm(10),sum;
|
||||
printf("\n\n");
|
||||
|
||||
sum = Double(tm);
|
||||
|
||||
|
||||
printf("\n\nsum.a = %d \n",sum.a);
|
||||
}
|
||||
---------------------------------------------------------
|
||||
Output:
|
||||
Construct function!
|
||||
a = 10
|
||||
Construct function!
|
||||
a = 0
|
||||
|
||||
Construct function!
|
||||
a = 0
|
||||
Copy Ctor!
|
||||
Assignment Copy Ctor!
|
||||
|
||||
sum.a = 20
|
||||
--------------------------------------------------------
|
||||
我特地加宽了语句:
|
||||
sum = Double(tm);
|
||||
这条语句竟生成了两个对象,Horrible! 我们现在将这条语句逐步分解一下:
|
||||
1,我们显式创建一个tmp临时对象,
|
||||
语句:CTemp tmp;
|
||||
2,将temp对象返回,返回过程中调用Copy cotr创建一个返回对象,
|
||||
语句:return tmp;
|
||||
|
||||
3,将返回结果通过调用赋值拷贝函数,赋给sum
|
||||
语句: sum = 函数返回值;(该步并没有创建对象,只是给sum赋值)
|
||||
|
||||
tm.Double返回一个用拷贝构造函数生成的临时对象,并用该临时对象给sum赋值.
|
||||
上面的第1步创建对象可以不用创建,我们可以直接对返回值进行操作,有些C++编译器中会有一种优化,叫做(NRV,named return value).不过本人使用的VC++6.0并没有这个启用这个优化。
|
||||
第2步创建的返回对象是难以避免的,你或许想可以返回一个引用,但你别忘记了在函数里创建的局部对象,在返回时就被销毁了。这时若再引用该对象会产生未预期的行为。(C#中解决了这个问题)。
|
||||
|
||||
解决方法:
|
||||
我们将对象直接操作(Manipulate)返回对象,再结合上面的减少临时对象的方法,将函数Double的代码,及main函数中的代码修改如下:
|
||||
|
||||
|
||||
CTemp Double(CTemp& ts)
|
||||
{
|
||||
return ts.a*2;
|
||||
}
|
||||
/*--------上面的代码相当于-------
|
||||
CTemp _ret
|
||||
void Double(CTemp& ts)
|
||||
{
|
||||
_ret.a = ts.a*2;
|
||||
}
|
||||
---------------*/
|
||||
|
||||
|
||||
//---------Main函数-----------
|
||||
void main()
|
||||
{
|
||||
CTemp tm(10);
|
||||
printf("\n\n");
|
||||
|
||||
CTemp sum = Double(tm);
|
||||
|
||||
printf("\n\nsum.a = %d \n",sum.a);
|
||||
}
|
||||
--------------------------------------------------------
|
||||
Output:
|
||||
Construct function!
|
||||
a = 10
|
||||
|
||||
Construct function!
|
||||
a = 20
|
||||
|
||||
sum.a = 20
|
||||
-------------------------------------------------------
|
||||
发现减少了一次构造函数调用(tmp),一次拷贝构造函数(tmp拷贝给返回对象)调用和一次赋值拷贝函数调用.(Assignment Copy Ctor),这是因为:
|
||||
返回对象直接使用为sum预留的空间,所以减少了返回临时对象的生成——返回对象即是sum,返回对象的创建即是sum对象的创建.多么精妙!
|
||||
@@ -0,0 +1,86 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T16:47:10+08:00
|
||||
|
||||
====== 临时对像的思考 ======
|
||||
Created Saturday 06 August 2011
|
||||
http://www.cnblogs.com/welkinwalker/archive/2011/03/10/1979745.html
|
||||
|
||||
由无名对象(临时对象)引发的关于“引用”的思考
|
||||
|
||||
预备知识:
|
||||
|
||||
__无名对象,也叫临时对象__。指的是**直接由构造函数产生**,但是__没有被任何符号所引用的对象__。例如:string("abc"),这句话产生的就是一个无名对象,这个对象产生以后,没有什么办法使用它。但是对于string str("abc")来说,则产生的是一个有名字的对象,他的名字就是 str。
|
||||
__ 任何引用必须初始化。__
|
||||
__ const(对象)变量只能传递给const引用,不能传递给非const引用__。假如说把一个const的对象传递给了非const引用,那么修改这个引用就相当于修改了原来的const对象,这个违反了const约束。这种方式不能通过编译,会报error: passing `const ref' as `this' argument of `void ref::change()' discards qualifiers。
|
||||
|
||||
请观察下面的代码的微妙差别,特别留意注释中的说明
|
||||
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
|
||||
class ref
|
||||
{
|
||||
public:
|
||||
ref(int input);
|
||||
~ref();
|
||||
int i;
|
||||
void change();
|
||||
**void change_const() const;**
|
||||
|
||||
};
|
||||
|
||||
void ref::change_const() const
|
||||
{
|
||||
cout<<"in change_const"<<endl;
|
||||
}
|
||||
void ref::change()
|
||||
{
|
||||
i=3;
|
||||
cout<<"in change"<<endl;
|
||||
}
|
||||
ref::ref(int input)
|
||||
{
|
||||
i=input;
|
||||
}
|
||||
ref::~ref()
|
||||
{
|
||||
cout<<"tear down"<<endl;
|
||||
}
|
||||
|
||||
|
||||
void test_const(const string & str)
|
||||
{
|
||||
cout<<str<<endl;
|
||||
}
|
||||
|
||||
void test(string & str)
|
||||
{
|
||||
cout<<str<<endl;
|
||||
}
|
||||
|
||||
main()
|
||||
{
|
||||
// int &i=1; //__不能用非对象去初始化一个引用, 其实就是不能用一个常量来初始化一个引用。__
|
||||
注意: 其实上面的更深层原因是,编译器调用 int(1)生成一个__临时对像__,而临时对像只能用const同类型的引用。
|
||||
|
||||
const int &j=1;//这样可以,但是没什么实际意义
|
||||
|
||||
string str("haha"); // 这里创建的str是一个__有名的局部对像。__
|
||||
test(str);
|
||||
test_const(str);
|
||||
|
||||
// test(string("haha"));
|
||||
//报invalid initialization of non-const reference of type 'std::string&' from a temporary of type 'std::string'。使用__临时对象不能初始化test的string & 引用__。
|
||||
test_const(string("haha"));//__使用临时对象初始化函数形参的时候,函数形参必须是有const限定。__
|
||||
|
||||
|
||||
ref const obj(1);
|
||||
cout<<obj.i<<endl;
|
||||
obj.change_const();
|
||||
cout<<obj.i<<endl;
|
||||
|
||||
ref(2).change();__//无名对象调用非const函数,这说明无名对象(临时对象)并不能等同于用const修饰的有名对象,用const修饰的有名对象,是不允许调用非const方法的(不管该方法是否真的修改了对像的状态),因为那样会修改对象的成员。这可以从下面的例子看到。__
|
||||
__// obj.change();//const对象调用非const函数,报passing `const ref' as `this' argument of `void ref::change()' discards qualifiers(限定符)__
|
||||
//上面的意思是,obj对像是一个const ref类型的实例,但是调用的方法change()是非const型的(即丢弃了const限定符)。
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-10-28T17:08:05+08:00
|
||||
|
||||
====== 临时对象的生存期 ======
|
||||
Created Friday 28 October 2011
|
||||
|
||||
http://tech.e800.com.cn/articles/2009/1010/1255136951349_1.html
|
||||
|
||||
* 今天看了c++发明人的the c++ programming language(special edition)关于临时对象的说明,虽然是一小节,但感觉c++的书籍,论内容还是这本最权威,里面很多东西都直接确定的表达了出来,权威、确定而令人信服。
|
||||
|
||||
临时对象的生存时限在这本书中只花了不长的篇章,但说的很清楚:
|
||||
__临时对象的生存时限限制在其出现的“完整”的表达式中,“完整”的表达式结束了,临时对象也就销毁了。例外是把临时对象被引用或者初始化给具名对象,临时对象的生存周期会加长到引用或者具名对象的生存周期。__
|
||||
|
||||
底下的简短程序测试了临时对象的生存时限,vs2005很好的说明了临时对象在其“完整”表达式结束后销毁。
|
||||
*/
|
||||
|
||||
#include
|
||||
#include
|
||||
using namespace std;
|
||||
|
||||
/* 打印 字符串 */
|
||||
void print(const string& str)
|
||||
{
|
||||
cout << str.c_str() << endl;
|
||||
}
|
||||
|
||||
/* 打印 字符串2 */
|
||||
void display(string str)
|
||||
{
|
||||
cout << "xxxxxxxx" << endl;
|
||||
cout << str.c_str() << endl;
|
||||
cout << "xxxxxxxx" << endl;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
string str1 = "xiaocui";
|
||||
string str2 = ",nihao";
|
||||
const char* s = NULL;
|
||||
|
||||
if ( s = (str1 + str2).c_str() ) //此处s1+s2产生一个临时string对象,它的生存周期是临时对象所出现的“完整”的表达式的生存周期
|
||||
{
|
||||
cout << s << endl; //上面的临时对象已经在其“完整”的表达式结束后析构了,那么s指向的那块内存已经不存在了,此处引用s是未定义的,危险的
|
||||
}
|
||||
|
||||
string ss = str1 + str2; //str1+str2同样产生临时对象,但其用来初始化具名对象,所以临时对象的生存周期延长到具名对象的周期
|
||||
cout << ss.c_str() << endl; //正确输出,说明临时对象仍然存在,并没有销毁,存放字符串的存储正常存在
|
||||
|
||||
print(str1 + str2); //把str1+str2产生的临时对象传递给cosnt 引用,临时对象的生命周期延长到const引用的生命周期,所以正常打印
|
||||
display(str1 + str2); //函数传递具有初始化语义,所以str1+str2产生的临时对象初始化str,生命周期延长,正常打印
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
如果不深究,下面这句话可作为总体印象区别:
|
||||
__临时对象只在当前行有效__
|
||||
__局部 对象到函数结束都有效__
|
||||
@@ -0,0 +1,35 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T14:52:53+08:00
|
||||
|
||||
====== 关于基类、派生类、对象、指针和虚函数、多态、 静态绑定、 动态绑定 纯虚函数、抽象类 ======
|
||||
Created Saturday 06 August 2011
|
||||
|
||||
以下观点来源于《深入浅出MFC》
|
||||
|
||||
到底调用到哪个函数,必须视指针的原始类型而定,于指针实际所指对象无关。
|
||||
|
||||
1.如果你以一个“基类之指针”指向“派生类”,那么经由该指针你只能够调用基类所定义的函数。
|
||||
|
||||
2.如果你以一个“派生类之指针”指向一个“基类之对象”,你必须先做明显的转型操作(explicit cast)/(显示强制类型转换)。这种做法很危险,不符合真实生活经验,在程序设计上也会带给程序员困惑。
|
||||
|
||||
3.如果基类和派生类都定义了“相同名称之成员函数”,那么通过对象指针调用成员函数时,到底调用到哪一个函数,必须视该指针的原始类型而定,而不是视指针实际所指的对象的类型而定。这与第1点其实意义想通。
|
||||
|
||||
虚函数正是为了对“如果你以一个基类之指针指向一个派生类之对象,那么通过该指针你就只能调用基类所定义之成员函数”这条规则反其道而行的设计。也有点反继承的味道呵呵 。多态。
|
||||
|
||||
从操作类型定义来看,什么是虚函数呢?__如果你预期派生类有可能重新定义某一个成员函数,那么你就在基类中把此函数设为virtual。__MFC有两个十分十分重要的虚函数:与document有关的serialize函数和view有关的ondraw函数。你应该在自己的CMyDoc和CMyView中改写这两个虚函数。
|
||||
|
||||
多态(Polymorphism)
|
||||
|
||||
以相同的指令指针调用了不同的函数,这种性质称为Polymorphism,意思是“the ability to assume many forms”(多态)。__编译器无法再编译时期判断基类指针到底调用哪一个派生类函数__,必须在执行期才能判断之,这称为__后期绑定late binding或动态绑定dynamic binding__。至于C函数或C++的non-virtual函数,在编译时期就转换为一个固定地址的调用了,这称为前期绑定early binding或静态绑定static binding。
|
||||
|
||||
纯虚函数
|
||||
|
||||
纯虚函数其函数值“=0 ”。__纯虚函数不需要定义其实际操作__,它的的存在__只是为了在派生类中被重新定义__,只是为了提供一个多态接口。只要是拥有纯虚函数的类,就是一个__抽象类__,它是不能够被实例化(instantiate)的,也就是说,你不能根据它产生一个对象。
|
||||
|
||||
关于抽象类。
|
||||
|
||||
如果抽象类被继承了,在派生类中没有改写基类中的纯虚函数,那么派生类依然是个抽象类,直到__纯虚函数被实际操作为止__。
|
||||
|
||||
虚函数派生下去仍为虚函数,而且__可以省略virtual关键词__。
|
||||
|
||||
59
Zim/Programme/C++/函数参数的传递与返回.txt
Normal file
@@ -0,0 +1,59 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T09:50:59+08:00
|
||||
|
||||
====== 函数参数的传递与返回 ======
|
||||
Created Saturday 06 August 2011
|
||||
|
||||
一、通过值传递:函数变量A中的值,传递(复制)到了被调函数中的变量a中,两者的内存地址不同。
|
||||
应用范围:当函数不必改变请求调用的程序中的原变量时,以值传递参数。实际上它保证了函数无法破坏原变量。
|
||||
注意:如果传递的值是一个对像,则在传递的过程中会调用该对像的复制构造函数:
|
||||
list student_list = uestc_list ;
|
||||
|
||||
二、通过引用传递 &:函数变量A与被调函数中引用自A的变量a,是内存中的同一地址的不同名称。
|
||||
|
||||
实质:通过引用传递参数,是对请求调用的程序中 原变量的引用 被传递给调用函数:实际上,是变量的内存地址被传递了;
|
||||
好处: 1.被调用的函数可以访问请求调用的程序中的实际变量。
|
||||
2.__提供了从被调函数向调用他的程序返回不止一个值得机制。__
|
||||
|
||||
三、通过引用返回:通过引用&,引用被调函数的返回值到调用函数中。
|
||||
|
||||
原因:1.避免虚函数中要见到的复制巨大的对象。
|
||||
2__.允许在等号的左边使用调用函数。__
|
||||
|
||||
例:
|
||||
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
int x; //全局变量
|
||||
int& setx(); //函数声明
|
||||
|
||||
int main()
|
||||
{
|
||||
__ setx()=92; __ //函数调用,等号左边通过引用返回
|
||||
cout<<"x="<<x<<endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int& setx() //函数定义
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
程序中的函数setx()声明为应用类型int&,它和返回类型是一样的。
|
||||
|
||||
setx()=92; //__表示函数返回的变量(这里为x)被赋值为等号右方的值__。即结果为:x=92
|
||||
|
||||
注意:1.y=setx();表示把函数当做值来处理
|
||||
2.setx()=92;表示通过引用返回的函数被当做变量来处理
|
||||
3.__不能从引用返回的函数返回一个常量__。 上面程序如果是return 4; 那么程序错误。
|
||||
4.不能将引用返回给__局部变量__。 如:
|
||||
int& setx() //函数定义
|
||||
{
|
||||
|
||||
int x=3;
|
||||
return x;
|
||||
}
|
||||
|
||||
此时也错误,因为当函数返回时,函数的局部变量可能被销毁,返回引用一个不存在的变量是没有意义的。
|
||||
|
||||
81
Zim/Programme/C++/四种类型转换运算符.txt
Normal file
@@ -0,0 +1,81 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-17T15:05:06+08:00
|
||||
|
||||
====== 四种类型转换运算符 ======
|
||||
Created Friday 17 February 2012
|
||||
|
||||
http://tianshan.blog.51cto.com/1019606/228984
|
||||
|
||||
C++的四个类型转换运算符用法和区别归纳如下
|
||||
|
||||
===== reinterpret_cast =====
|
||||
该函数**将一个类型的指针转换为另一个类型的指针**. 转换过程中不做任何类型检查和转换,不改变指针变量以及指向区域的内容,__只是转换对二进制的解释__!
|
||||
只需在编译时**重新解释指针的类型**就可做到. reinterpret_cast 只能对指针变量进行重新解释,不能对非指针类型变量重新解释(也就是说操作的只能是指针类型)
|
||||
例:
|
||||
//基本类型指针的类型转换
|
||||
double d=9.2;
|
||||
double *pd = &d;
|
||||
int *pi = reinterpret_cast<int *>(pd); //相当于int *pi = (int *)pd;
|
||||
|
||||
//不相关的类的指针的类型转换
|
||||
class A{};
|
||||
class B{};
|
||||
A* pa = new A;
|
||||
B* pb = reinterpret_cast<B*>(pa); //相当于B* pb = (B*)pa;
|
||||
|
||||
//指针转换为整数
|
||||
long l = reinterpret_cast<long>(pi); //相当于long l = (long)pi;
|
||||
|
||||
===== const_cast =====
|
||||
该函数用于__改变指针变量的常量属性__,将它转换为一个对应指针类型的普通变量。反过来,也可以将一个非常量的指针变量转换为一个常指针变量。
|
||||
这种转换是在编译期间做出的类型更改。
|
||||
例:
|
||||
|
||||
const int* pci = 0;
|
||||
int* pk = const_cast<int *>(pci); //相当于int* pk = (int*) pci;
|
||||
|
||||
const A * pca = new A;
|
||||
A* pa = const_cast<A *>(pca); //相当于A* pa = (A*)pca;
|
||||
|
||||
出于安全性考虑,const_cast无法将非指针的常量转换为**普通变量(也就是说操作和返回的类型都是指针)**。
|
||||
|
||||
===== static_cast =====
|
||||
该函数主要用于**基本类型之间和具有继承关系的类型之间**的转换(__操作和返回的都是对象__)。__类似于C风格的类型转换__,用于非多态转换。这种转换返回的值一般会__更改变量的内部表示方式__,因此,static_cast应用于**指针类型(所有的指针都是同意中类型)**转换没有太大意义。
|
||||
例:
|
||||
//基本类型转换
|
||||
int i=0;
|
||||
double d = static_cast<double>(i); //相当于 double d = (double)i;
|
||||
|
||||
//转换继承类的对象为基类对象
|
||||
class Base{};
|
||||
class Derived : public Base{};
|
||||
Derived d;
|
||||
Base b = static_cast<Base>(d); //相当于 Base b = (Base)d;
|
||||
|
||||
|
||||
===== dynamic_cast =====
|
||||
|
||||
它与static_cast相对,是动态转换。这种转换是在__运行时__进行转换分析的,并非在编译时进行,明显区别于上面三个类型转换操作。在多态下用于父子类之间的__向下安全转换__,该函数只能__在继承类对象的指针之间或引用之间进行类型转换__。进行转换时,会根据当前运行时类型信息,判断类型对象之间的转换是否合法。dynamic_cast的指针转换失败,可通过是否为null检测,**引用转换**失败则抛出一个bad_cast异常。
|
||||
例:
|
||||
class Base{};
|
||||
class Derived : public Base{};
|
||||
|
||||
//派生类指针转换为基类指针(合法)
|
||||
Derived *pd = new Derived;
|
||||
Base *pb = dynamic_cast<Base*>(pd);
|
||||
|
||||
if (!pb)
|
||||
cout << "类型转换失败" << endl;
|
||||
|
||||
//基类指针转换为派生类指针(__非法__)
|
||||
Base *pb = new Base ;
|
||||
Derived * pd = dynamic_cast<Derived * > (pb);
|
||||
|
||||
//没有继承关系,但被转换类有虚函数
|
||||
class A { virtual ~A(); } //有虚函数
|
||||
class B{}:
|
||||
A* pa = new A;
|
||||
B* pb = dynamic_cast<B *>(**pa**);
|
||||
|
||||
如果对无继承关系或者没有虚函数的对象指针进行转换、基本类型指针转换以及**基类指针转换为派生类指针**,都不能通过编译。
|
||||
73
Zim/Programme/C++/四种类型转换运算符/C++的四种cast操作符的区别.txt
Normal file
@@ -0,0 +1,73 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-17T15:43:20+08:00
|
||||
|
||||
====== C++的四种cast操作符的区别 ======
|
||||
Created Friday 17 February 2012
|
||||
http://welfare.cnblogs.com/articles/336091.html
|
||||
|
||||
C++的4种类型转换
|
||||
|
||||
一、C 风格(C-style)强制转型如下:
|
||||
|
||||
(T) expression // cast expression to be of type T
|
||||
函数风格(Function-style)强制转型使用这样的语法:
|
||||
T(expression) // cast expression to be of type T
|
||||
这两种形式之间没有本质上的不同,它纯粹就是一个把括号放在哪的问题。我把这两种形式称为__旧风格(old-style)的强制转型__。
|
||||
|
||||
二、 C++的四种强制转型形式:
|
||||
|
||||
C++ 同时提供了四种新的强制转型形式(通常称为新风格的或 C++ 风格的强制转型):
|
||||
const_cast <new_type>(expression)
|
||||
dynamic_cast<new_type>(expression)
|
||||
reinterpret_cast<new_type>(expression)
|
||||
static_cast<new_type>(expression)
|
||||
|
||||
每一种适用于特定的目的:
|
||||
|
||||
·dynamic_cast 主要用于执行“__安全的向下转型__(safe downcasting)”,也就是说,要确定一个对象是否是一个继承体系中的一个特定类型。它是唯一不能用旧风格语法执行的强制转型,也是唯一可能有**重大运行时代价**的强制转型。
|
||||
|
||||
·static_cast 可以被用于强制隐型转换(例如,non-const 对象转型为 const 对象,int 转型为 double,等等),它还可以用于很多这样的转换的反向转换(例如,void* 指针转型为有类型指针,基类指针转型为派生类指针),但是它**不能将一个 const 对象转型为 non-const 对象**(只有 const_cast 能做到),__它最接近于C-style的转换__。
|
||||
|
||||
·const_cast 一般用于强制消除对象的常量性。它是唯一能做到这一点的 C++ 风格的强制转型。
|
||||
|
||||
·reinterpret_cast 是特意用于底层的强制转型,导致实现依赖(implementation-dependent)(就是说,不可移植)的结果,例如,将一个指针转型为一个整数。这样的强制转型在底层代码以外应该极为罕见。
|
||||
|
||||
旧风格的强制转型依然合法,但是__新的形式更可取__。首先,在代码中它们更容易识别(无论是人还是像 grep 这样的工具都是如此),这样就简化了在代码中寻找类型系统被破坏的地方的过程。第二,更精确地指定每一个强制转型的目的,使得编译器诊断使用错误成为可能。例如,如果你试图使用一个 const_cast 以外的新风格强制转型来消除常量性,你的代码将无法编译。
|
||||
|
||||
==
|
||||
== dynamic_cast .vs. static_cast
|
||||
==
|
||||
|
||||
class B { ... };
|
||||
class D : public B { ... };
|
||||
|
||||
void f(B* pb)
|
||||
{
|
||||
D* pd1 = dynamic_cast<D *>(pb);
|
||||
D* pd2 = static_cast<D*>(pb);
|
||||
}
|
||||
|
||||
If pb** really points to an object of type D**, then pd1 and pd2 will get the same value. They will also get the same value if pb == 0.
|
||||
|
||||
If pb points to an object of type B and not to the complete D class, then dynamic_cast will know enough to __return zero__. However, static_cast relies on the programmer’s assertion that pb points to an object of type D and simply returns a pointer to that supposed D object.
|
||||
|
||||
即dynamic_cast可__用于继承体系中的向下转型,即将基类指针转换为派生类指针__,比static_cast更严格更安全。dynamic_cast在执行效率上比static_cast要差一些,但static_cast在更宽上范围内可以完成映射,这种**不加限制的映射伴随着不安全性**. static_cast覆盖的变换类型除类层次的静态导航以外,还包括无映射变换,窄化变换(这种变换会导致对象切片,丢失信息),用void*的强制变换,隐式类型变换等...
|
||||
|
||||
|
||||
==
|
||||
== static_cast .vs. reinterpret_cast
|
||||
==
|
||||
|
||||
reinterpret_cast是为了__映射到一个完全不同类型__的意思,这个关键词在我们需要把类型映射回原有类型时用到它.我们映射到的类型仅仅是为了故弄玄虚和其他目的,这是所有映射中__最危险__的.(这句话是C++编程思想中的原话)
|
||||
|
||||
static_cast 和 reinterpret_cast 操作符修改了操作数类型. 它们__不是互逆的__; static_cast 在编译时使用类型信息执行转换, **在转换执行必要的检测**(诸如指针越界计算, 类型检查). 其操作数**相对是安全的**. 另一方面, reinterpret_cast 仅仅是重新解释了给出的对象的比特模型而没有进行二进制转换, 例子如下:
|
||||
|
||||
int n=9; double d=static_cast < double > (n);
|
||||
|
||||
上面的例子中, 我们将一个变量从 int 转换到 double. 这些类型的二进制表达式是不同的. 要将整数 9 转换到 双精度整数 9, static_cast 需要正确地为双精度整数 d 补足比特位. 其结果为 9.0. 而reinterpret_cast 的行为却不同:
|
||||
|
||||
int n=9;
|
||||
double d=reinterpret_cast<double & > (n);
|
||||
|
||||
这次, 结果有所不同. 在进行计算以后, __d 包含无用值__. 这是因为 reinterpret_cast 仅仅是**复制 n 的比特位到 d**, **没有进行必要的分析**.
|
||||
117
Zim/Programme/C++/复制构造函数的用法及指针悬挂问题的产生和解决(上)——提出问题.txt
Normal file
@@ -0,0 +1,117 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T21:47:27+08:00
|
||||
|
||||
====== 复制构造函数的用法及指针悬挂问题的产生和解决(上)——提出问题 ======
|
||||
Created Saturday 06 August 2011
|
||||
=========================何为复制构造函数的“复制”=========================
|
||||
|
||||
复制构造函数利用下面这行语句来复制一个对象:
|
||||
A (A &a)
|
||||
|
||||
从上面这句话可以看出,__所有的复制构造函数均只有一个参数,及对同一个类的对象的引用__。这句话的意思其实就是,如果我们在程序中写上下面这样一句话:
|
||||
A b=a
|
||||
或者是:
|
||||
A b(a)
|
||||
|
||||
就表示b其实是对象a的一个拷贝。
|
||||
|
||||
=========================复制构造函数的“运行机制”=========================
|
||||
|
||||
比如说我们有一个类A,定义如下:
|
||||
01 class A
|
||||
02 {
|
||||
03 public:
|
||||
04 A(int i,int j){n=i;m=j;} //构造函数带两个参数,并将参数的值分别赋给两个私有成员
|
||||
05 A(A &t); //我们自己定义一个默认构造函数
|
||||
06 void print(){cout<<n<<m;}//输出两个成员的值
|
||||
07 private:
|
||||
08 int n; //数据成员n
|
||||
09 int m; //数据成员m
|
||||
10 };
|
||||
|
||||
在上面这个类的定义中我们定义了一个默认的构造函数(虽然默认构造函数一般是通过编译器自动定义的,但是这里我们模拟一下它的工作过程)。默认构造函数的工作方法应该如下面所示:
|
||||
1 A(A &t)
|
||||
2 {
|
||||
3 n=t.n;m=t.m;
|
||||
4 }
|
||||
|
||||
在这里我们模拟了一个默认复制构造函数是如何运行的。它通过别名t访问一个对象,并将该对象的成员赋给新对象的成员。这样就完成了复制工作。这样一来,我们如果再程序中定义了:
|
||||
A a(2,4);
|
||||
这个对象。这就表示,利用构造函数,对象a的数据成员a.n=2;a.m=4,如果我们利用下面这句话调用一个默认的复制构造函数:
|
||||
__A b(a);__
|
||||
**那么它就会调用复制构造函数**,即上面我们模拟的复制构造函数中所定义的“n=t.n;m=t.m”。这时对象b的数据成员b.n=2;b.m=4,这与对象a中的数据成员的值应该是一模一样的。
|
||||
|
||||
===好了,复制构造函数介绍完了,下面我们来看看为什么利用复制构造函数有时候会产生__迷途指针__的问题~~~===
|
||||
|
||||
==== 迷途指针产生的原因:“浅层复制构造函数” ====
|
||||
|
||||
一般地,__编译器提供的默认复制构造函数的功能只是把传递进来的对象的每一个成员变量的值复制给了新对象的成员变量__。但是,如果这个老对象是一个指针,那么新对象也是一个指针,且它指向的内存地址和老对象是一样的!这样就会产生2个显而易见的问题:
|
||||
|
||||
我们可以随意地对一个指针所指向的内存空间进行赋值操作。那么另外一个指针所指向的内存空间由于和前面那个指针式一模一样的,那么它就不可避免地被修改;
|
||||
如果我们在程序中无意将老对象所指向的内存地址释放掉了,那么新对象的指针自然就变成了一个__迷途指针__。举一个形象的例子来说就是:(这个例子来自我上一篇博文《删除一个指针之后可不要就跟着悲剧了啊……》后面 mrfangzheng的留言):如果B对象持用一个指针指向对象A, 现有一个B对象的拷贝C,那么它也持有一个指针p2指向A. 若某时, B释放了对象A, 但C是无法知晓的, C认为它持有的指针指向的A有效, 之后若C再调用A的方法就报错。
|
||||
|
||||
我们用一个程序来演示上面这两种错误:
|
||||
01 #include <iostream>
|
||||
02 using namespace std;
|
||||
03 class A
|
||||
04 {
|
||||
05 public:
|
||||
06 A(){x=new int;*x=5;} //创建一个对象的同时将成员指针指向的变量保存到 新空间中
|
||||
07 ~A(){delete x;x = NULL;}//析构对象的同时删除成员指针指向的内存空间并将指针赋为空
|
||||
08
|
||||
09 A(A &a)
|
||||
10 {
|
||||
11 cout << "复制构造函数执行...\n" <<endl;
|
||||
12
|
||||
13 x = a.x; //将旧对象的成员指针x指向的空间处的数据赋给新对象的成员指针x
|
||||
14 }
|
||||
15 void print(){cout<<*x<<endl;}
|
||||
16 void set(int i){*x=i;}
|
||||
17 private:
|
||||
18 int *x;
|
||||
19 };
|
||||
20 int main()
|
||||
21 {
|
||||
22 A *a = new A(); //利用指针在堆中创建一个对象
|
||||
23 cout<<"a:";
|
||||
24 a->print();
|
||||
25 cout<<endl;
|
||||
26 A b=(*a); //这里初始化的对象为指针a所指向的堆中的对象
|
||||
27
|
||||
28 //调用复制构造函数之后,将对象b变成对象a的一个拷贝,那么对象a和对象b所输出的值应该是一样的
|
||||
29
|
||||
30 cout<<"b:";
|
||||
31 b.print();
|
||||
32 cout<<endl;
|
||||
33
|
||||
34 //利用对象a中的成员函数set()将指针x指向的内存区域中的值改成32
|
||||
35 a->set(32);
|
||||
36 cout<<"a:";
|
||||
37 a->print();
|
||||
38
|
||||
39 cout<<"b:";
|
||||
40 b.print();
|
||||
41 cout<<endl;
|
||||
42
|
||||
43 //利用对象b中的成员函数set()将指针x指向的内存区域中的值改成99
|
||||
44 b.set(99);
|
||||
45 cout<<"a:";
|
||||
46 a->print();
|
||||
47
|
||||
48 cout<<"b:";
|
||||
49 b.print();
|
||||
50 cout<<endl;
|
||||
51
|
||||
52 delete a;
|
||||
53 return 0;
|
||||
54 }
|
||||
|
||||
来看一看这个程序的输出:
|
||||
|
||||
|
||||
上面红色框就代表了用a.set(32)来改变a中指针x所指向的内存空间的值,可以看出b的指针x所指向的内存空间的值也跟着变成了32。绿色框代表了用b.set(99)来改变b中指针x所指向的内存空间的值,可以看出a的指针x所指向的内存空间的值也跟着变成了99。而在程序崩溃对话框(白底黑字那个)中指明了程序的第52行引发了一个错误,大意就是那个__内存区域是非法的__(如上图中蓝色框所示)。出现这个错误的原因其实就是由迷途指针造成的:当main函数结束(右大括号)并析构对象b的时候,由于指针成员x所指向的内存区域已经在第52行被释放了,这样一来,对象b中的指针x就变成了迷途指针了,我们再次释放这块内存空间的时候肯定就会导致程序的崩溃。
|
||||
|
||||
未完待续......
|
||||
|
||||
(那我们该怎么解决这个问题呢,请看下集~~~)
|
||||
@@ -0,0 +1,25 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T21:52:16+08:00
|
||||
|
||||
====== 赋值构造函数的用法即指针悬挂问题的产生和解决(中)——解决之道 ======
|
||||
Created Saturday 06 August 2011
|
||||
|
||||
上篇文章末尾谈到了指针悬挂的问题,这主要是由于浅层复制构造函数的原因。为了解决这个指针悬挂的问题,这时候我们就需要引进一个新的概念:__深层复制构造函数__。
|
||||
|
||||
下面,我们来介绍一下浅层复制构造函数与深层复制构造函数之间的区别与联系......
|
||||
|
||||
浅层复制构造函数:浅层构造赋值函数主要是将传递进来的对象的**成员变量的所有值赋值给新对象的成员变量**。这主要是由于下面这个语句造成的(博文《复制构造函数的用法及指针悬挂问题的产生和解决(上)》中的程序的第13行):
|
||||
x=a.x;//把对象a中的指针成员变量x的值复制给了对象b中的指针成员变量x
|
||||
|
||||
上面这个语句就会产生一个问题,即对象b的**指针成员变量**b.x和对象a的指针成员变量a.x所保存的值是同一块内存空间的地址。如果我们析构了对象a,那么编译器会自动释放该内存地址,而b并不知道,这样就产生了指针悬挂的问题。这就是由于浅层复制构造函数的运作机理产生的,它只是将旧对象的数据复制给新对象的数据,而如果是指针对象,它复制的就是指针所保存的地址。上面这句话是不是有点绕啊,我们用下图来解释一下:
|
||||
{{./1.png}}
|
||||
从上面这个图就可以非常清楚地看到,当我 们析构掉对象a的时候,编译器会自动释放堆中所创建的内存空间。而对于对象b而言,它压根就不知道有编译器释放堆中内存这么一回事,所以自然b.x就变成迷途指针了。
|
||||
|
||||
__深层复制构造函数__:浅层构造赋值函数主要功能虽然也是是将传递进来的对象的成员变量的所有值赋值给新对象的成员变量,但是有一点不同之处在于,先看程序:
|
||||
1 x=new int;//创建一块新空间
|
||||
2 *x=*(a.x);//把对象a中指针成员x所指向的值赋值给了利用深层赋值构造函数所创建的对象b的指针成员x
|
||||
|
||||
由上面的程序可以看出,其实__深层复制构造函数中我们只添加了为成员指针指向的数据成员分配内存,同时在赋值的时候,我们是把旧对象中指针成员所指向的值复制给了新对象的指针成员,而不是地址,这样就可以避免指针悬挂的问题了__。你可能会问,上面这么长一串话是啥意思哦?不解释,我们直接上图!
|
||||
{{./2.jpg}}
|
||||
从上面这个图,我们就显而易见地看出深层复制构造函数的好处了!注意看如果我们析构了对象a,那么编译器只是会回收内存地址为A处得内存,而不会管内存地址为D处的值。这样就避免了利用浅层复制函数所产生的对象b的指针悬挂问题了。
|
||||
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 104 KiB |
7
Zim/Programme/C++/对象及内存管理.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-17T14:38:14+08:00
|
||||
|
||||
====== 对象及内存管理 ======
|
||||
Created Friday 17 February 2012
|
||||
|
||||
578
Zim/Programme/C++/对象及内存管理/Overloading_operator_new.txt
Normal file
@@ -0,0 +1,578 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-17T13:09:40+08:00
|
||||
|
||||
====== Overloading operator new ======
|
||||
Created Friday 17 February 2012
|
||||
|
||||
http://www.relisoft.com/book/tech/9new.html
|
||||
|
||||
Both** new and delete** are considered __operators__ in C++. What it means, in particular, is that they can be overloaded like any other operator. And just like you can define **a class-specific operator=**, you can also define class-specific operators new and delete. They will be __automatically called by the compiler__ to allocate and deallocate objects of that particular class. Moreover, you can **overload and override** **global versions** of new and delete.
|
||||
|
||||
===== Class-specific new =====
|
||||
**Dynamic memory **allocation and deallocation are __not cheap__. A lot of programs spend the bulk of their time inside the** heap,** searching for free blocks, recycling deleted blocks and merging them to prevent heap fragmentation. If memory management is a performance bottleneck in your program, there are several optimization techniques that you might use.
|
||||
|
||||
Overloading new and delete on a per-class basis is usually used to speed up allocation/deallocation of objects of that particular class. There are two main techniques--__caching and bulk allocation__.
|
||||
|
||||
==== Caching ====
|
||||
The idea behind caching is that __recycling is cheaper than manufacturing__. Suppose that we wanted to speed up additions to a hash table. Every time an addition is performed, a new link is allocated. In our program, these links are only deallocated when the **whole **hash table is destroyed, which happens at the end of the program. Imagine, however, that we are using our hash table in another program, where it's either possible to selectively remove items from the hash table, or where there are many hash tables created and destroyed during the lifetime of the program. In both cases, we might speed up average link allocation time by __keeping around the links that are currently not in use__.
|
||||
|
||||
A //FreeList //object will be used as storage for// unused links//. To get a new link we call its //NewLink// method. To return a link back to the __pool__, we call its //Recycle// method. The pool of links is implemented as a linked list. There is also a Purge method that frees the **whole **pool.
|
||||
|
||||
class Link; //前向声明,解决交叉依赖。
|
||||
class FreeList
|
||||
{
|
||||
public:
|
||||
FreeList () : _p (0) {}
|
||||
~FreeList ();
|
||||
void Purge ();
|
||||
void * NewLink ();
|
||||
void Recycle (__void *__ link);
|
||||
private:
|
||||
__Link * ___p;
|
||||
};
|
||||
|
||||
Class //Link //has a static member //_freeList// which is used by the__ overloaded class-specific operators new and delete__. Notice the assertion in operator new. It protects us from somebody calling this particular operator for a different class. How could that happen? Operators new and delete are__ inherited__. If a class derived from Link didn't **override** these operators, new called for the derived class would return an object of the wrong size (base-class size).
|
||||
|
||||
class Link //__链表节点__,需要重载其new和delete操作符。
|
||||
{
|
||||
friend class FreeList;
|
||||
public:
|
||||
Link (Link * pNext, int id)
|
||||
: _pNext (pNext), _id (id) {}
|
||||
|
||||
Link * Next () const { return _pNext; }
|
||||
int Id () const { return _id; }
|
||||
|
||||
// allocator
|
||||
__void * operator new__ (size_t size)
|
||||
{
|
||||
assert (size ==__ sizeof __(Link));
|
||||
return _freeList.NewLink ();
|
||||
}
|
||||
__void operator delete __(void * mem)
|
||||
{
|
||||
if (mem)
|
||||
_freeList.Recycle (mem);
|
||||
}
|
||||
static void Purge () { _freeList.Purge (); }
|
||||
private:
|
||||
__ static FreeList__ _freeList;
|
||||
|
||||
Link * _pNext;
|
||||
** int _id;**
|
||||
};
|
||||
|
||||
Inside List::Add the creation of a new Link will be translated by the compiler into the call to the **class-specific operator new** followed by the call to its constructor (if any). The beauty of this method is that no changes to the implementation of List are needed.
|
||||
|
||||
__class List__
|
||||
{
|
||||
public:
|
||||
List ();
|
||||
~List ()
|
||||
{
|
||||
while (_pHead != 0)
|
||||
{
|
||||
Link * pLink = _pHead;
|
||||
_pHead = _pHead->Next();
|
||||
delete pLink;
|
||||
}
|
||||
}
|
||||
void Add (int id)
|
||||
{
|
||||
Link * pLink =__ new Link__ (_pHead, id);
|
||||
_pHead = pLink;
|
||||
}
|
||||
Link const * GetHead () const { return _pHead; }
|
||||
private:
|
||||
__ Link * _pHead;__
|
||||
};
|
||||
|
||||
__A hash table contains an array of Lists __which will all internally use the special-purpose allocator for its links.
|
||||
|
||||
After we are done with the hash table, we might want to purge the memory stored in the private allocator. That would make sense if, for instance, there was only one hash table in our program, but it allowed deletion as well as addition of entries. On the other hand, if we __wanted our pool of links to be shared between multiple hash tables__, we wouldn't want to purge it every time a hash table is destroyed.
|
||||
|
||||
__class HTable__
|
||||
{
|
||||
public:
|
||||
explicit HTable (int size): _size(size)
|
||||
{
|
||||
_aList = new List [size];
|
||||
}
|
||||
|
||||
~HTable ()
|
||||
{
|
||||
delete [ ] _aList;
|
||||
// release memory in free list
|
||||
__Link::Purge (); __// optional
|
||||
}
|
||||
|
||||
// ...
|
||||
private:
|
||||
List * _aList;
|
||||
int _size;
|
||||
};
|
||||
|
||||
Notice: **Purge is a static method of Link**, so we don't need an instance of a Link in order to call it.
|
||||
|
||||
In the implementation file, we first have to define the static member **_freeList **of the class Link. Static data is automatically initialized to zero.
|
||||
|
||||
FreeList Link::_freeList;
|
||||
|
||||
The implementation of FreeList is pretty straightforward. We try to __reuse Links__, if possible; otherwise we call the __global operator new__. Since we are allocating** raw memory**, we ask for sizeof (Link) bytes (chars).
|
||||
When we delete this storage, we cast Links back to their raw form. Deleting a Link as a Link would result in a (second!) call to its destructor. We don't want to do it here, since destructors for these Links have already been called when the class-specific delete was called.
|
||||
|
||||
__void *__ FreeList::NewLink ()
|
||||
{
|
||||
if (_p != 0)
|
||||
{
|
||||
void * mem = _p ;
|
||||
_p = _p->_pNext;
|
||||
return mem;
|
||||
}
|
||||
else
|
||||
{
|
||||
//__ use global operator new__
|
||||
return __::new __char [sizeof (Link)];
|
||||
}
|
||||
}
|
||||
|
||||
void FreeList::Recycle (void * mem)
|
||||
{
|
||||
Link * link = static_cast<__Link *__> (mem);
|
||||
link->_pNext = _p;
|
||||
_p = link;
|
||||
}
|
||||
|
||||
FreeList::~FreeList ()
|
||||
{
|
||||
Purge ();
|
||||
}
|
||||
|
||||
void FreeList::Purge ()2
|
||||
{
|
||||
while (_p != 0)
|
||||
{
|
||||
// it was allocated as an array of char
|
||||
char * mem = reinterpret_cast<__char *__> (_p);
|
||||
_p = _p->Next();
|
||||
__::delete__ [ ] mem;
|
||||
}
|
||||
}
|
||||
|
||||
Notice all the casting we have to do. When our overloaded new is called, it is expected to return a void pointer. Internally, however, we either recycle a Link from a linked-list pool, or allocate a raw chunk of memory of the appropriate size. We don't want to call __::new Link__, because that would have an unwanted **side effect** of calling Link's constructor (it will be called anyway after we return from operator new).
|
||||
|
||||
Our delete, on the other hand, is called with a void pointer, so we have to cast it to a Link in order to store it in the list.
|
||||
|
||||
Purge deletes all as if they were arrays of chars--since that is how they were allocated. Again, we don't want to delete them as Links, because Link destructors have already been called.
|
||||
|
||||
As usually, calls to global operators new and delete can be disambiguated by prepending double colons. Here, they ar not strictly necessary, but they enhance the readability.
|
||||
|
||||
|
||||
|
||||
===== Bulk Allocation(批量) =====
|
||||
bulk [bʌlk] n.体积, 容积,大块, 大部分, 大批 adj.散装的, 大量的 vt.使膨胀, 使成堆 vi.膨胀, 显得重要
|
||||
amortize [ə'mɔ:taiz]v.分期偿还, 摊还 =amortise(英)
|
||||
|
||||
Another approach to speeding up allocation is to allocate in bulk and thus amortize the cost of memory allocation across many calls to operator** new**. The implementation of Links, Lists and HashTables is as before, except that a new class, __LinkAllocator__ is used in place of __FreeList__. It has the same interface as FreeList, but its implementation is more involved. Besides keeping a list of recycled **Link**s, it also has a separate list of blocks of links. Each block consists of a header of class __Block__ and a block of 16 consecutive raw pieces of memory each the size of a Link.
|
||||
|
||||
class Link;
|
||||
class LinkAllocator
|
||||
{
|
||||
enum { **BlockLinks = 16** };
|
||||
__ class Block__
|
||||
{
|
||||
public:
|
||||
Block * Next () { return _next; }
|
||||
void SetNext (Block * next) { _next = next; }
|
||||
private:
|
||||
Block * _next;
|
||||
};
|
||||
public:
|
||||
LinkAllocator () : _p (0), _blocks (0) {}
|
||||
~LinkAllocator ();
|
||||
void Purge ();
|
||||
void * NewLink ();
|
||||
void Recycle (void * link);
|
||||
private:
|
||||
Link * _p; // 回收(recycled)的节点组成的链表。
|
||||
__Block * _blocks; // 批量分配的内存空间链表(不管该空间是否被利用),每个节点可以容纳一个Block对象+BlockLinks个Link对象。__
|
||||
};
|
||||
|
||||
This is how a new Link is created:
|
||||
|
||||
void * LinkAllocator::NewLink ()
|
||||
{
|
||||
if (_p == 0)
|
||||
{
|
||||
// use global operator new to allocate **a block of links**
|
||||
char * p = __::new char [sizeof (Block) + BlockLinks * sizeof (Link)]__;
|
||||
|
||||
// add it to the list of blocks
|
||||
Block * block = reinterpret_cast<__Block *__> (p);
|
||||
block->SetNext (_blocks);
|
||||
_blocks = block;
|
||||
|
||||
// __add it to the list of links(将分配的BlockLinks个Link对象链接起来,同时加到已有的_p链表上)__
|
||||
p += sizeof (Block);
|
||||
for (int i = 0; i < BlockLinks; ++i)
|
||||
{
|
||||
Link * link = reinterpret_cast<__Link *__> (p);
|
||||
link->_pNext = _p;
|
||||
_p = link;
|
||||
p += sizeof (Link);
|
||||
}
|
||||
}
|
||||
|
||||
void * mem = _p;
|
||||
_p = _p->_pNext;
|
||||
return mem;
|
||||
}
|
||||
|
||||
The first block of code deals with the situation when there are **no unused links** in the **Link **list. A whole block of 16 (BlockLinks) __Link-sized chunks__ is allocated all at once, together with some room for the** Block **header. The Block is immediately linked into the list of__ blocks__ and then chopped up into separate Links which are added to the Link list. Once the Link list is replenished, we can pick a Link from it and pass it out.
|
||||
|
||||
The implementation of Recycle is the same as before--the links are returned to the __Link__ list. Purge, on the other hand, does bulk deallocations of whole blocks.
|
||||
|
||||
void LinkAllocator::Purge ()
|
||||
{
|
||||
while (_blocks != 0)
|
||||
{
|
||||
// it was allocated as an array of char
|
||||
char * mem = reinterpret_cast<__char *__> (_blocks);
|
||||
_blocks = _blocks->Next();
|
||||
::delete [ ] mem;
|
||||
}
|
||||
}
|
||||
|
||||
Only **one call in 16** to new Link results in actual memory allocation. All others are dealt with very quickly by picking a ready-made Link from a list.
|
||||
|
||||
===== Array new =====
|
||||
Even though class **Link** has overloaded operators new and delete, if you were to allocate a** whole array of Links**, as in __new Link [10]__, the compiler would call __global new__ to allocate enough memory for the whole array. It would not call the class-specific overload. Conversly, deleting such an array would result in the call to global operator delete--not it's class-specific overload.
|
||||
|
||||
Since in our program we never allocate arrays of Links, we have nothing to worry about. And even if we did, global new and delete would do the right thing anyway.
|
||||
|
||||
However, in the unlikely case when you actually want to have control over **array allocations**, C++ provides a way. It let's you overload operators__ new[] __and __delete[]__. The syntax and the signatures are analogous to the overloads of straight new and delete.
|
||||
|
||||
void * operator new [] (size_t size);
|
||||
void operator delete [] (void * p);
|
||||
|
||||
The only difference is that the size passed to new[] takes into account the **total size of the array plus some additional data **used by the compiler to distinguish between **pointers to objects and arrays of objects**. For instance, the compiler has to know the number of elements in the array in order to be able to call destructors on all of them when __delete [ ]__ is called.
|
||||
|
||||
All four operators new, delete, new[] and delete[] are treated as__ static members of the class__ that overloads them (i.e., they don't have access to this).
|
||||
|
||||
===== Global new =====
|
||||
Unlike class-specific new, global new is usually overloaded for **debugging purposes**. In some cases, however, you might want to overload global new and delete permanently, because you have** a better allocation strategy** or because you want **more control** over it.
|
||||
|
||||
In any case, you have a choice of overriding __global new and delete__ or adding your own special versions that follow a slightly different syntax. **Standard **operator new takes __one argument__ of type size_t. Standard delete takes one argument of type void *. You can define your own versions of new and delete that take **additional arguments** of arbitrary types. For instance, you can define
|
||||
|
||||
void * operator new (size_t size, __char * name__);
|
||||
void operator delete (void * p, char * name); //name两者的内容要一致。
|
||||
|
||||
and call the special new using this syntax:
|
||||
|
||||
Foo * p = new ("special") Foo; //size参数会自动计算并传入,new/delete是运算符。
|
||||
|
||||
Unfortunately, __there is no way to call the **special** delete explicitly__, so you have to be sure that standard delete will correctly handle memory allocated using your special new (or that delete is never called for such objects).
|
||||
|
||||
So what's the use of the overloaded delete with special arguments? There is actually one case in which it will be called--__when an exception is thrown during object construction__.
|
||||
As you might recall, there is a contract implicit in the language that if an exception happens during the construction of an object, the memory for this object will be automatically deallocated. It so happens that during object's construction the compiler is still aware of which version of operator new was called to allocate memory. It is therefore able to **generate a call to the **corresponding version** of delete**, in case an exception is thrown. After the successful completion of construction, this information is __no longer available__ and the compiler has no means to guess which version of global delete is appropriate for a given object.
|
||||
|
||||
Once you have defined an overloaded version of new, you can call it explicitly,** by specifying additional argument(s)**. Or you can substitute all calls to new in your code with the overloaded version using __macro substitution__.
|
||||
|
||||
===== Macros =====
|
||||
We haven't really talked about macros in this book--they are a part of standard C++, but their use is __strongly discouraged__. In the old times, they were used in place of the more sophisticated C++ features, such as inline functions and templates. Now that there are better ways of getting the same functionality, macros are fast becoming obsolete. But just for completeness, let me explain how they work.
|
||||
|
||||
Macros are obnoxious, smelly, sheet-hogging bedfellows for several reasons, most of which are related to the fact that they are a glorified__ text-substitution__ facility whose effects are applied during preprocessing, before any C++ syntax and semantic rules can even begin to apply.
|
||||
|
||||
A macro works through __literal substitution__. You may think of **macro expansion** as a separate process performed by the compiler before even getting to the main task of parsing C++ syntax. In fact, in older compilers, macro expansion was done by a separate program, the__ preprocessor__.
|
||||
|
||||
There are two major types of macros. The first type simply substitutes one string with another, in the code that logically follows it (by logically I mean that, if the macro is defined in an include file, it will also work in the file that includes it, and so on). Let me give you an example that might actually be useful. Let's define the following macro in the file dbnew.h
|
||||
|
||||
#define new new(____FILE____, ____LINE____)
|
||||
|
||||
This macro will substitute all occurrences of** new **that logically follow it with the string **new (FILE, LINE).** Moreover, the macro preprocessor will then substitute all occurrences of the special pre-defined symbol FILE with the full name of the source file in which it finds it; and all occurrences of __LINE__ with the appropriate line number. So if you have a file c:\test\main.cpp with the contents:
|
||||
|
||||
#include "dbnew.h"
|
||||
int main ()
|
||||
{
|
||||
int * p = __new__ int;
|
||||
return 0;
|
||||
}
|
||||
|
||||
it will be pre-processed to produce the following code:
|
||||
|
||||
int main ()
|
||||
{
|
||||
int * p = new __("c:\test\main.cpp", 4)__ int;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Now you can use your own overloaded operator new, for instance to trace all memory allocation. Here's a simple example of such implementation.
|
||||
|
||||
void * __operator new__ (size_t size, char const * file, int line)
|
||||
{
|
||||
std::cout << file << ": " << line << std::endl;
|
||||
return __::new__ char [size];
|
||||
}
|
||||
|
||||
Notice that we have to make sure that our macro is not included in the file that defines this overload. Otherwise both occurrences of "new" would be substituted by "new(FILE, __LINE)" resulting in incorrect code.
|
||||
|
||||
The second type of macro also works through textual substitution, but it behaves more like** an inline function**-- it takes arguments. And again, since macro expansion works outside of the C++ compiler, there is no type checking and a possibility of unexpected side effects. A classic example is the max macro:
|
||||
|
||||
#define max(a, b) (((a) > (b))? (a): (b))
|
||||
|
||||
Notice the parentesis paranoia--a characteristic feature of macros. Programmers learned to put parentheses around macro parameters, because they might be expressions containing low precedence operators. Consider, for instance, what happens when you call
|
||||
|
||||
c = max (a & mask, b & mask)
|
||||
|
||||
Without the parentheses around parameters in the definition of max, the preprocessor would expand it into
|
||||
|
||||
c = a & mask > b & mask? a & mask: b & mask;
|
||||
|
||||
which, because of operator precedence rules, would be interpreted as:
|
||||
|
||||
c = (a & (mask > b) & mask)? (a & mask): (b & mask;)
|
||||
|
||||
The result of this calculation would most likely be erroneous.
|
||||
|
||||
Things get even worse, when you call a macro with expressions that have side effects. Consider for instance the expansion of max (a++, b++):
|
||||
|
||||
(((a++) > (b++))? (a++): (b++))
|
||||
|
||||
One of the variables will be incremented twice, the other once. This is probably not what the programmer expected.
|
||||
|
||||
By the way, there is one more gotcha--notice that I didn't put a space between.
|
||||
|
||||
===== Tracing Memory Leaks =====
|
||||
|
||||
A more interesting application of this technique lets you trace unreleased allocations, a.k.a. memory leaks. The idea is to store information about each allocation in a global data structure and dump its contents at the end of the program. Overloaded operator delete would remove entries from this data structure.
|
||||
|
||||
Since operator delete has only access to a pointer to previously allocated memory, we have to be able to reasonably quickly find the entry based on this pointer. A map keyed by a pointer comes to mind immediately. We'll call this global data structure a Tracer
|
||||
|
||||
class Tracer
|
||||
{
|
||||
private:
|
||||
class Entry
|
||||
{
|
||||
public:
|
||||
Entry (char const * file, int line)
|
||||
: _file (file), _line (line)
|
||||
{}
|
||||
Entry ()
|
||||
: _file (0), _line (0)
|
||||
{}
|
||||
char const * File () const { return _file; }
|
||||
int Line () const { return _line; }
|
||||
private:
|
||||
char const * _file;
|
||||
int _line;
|
||||
};
|
||||
class Lock
|
||||
{
|
||||
public:
|
||||
Lock (Tracer & tracer)
|
||||
: _tracer (tracer)
|
||||
{
|
||||
_tracer.lock ();
|
||||
}
|
||||
~Lock ()
|
||||
{
|
||||
_tracer.unlock ();
|
||||
}
|
||||
private:
|
||||
Tracer & _tracer;
|
||||
};
|
||||
typedef std::map<void *, Entry>::iterator iterator;
|
||||
friend class Lock;
|
||||
public:
|
||||
Tracer ();
|
||||
~Tracer ();
|
||||
void Add (void * p, char const * file, int line);
|
||||
void Remove (void * p);
|
||||
void Dump ();
|
||||
|
||||
static bool Ready;
|
||||
private:
|
||||
void lock () { _lockCount++; }
|
||||
void unlock () { _lockCount--; }
|
||||
private:
|
||||
|
||||
std::map<void *, Entry> _map;
|
||||
int _lockCount;
|
||||
};
|
||||
|
||||
We have defined two auxillary classes, Tracer::Entry which is used as the value for the map, and Tracer::Lock which is used to temporary disable tracing. They are used in the implementation of Tracer::Add and Tracer::Remove.
|
||||
|
||||
The method Add adds a new entry to the map, but only when tracing is active. Notice that it disables tracing when accessing the map--we don't want to trace the allocations inside the map code.
|
||||
|
||||
void Tracer::Add (void * p, char const * file, int line)
|
||||
{
|
||||
if (_lockCount > 0)
|
||||
return;
|
||||
Tracer::Lock lock (*this);
|
||||
_map [p] = Entry (file, line);
|
||||
}
|
||||
|
||||
The method Remove makes the same preparations as Add and then searches the map for the pointer to be removed. If it's found, the whole entry is erased.
|
||||
|
||||
void Tracer::Remove (void * p)
|
||||
{
|
||||
if (_lockCount > 0)
|
||||
return;
|
||||
|
||||
Tracer::Lock lock (*this);
|
||||
|
||||
iterator it = _map.find (p);
|
||||
if (it != _map.end ())
|
||||
{
|
||||
_map.erase (it);
|
||||
}
|
||||
}
|
||||
|
||||
Finally, at the end of the program, the method Dump is called from the destructor of Tracer to display all the leaks.
|
||||
|
||||
Tracer::~Tracer ()
|
||||
{
|
||||
Ready = false;
|
||||
Dump ();
|
||||
}
|
||||
|
||||
void Tracer::Dump ()
|
||||
{
|
||||
if (_map.size () != 0)
|
||||
{
|
||||
std::cout << _map.size () << " memory leaks detected\n";
|
||||
for (iterator it = _map.begin (); it != _map.end (); ++it)
|
||||
{
|
||||
char const * file = it->second.File ();
|
||||
int line = it->second.Line ();
|
||||
std::cout << file << ", " << line << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Notice: if your implementation of standard library cannot deal with standard output after the termination of main (), read the next section, Debug Output.
|
||||
|
||||
Since we are overloading global operators new and delete, the Tracer has to be a global object too.
|
||||
|
||||
extern Tracer NewTrace;
|
||||
|
||||
Notice that this might lead to some problems, if there are other global objects that allocate memory in their constructors. The order of construction of global objects residing in different files is undefined. If a memory-allocating global object is constructed before the construction of NewTracer, we're in trouble. That's why I introduced a static Boolean flag, Tracer::Ready, which is originally set to false.
|
||||
|
||||
bool Tracer::Ready = false;
|
||||
|
||||
The constructor of Tracer sets this flag to true and Tracer::Dump sets it back to false.
|
||||
|
||||
Tracer::Tracer ()
|
||||
: _lockCount (0)
|
||||
{
|
||||
Ready = true;
|
||||
}
|
||||
|
||||
The implementation of the overloaded new is straightforward.
|
||||
|
||||
void * operator new (size_t size, char const * file, int line)
|
||||
{
|
||||
void * p = malloc (size);
|
||||
if (Tracer::Ready)
|
||||
NewTrace.Add (p, file, line);
|
||||
return p;
|
||||
}
|
||||
|
||||
Notice that we use the low level memory allocating function malloc, rather than calling operator ::new. That's because we are going to overload the regular new as well.
|
||||
|
||||
There must be a corresponding overload of delete, to be used during exception unwinding.
|
||||
|
||||
void operator delete (void * p, char const * file, int line)
|
||||
{
|
||||
if (Tracer::Ready)
|
||||
NewTrace.Remove (p);
|
||||
free (p);
|
||||
}
|
||||
|
||||
Since we used malloc for memory allocation, we have to use free for deallocation.
|
||||
|
||||
For completeness, we also override the regular global new, in case there are parts of our code outside of the reach of macro substitution (for instance, parts of the standard library).
|
||||
|
||||
void * operator new (size_t size)
|
||||
{
|
||||
void * p = malloc (size);
|
||||
if (Tracer::Ready)
|
||||
NewTrace.Add (p, "?", 0);
|
||||
return p;
|
||||
}
|
||||
|
||||
Finally, we have to override the global delete in order to trace all deallocations.
|
||||
|
||||
void operator delete (void * p)
|
||||
{
|
||||
if (Tracer::Ready)
|
||||
NewTrace.Remove (p);
|
||||
free (p);
|
||||
}
|
||||
|
||||
Since we only want the tracing to be enabled in the debug version of our program, we'll enclose the definition of our macro in conditional compilation directives.
|
||||
|
||||
#if !defined NDEBUG
|
||||
#include "debugnew.h"
|
||||
|
||||
#define new new(__FILE__, __LINE__)
|
||||
|
||||
#endif
|
||||
|
||||
Most compilers define the flag NDEBUG (no debug) when building the release (non-debugging) version of the program. The file debugnew.h contains, among others, the declaration of overloaded operators new and delete.
|
||||
|
||||
Similarly, we have to make sure that the implementation of overloaded new and delete is also compiled conditionally. That's because the decision as to which version of new and delete will be called from your program is done by the linker. If the linker doesn't find an implementation of these operators in your code, it will use the ones privided by the runtime library. Otherwise it will call your overrides throughout.
|
||||
|
||||
Finally, we add the definition of the global object NewTrace to main.cpp. The destructor of this object will dump memory leaks after the end of main ().
|
||||
|
||||
#if !defined NDEBUG
|
||||
Tracer NewTrace;
|
||||
#endif
|
||||
|
||||
|
||||
Debug Output
|
||||
|
||||
An even better idea is to redirect the dump to the debugger. (An additional advantage of doing that is to bypass potential library bugs that prevent standard output after main ()). There is a function OutputDebugString, declared in <windows.h>, which outputs strings to the debug window, if you are running the program under the debugger. We format the string using std::stringstream.
|
||||
|
||||
if (_map.size () != 0)
|
||||
{
|
||||
OutputDebugString ("*** Memory leak(s):\n");
|
||||
for (iterator it = _map.begin (); it != _map.end (); ++it)
|
||||
{
|
||||
char const * file = it->second.File ();
|
||||
int line = it->second.Line ();
|
||||
int addr = reinterpret_cast<int> (it->first);
|
||||
std::stringstream out;
|
||||
out << "0x" << std::hex << addr << ": "
|
||||
<< file << ", line " << std::dec << line << std::endl;
|
||||
OutputDebugString (out.str ().c_str ());
|
||||
}
|
||||
OutputDebugString ("\n");
|
||||
}
|
||||
|
||||
If your standard library doesn't handle even that, try bypassing integer output by using a low-level conversion routine, itoa (integer-to-ascii).
|
||||
|
||||
char buffer1 [10];
|
||||
char buffer2 [8];
|
||||
out << "0x" << itoa (addr, buffer1, 16) << ": "
|
||||
<< file << ", line " << itoa (line, buffer2, 10) << std::endl;
|
||||
|
||||
|
||||
Placement new
|
||||
|
||||
There is one particular overload of new that is part of the standard library. It's called placement new (notice: sometimes all overrides of new that take extra arguments are called placement new) and it takes one additional argument--a void pointer. It is used whenever the memory for the object has already been allocated or reserved by other means. The argument could be a pointer to some static memory or to a chunk of pre-allocated raw dynamic memory. Placement new does not allocate memory--it uses the memory passed to it (it's your responsibility to make sure the chunk is big enough) and calls the appropriate constructor.
|
||||
|
||||
For instance, in our earlier example with bulk allocation, we could use placement new to create a Block object using memory that's been allocated as an array of bytes.
|
||||
|
||||
char * p = ::new char [sizeof (Block) + BlockLinks * sizeof (Link)];
|
||||
Block * block = new (p) Block (_blocks);
|
||||
_blocks = block;
|
||||
|
||||
It makes sense now to have a constructor of Block that initializes the pointer to next. In fact, the method SetNext is no longer needed.
|
||||
|
||||
LinkAllocator::Block::Block (Block * next) : _next (next) {}
|
||||
|
||||
The standard library defines a corresponding placement delete which does absolutely nothing, but is required in case the constructor throws an exception. Since placement new doesn't allocate any memory, it's an error to delete the object created by it. Of course, the raw memory that's been passed to placement new has to be dealt with appropriately. In our example, it's the Purge method that frees raw memory.
|
||||
|
||||
By the way, there is also an array placement operator new[] and the corresponding delete[]. It is left as an exercise for the user to use it for converting memory following the Block header to an array of Links (what kind of a constructor would you add to Link for that purpose?).
|
||||
115
Zim/Programme/C++/对象及内存管理/关于C++类成员函数的重载、覆盖、隐藏与virtual关键字.txt
Normal file
@@ -0,0 +1,115 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-17T13:19:34+08:00
|
||||
|
||||
====== 关于C++类成员函数的重载、覆盖、隐藏与virtual关键字 ======
|
||||
Created Friday 17 February 2012
|
||||
|
||||
http://www.cppblog.com/phoenix8848cn/archive/2008/10/13/63849.html
|
||||
|
||||
最近看<<高质量C++>>时读到的关于成员函数的重载/覆盖/隐藏,把我的一点理解写出来,希望大家批评与指正.
|
||||
|
||||
===== 1. 重载、覆盖与隐藏 =====
|
||||
|
||||
1).重载(overload):成员函数具有以下的特征时发生“重载”
|
||||
A.__相同的范围__(同一个类中)
|
||||
B.函数的名字相同
|
||||
C.参数类型不同(不能进行隐式类型转换)
|
||||
D.Virtual关键字可有可无
|
||||
|
||||
2).覆盖(override, 也叫“继承”):指派生类函数覆盖基类函数,特征是:
|
||||
A.__不同的范围__(分别位于基类与派生类中)
|
||||
B.函数名字相同
|
||||
C.**参数相同**
|
||||
D.基类函数__必须有virtual__关键字
|
||||
|
||||
3).隐藏(hide):是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
|
||||
A.如果派生类的函数与基类的函数同名,但是参数不同,此时不论有无virtual关键字,__基类的函数都将被隐藏__,注意别与重载混淆
|
||||
B.如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字,此时基类的函数被隐藏(注意别与覆盖混淆)
|
||||
|
||||
2.看下面这个例子代码:
|
||||
|
||||
1 #include <iostream>
|
||||
2 using std::cout;
|
||||
3 using std::endl;
|
||||
4
|
||||
5 class Base
|
||||
6 {
|
||||
7 public:
|
||||
8 virtual void f(float x){ cout << "Base::f(float) " << x << endl;}
|
||||
9 void g(float x){ std::cout << "Base::g(float) " << x << std::endl;}
|
||||
10 void h(float x){ std::cout << "Base::h(float) " << x <<std::endl;}
|
||||
11 };
|
||||
12
|
||||
13 class Derived : public Base
|
||||
14 {
|
||||
15 public:
|
||||
16 virtual void f(float x){ std::cout << "Derived::f(float) " << x << std::endl;}
|
||||
17 void g(int x){ std::cout << "Derived::g(int) " << x << std::endl;}
|
||||
18 void h(float x){ std::cout << "Derived::h(float) " << x << std::endl;}
|
||||
19 };
|
||||
20
|
||||
21 void main(void)
|
||||
22 {
|
||||
23 Derived d;
|
||||
24 Base *pb = &d;
|
||||
25 Derived *pd = &d;
|
||||
26
|
||||
27 pb->f(3.14f);//Derived::f(float) 3.14
|
||||
28 pd->f(3.14f);//Derived::f(float) 3.14
|
||||
29
|
||||
30 pb->g(3.14f);//Base::g(float) 3.14
|
||||
31 __ pd->g(3.14f);//Derived::g(int) 3__
|
||||
32
|
||||
33 pb->h(3.14f);//Base:h(float) 3.14
|
||||
34 pd->h(3.14f);//Derived::h(float) 3.14
|
||||
35 }
|
||||
|
||||
|
||||
3. 解释
|
||||
在27与28行,派生类的Derived::f(float x)通过virtual关键字 __继承(覆盖)__了基类的Base::f(float x)方法,所以这里无论采有基类指针还是派生类指针,最后调用的其实都是Derived::f(float x)方法。这正是一般情况我们所期望的。
|
||||
|
||||
在30行,由于基类的Base::g()没有用virtual关键字声明,所以这里它不会被派生类的Derived::g()方法覆盖。所以通过基类指针访问时只能访问到Base::g(float x),而在31行通过派生类指针时可以访问的方法有Base::g(float x)和Derived::g(int x),这两个方法虽然方法名相同而且参数不同(似乎)符合重载的标准,__但是它们却分属于不同的“域”因此重载不会发生__,这时Derived::g(int x)就只能把Base::g(float x)“隐藏”掉。
|
||||
|
||||
同上,在第33行通过基类指针能访问的方法只有Base::h(float x),由于该方法没有被virtual关键字声明,所以不会被派生类方法Derived::h(float x)“替换”,因此调用的是Base::h(float x)。而在第34行通过派生类指针可以访问的方法同时有Base::h(float x)与Derived::h(float x),这似乎又冲突,而这时C++的“隐藏”规则发生作用,所以派生类方法Derived::h(float x)把基类方法Base::h(float x)“隐藏”,于是Derived::h(float x)被调用。
|
||||
|
||||
===== 4.总结 =====
|
||||
|
||||
C++的“重载”、“继承”与“隐藏”机制比一般想象中的要复杂,而这就突显了__virtual关键字的重要性__。所以在派生类存在的前提下一,__一定要把基类中可能在派生类中也实现的方法用virtual关键字声明__。除非在特殊情况下,比如需要检查指针类型的时候。
|
||||
|
||||
1 #include <iostream>
|
||||
2 using std::cout;
|
||||
3 using std::endl;
|
||||
4
|
||||
5 class Base
|
||||
6 {
|
||||
7 public:
|
||||
8 void CheckType(void){ cout << "This's Base Ptr" << endl;}
|
||||
9 };
|
||||
10
|
||||
11 class Derived : public Base
|
||||
12 {
|
||||
13 public:
|
||||
14 void CheckType(void){ cout << "This;s Derived Ptr" << endl;}
|
||||
15 };
|
||||
16
|
||||
17 void main(void)
|
||||
18 {
|
||||
19 Derived d;
|
||||
20 Base *pb = &d;
|
||||
21 Derived *pd = &d;
|
||||
22
|
||||
23 pb->CheckType();//This's Base Ptr
|
||||
24 pd->CheckType();//This's Derived Ptr
|
||||
25 }
|
||||
26
|
||||
|
||||
|
||||
posted on 2008-10-13 07:42 西门有悔 阅读(2588) 评论(1) 编辑 收藏 引用
|
||||
评论
|
||||
# re: 关于C++类成员函数的重载、覆盖、隐藏与virtual关键字 回复 更多评论
|
||||
整理得非常好
|
||||
不过对隐藏的概念应该还可以简化为:__基类成员函数中,不满足覆盖条件的派生类同名成员函数,都视为隐藏__(既基类方法不能被派生类继承使用)。
|
||||
而显然满足覆盖的充要条件是:
|
||||
(a)在基类中函数声明的时候有virtual关键字
|
||||
(b)基类中的函数和派生类中的函数声明一模一样即函数名,参数,返回类型都一样
|
||||
128
Zim/Programme/C++/对象及内存管理/动态分配内存new关见字的使用方法.txt
Normal file
@@ -0,0 +1,128 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T14:22:20+08:00
|
||||
|
||||
====== 动态分配内存new关见字的使用方法 ======
|
||||
Created Saturday 06 August 2011
|
||||
|
||||
new是在堆上分配内存,它需要用delete释放,否则会造成内存泄漏(使用的内存没有即时释放,造成内存的浪费)
|
||||
|
||||
而A a在右大括号执行后,会自动释放内存
|
||||
如
|
||||
int main()
|
||||
{
|
||||
A a;//定义了一个a对象
|
||||
A *p=new A;//在堆上定义了一个对象,它的指针保存在p里,注意,堆上定义的对象没有名字,必须用指针保存
|
||||
return 0;
|
||||
}//a到这里的时候,它占用的内存就会被回收 而p,除非调用delete p; 否则内存永远不会被回收,指针p丢弃后,那块内存没被释放,
|
||||
|
||||
无法被再次使用,造成内存浪费
|
||||
|
||||
下面给你详细介绍一下动态分配内存new关键字的使用方法,希望看了之后你能全现了解什么时候可以使用new以及怎样使用new,
|
||||
|
||||
以下内容全面,简单易懂,是完全出自本人的学习总结,绝非复制。
|
||||
|
||||
动态分配内存new关键字
|
||||
|
||||
1. 全局对象和局部对象的__生命期__都是严格定义的,程序员不能以任何方式改变他们的生命期。但是有时候需要创建一些生命期能被程序员控制的
|
||||
对象,他的分配和释放可以根据程序运行中的操作来决定。这时就需要使用new操作符了。
|
||||
2. 动态分配内存,将从堆中分配内存,动态分配的存储区是在运行时确定的,动态分配的存储区可以随着需求而自动扩大.
|
||||
3. 局部变量一般存储在__堆栈__中,堆栈是先进后出的存储结构,而堆却不是.
|
||||
4. 在C++中使用new动态分配内存,delete释放以前new分配的内存.
|
||||
5. 使用new运算符系统将从空闲存储区中为对象分配内存,并返回一个指向该对象的指针即该对象的地址。new运算符的特点是:
|
||||
|
||||
用new运算符__分配的对象没有名字__,对该对象的操作都要__通过指针间接地完成__操作。例如new int,就是从空闲存储区分配了一个int型对象,
|
||||
但没法对这个对象进行操作,只是从存储区分配了这么一个空间。语句int *p=new int表示从空闲存储区分配一个int对象并把这个对象的
|
||||
地址赋给p,现在p就是用new分配的int对象的地址,而*p就是那里的值。语句int i;int*p=&i;和int *p=new int都是将int变量的地址赋给
|
||||
了指针,但不同的是前句可以用名称i和*p来访问该int型变量,而后句则只能用*p来访问该变量,也就是说p指向的内存没有名称。
|
||||
|
||||
6. __动态创建数组__:int *p=new int [11];创建动态数组时必须有[]方括号,且里面要有创建的维数,但该数组的第一维可以是一个复杂的
|
||||
表达式。访问地址中的内容的方法为*p访问数组中的第一个元素,p[1]该问第二个元素,以此类推。创建二组数组的例子:
|
||||
int (*p)[102]=new int [4][102]。
|
||||
|
||||
7. 动态创建对象的初始化:int *p=new int(102)该语句表明由p指向的新创建你对象被初始化为102。动态创建对象的初始化方式为在__类型名__
|
||||
__ 后面用一对括号来被始化__。
|
||||
|
||||
8. 动态创建对象的__默认初始化__:方式为在类型名后面跟一对__空的圆括号__初始化,
|
||||
|
||||
int *p=new int (); int *ps=new string(); cls *pc=new cls();
|
||||
第一条语句把对象的值初始化为0,__第二条语句对于提供了默认构造函数的string类,不论程序是要明确的不初始化,还是要求进行值初始化__
|
||||
__ 都会调用默认构造函数初始化该对象。而对于内置类型或没有默认构造函数的类型,则采用不同的初始化方式就会有显著不同的差别。__
|
||||
例如:int *p=new int; int *p=new int();第一条语句没有被初始化,而第二条被初始化为0。
|
||||
|
||||
9. 用new动态创建的__数组不能被初始化__,不能创建初始化值集。
|
||||
10.耗尽内存:如果程序用完了所有可用的内存,new表达式就有可能失败。如果new表达式无法获得要需要的内存空间,
|
||||
系统将会抛出名为bad_alloc异常。
|
||||
|
||||
11.可以在一个函数内使用new运算符分配内存,而在另一个函数中使用delete释放内存空间.delete只能用于释放前次使用new分配的空间,
|
||||
不要使用delete来释放不是new分配的内存,不要使用delete释放相同的内存两次,应使用delete []来释放动态数组,例如:
|
||||
int *p=new int [10];delete [] p;删除数组必须要有[]方括号。delete不一定要用于new的指针,
|
||||
例如int *p=new int ; int *x=p; delete x;将是合法的.如果指针的值为0,则在其上作delete操作是合法的,但没有任何意义。
|
||||
12.悬垂指针:执行delete p后只是把p所指向的地址的内容给释放掉了,并没有删掉指针p本身,还可以将p重新指向到另一个新的内存块,
|
||||
因此p还指向原来他指向的对象的地址,然而p所指向的内容却已经被释放了,因此p不再有效而变得没有定义了。这样的指针称为悬垂指针。
|
||||
悬垂指针往往导致程序错误而且很难检测出来。
|
||||
13.静态联编:如果通过声明来创建数组,则在程序被__编译__时为他分配内存空间,不管程序是否使用数组,数组都在那里,占用了内存。
|
||||
在编译时给数组分配内存被称为静态联编。意味着数组是在编译时加入到程序中的。但__使用new时,如果在运行阶段需要数组__,则创建他,
|
||||
如果不需要,则不创建,还可以在程序运行时选择数组的长度,这被称为动态联编。意味着数组是在程序运行时创建的,这种数组叫做
|
||||
__ 动态数组__,使用静态联编时必须在编写程序的时候指定数组的长度,使用动态联编时,程序将在运行时确定数组的长度。
|
||||
|
||||
14.const常量对象的动态分配和回收:与其他常量一样,__动态创建的const对象必须在创建时初始化,并且一经初始化,其值不能再修改__。
|
||||
|
||||
例如:const int *p= new const int(111);删除方法为:delete p;尽管程序员不能修改const对象的值,但可以撤消对象本身。
|
||||
|
||||
15.注意:__不能在空闲存储区上创建内置类型元素(除类、数组、string外)的const数组__。因为我们不能初始化用new创建的内置类型数组的元素。
|
||||
|
||||
如const int *p=new const int [11];将是错误的。
|
||||
|
||||
16.注意:如果用new分配了资源,而没有用delete释放该资源的话,那么用new分配的资源将一指被占用。
|
||||
17.常见错误:如果对某个指针动态分配了内存,又把另一个变量的地址付给这个指针,这时这个指针就指向了一个静态地址,
|
||||
而不是原先的动态地址。如果再用delete删掉这个指针时就会出错,因为这时这个指针是指向静态地址的,不能用delete删除一个指向
|
||||
静态地址的指针。比如int *p =new int(1),这时指针p指向一个动态内存可以对他进行delete删除,但如果再执行语句int a=2; p=&a;
|
||||
这时就改变了指针指向的内容,使原先指向的动态内存地址变成了指向现在的静态内存地址,如果这时对指针p进行delete操作就会出错,
|
||||
|
||||
因为你在对一个静态指针静行删除,而delete只能删除动态指针。
|
||||
例:动态分配对象new
|
||||
class hyong
|
||||
{public:
|
||||
int a,b,c;
|
||||
hyong (){a=b=c=0;}
|
||||
hyong(int i){a=b=c=i;}
|
||||
~hyong() {cout<<"xigou"<<"/n";}
|
||||
};
|
||||
|
||||
int main()
|
||||
{ hyong *p=new hyong;
|
||||
hyong *p1=new hyong();
|
||||
cout<<p->a<<p1->a<<"/n"; //输出两个0,__都调用默认构造函数初始化指针__。
|
||||
int *p2=new int;
|
||||
int *p3=new int();
|
||||
int *p4=new int(1); __//new分配内存的初始化方式。__
|
||||
//对于__类置类型来说p2没有被初始化得到的是一个随机值__,p3被初始化为0,p4被初始化为1。
|
||||
cout<<*p2<<"/n"<<*p3<<"/n"<<*p4<<"/n"; //输出一个随机值,一个0,一个1
|
||||
int i=10;
|
||||
delete p4;
|
||||
cout<<*p4<<"/n"; //p4现在是悬垂指针,delete只是释放掉了指针p4所指向地址的内容, 但指针p4仍然指向原来的地址,但没有内容,指针p4仍然可以再指向其他地址。
|
||||
p4=&i;
|
||||
cout<<*p4<<"/n"; //可以对悬垂指针p4重新赋地址。
|
||||
const int *p5=new int;
|
||||
const int *p6=new int(4) ;
|
||||
cout<<*p5<<*p6<<"/n";//输出一个随机值和4,__const常量必须在声明时初始化。__
|
||||
int *p7=new int[2];
|
||||
p7[0]=5;
|
||||
p7[1]=6;
|
||||
cout<<p7[0]<<p7[1]<<"/n";__//定义动态数组,动态数组不能在声明时初始化。__
|
||||
//const int *p8=new int[2]; //错误,__因为动态数组不能在声明时初始化,而const又必须要求在声明时初始化,发生冲突__,出错。
|
||||
delete p1;
|
||||
delete p;
|
||||
//delete p,p1; //注意,如果使用该语句将则只调用一次析构函数。
|
||||
delete p2,p3,p4,p5,p6;
|
||||
delete [] p7;
|
||||
//int *p8=new int(9); int a=8; p8=&a; delete p8; //错误,现在的指针p8重新指向了一个静态的地址,
|
||||
用delete删掉一个静态地址将发生错误。
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
160
Zim/Programme/C++/对象及内存管理/类、对象和内存.txt
Normal file
@@ -0,0 +1,160 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-17T14:27:39+08:00
|
||||
|
||||
====== 类、对象和内存 ======
|
||||
Created Friday 17 February 2012
|
||||
|
||||
http://www.cnblogs.com/peer/archive/2011/07/14/2106226.html
|
||||
|
||||
==== 1.1 通过内存看对象 ====
|
||||
|
||||
我 们先回顾一下类和对象的定义,类是定义同一类所有实例变量和方法的蓝图或原型;对象是类的实例化。从内存的角度可以对这两个定义这样理解,__类刻画了实例的内存布局__,确定实例中__每个数据成员在一块连续内存中的位置、大小以及对内存的解读方式__;对象就是系统根据类刻画的内存布局去分配的内存。
|
||||
|
||||
除了实例变量和方法,类也可以定义类变量和类方法,这是我们通常所说的**类静态变量和类静态函数**,它们不属于某个具体的对象,而是__属于整个类__,所以不会影响对象的内存布局和内存大小。通过以上的讨论我们可以知道:__对象本质上就是一块连续的内存,对象的类型(类)就是对这块内存的解读方式__。
|
||||
|
||||
在c++中我们可以通过四个__类型转换运算符__改变对象的类型,这种转换__改变的是内存的解读方式,不会修改内存中的值__。
|
||||
|
||||
修改对象内存值的合法途径是**通过成员函数/友元函数**修改对象的数据成员。通过成员函数修改对象的值是c++语 言保证对象安全的一种机制,但这种机制__不是强制的__,你可以通过暴力的非法手段避开这个机制(比如你可以取得对象的起始地址,然后根据对象的内存布局任意修改内存的值),除了极其特殊情况,这种人为非法手段都应当被禁止,因为这种暴力代码难于理解、不便移植、极易出错;另外程序在运行过程中由于代码中的某些缺陷也会非法修改对象内存的值,这是我们程序中许多疑难bug的根源。所以正确的编写类,理解对象在内存的运行特点,合理的控制对象的创建和销毁是一个程序稳定运行的基本保证。
|
||||
|
||||
===== 1.2 不同内存区域的对象 =====
|
||||
|
||||
在C++中,对象通常存放在三个内存区域:__栈、堆、全局/静态数据区__;相对应的,在这三个区域中的对象就被称为__栈对象、堆对象、全局/静态对象__。
|
||||
|
||||
* **全局/静 态数据区:**全局对象和静态对象存放在该区,在该内存区的对象__一旦创建后直到进程结束才会释放__。在其生存期内可以被多个线程访问,它可以做为多线程通信的一 种方式,所以对于全局对象和静态对象要考虑__线程安全__,特别是对于函数中的局部静态变量,容易忘记它的线程安全性。全局对象和一些静态对象有一个特点:**这些对象的初始化操作先于main函数的执行(编译器自动添加的starup code会创建 并初始化他们)**,而且这些对象(可能分布在不同的源文件中)**初始化顺序没有规定**,所以在它们的初始化中不要启动线程,同时它们的初始化操作也不应有依赖关系。
|
||||
|
||||
* 堆:堆对象是通过new/malloc在堆中__动态分配__内存,通过delete/free释放内存的对象。我们可对这种对象的创建和销毁进行__精确控制__。堆对象在c++中的使用非常广泛,不同线程间、函数间的对象共享可以使用堆对象,**大型对象**一般也使用堆对象(栈空间是有限的),特别是__虚函数多态性一般是由堆对象实现__的。使用堆对象也有一些缺点:1.需要程序员管理生存周期,忘记释放会有内存泄露,多次释放可能造成程序崩溃,通过__智能指针__可以避免这个问题;2.堆对象的__时间效率和空间效率没有栈对象高__,堆对象一般通过某种搜索算法在堆中找到合适大小的内存,比较耗时间,另外从堆中分配的内存大小会比实际申请的内存大几个字节,比较耗空间,尤其是对于小型对象这种损耗是非常大的;3.频繁使用new/delete 堆对象会造成大量的**内存碎片**,内存得不到充分的使用。对于2,3两个问题可以通过**内存一次分配,多次使用**的方法解决,更好的方法是根据业务特点实现特定的__内存池__。
|
||||
|
||||
* 栈:栈 对象是**自生自灭型**对象,程序员无需对其生存周期进行管理。一般,__临时对象__、函数内的__局部对象__都是栈对象。使用栈对象是**高效**的,因为它不需要进行内存搜索只 进行栈顶指针的移动。另外栈对象是线程安全的,因为不同的__线程有自己的栈内存__。当然,栈的空间是有限的,所以使用中要防止栈溢出的出现,通常大型对象、大 型数组、递归函数都要慎用栈对象。
|
||||
|
||||
===== 2 C++对象的创建和销毁 =====
|
||||
|
||||
C++类有四个基本函数:构造函数、析构函数、拷贝构造函数、__赋值运算符重载函数__,这四个函数管理着C++对象的创建和销毁,正确而完整地实现这些函数是C++对象安全运行必要保证。
|
||||
|
||||
==== 2.1 构造/析构 ====
|
||||
创建一个对象有两个步骤:1.在内存中分配sizeof(CAnyType) 字节数的内存,即__分配内存__;2.调用合适构造函数__初始化分配内存__。
|
||||
C++对象的大小由三个因素决定:1.各个数据成员的大小;2.由字节对齐产生的**填充空间**的大小;3.为支持虚机制编译器添加的一个指针,大小是四个字节。
|
||||
__虚机制指针__有两种:1.支持虚函数的**虚表指针**,2.支持虚继承的**虚基类指针**。虚继承时,派生类只保存一份被继承的基类的实体,比如下面例子的菱形继承关系中,类D中只有一份类A的实体。
|
||||
另外,类A是一个空类,但sizeof(A)大小不是0,而__是1__,这是因为需要__用这一个字节来唯一标识类A在内存中的不同对象__(这里测量的是类自身的大小,而不是其对象的大小)。
|
||||
|
||||
Class A{};
|
||||
|
||||
Class B : virtual public A {};
|
||||
|
||||
Class C : virtual public A {};
|
||||
|
||||
Class D:public B, public C {}
|
||||
|
||||
由上面讨论知道,对象的内存中除了有编程人员写的数据成员,有时还有一些由编译器为支持虚机制而**偷偷给你添加的成员**,这些成员我们在代码中不会直接用到,但有可能被我们的代码非法修改。比如不恰当的构造函数会修改虚机制指针的值,在写构造函数时我们经常使用如下的代码对整个对象进行初始化:
|
||||
|
||||
memset(this, 0, sizeof(*this));
|
||||
|
||||
这种初始化方式只能在__类不涉及虚机制__的情况下使用,否则它会修改虚机制指针,使类对象的行为无定义。
|
||||
|
||||
销毁一个对象也有两个步骤:1.调用析构函数;2.向系统归还内存。
|
||||
|
||||
析构函数的作用是释放对象申请的资源。析构函数通常是由系统**自动调用**的,在以下几种情况下系统会调用析构函数:
|
||||
__1.栈对象生命周期结束时:包括离开作用域、函数正常return(不考虑NRV优化)、函数异常throw;__
|
||||
__2.堆对象进行delete操作时;__
|
||||
__3.全局对象在进程结束时__。
|
||||
|
||||
析构函数只在一种情况下需要被显式的调用,那就是用placement new构建的对象。当类里包含虚函数的时候我们应该声明__虚析构函数__,虚析构函数的作用是:当你delete一个指向派生类对象的基类指针时**保证派生类的析构函数被正确调用**。
|
||||
|
||||
有许多资源泄露的问题就是因为没有正确使用虚析构函数造成的,这种资源泄露有两种:
|
||||
1.派生类里直接分配的资源;2.派生类里的__成员对象分配的资源__。尤其是第二类,隐蔽性非常高。
|
||||
|
||||
构造和析构是一组被成对调用的函数,特别是对于栈对象,调用是由系统自动完成的,所以我们可以利用这一特性__将一些需要成对出现的操作分别封装在构造和析构函数里由系统自动完成,这样可以避免由于编程时的遗漏而忘记进行某种操作(RAII)__。比如资源的申请和释放,多线程中的加锁和解锁都可以利用栈对象的这一特性进行自动管理。
|
||||
|
||||
|
||||
===== 2.2 拷贝/赋值 =====
|
||||
拷贝构造函数、赋值运算符重载函数是一对孪生兄弟,通常一个类如果需要显式写拷贝构造函数,那么它__也需要__显式写赋值运算符重载函数。
|
||||
拷贝构造函数的功能是用__已存在__的对象构造一个__新__的对象,赋值运算符重载函数的功能是用__已存在__的对象**替换**一个__已存在__的对象。看下面几条语句:
|
||||
|
||||
string str1 = “string test”; //调用带参数的构造函数
|
||||
string str2(str1); //调用拷贝构造函数
|
||||
string str3 = str1; //调用拷贝构造函数
|
||||
string str4; //调用默认构造函数
|
||||
str4 = str3; //调用赋值运算符重载函数
|
||||
|
||||
拷贝构造函数、赋值运算符重载函数原型如下:
|
||||
|
||||
class string
|
||||
{
|
||||
private:
|
||||
|
||||
char* m_pStr;
|
||||
int m_nSize;
|
||||
|
||||
public:
|
||||
string (); //默认构造函数
|
||||
string (const char* pStr); //带参数的构造函数
|
||||
~ string ();
|
||||
string (const string & cOther); //拷贝构造函数
|
||||
string & operator=(const string & cOther); //赋值运算符重载函数
|
||||
}
|
||||
|
||||
这两个函数的参数类型都是const string &,我们知道对于c++对象通常以常引用作函数的参数,这样可以__提高参数的传递效率__,以对象作为函数参数时会调用**拷贝构造函数**生成一个__临时对象__供函数使用,效率较低。拷贝构造函数 是一个很特殊的函数,对于其他函数用对象作为函数参数顶多是效率的损失,但对拷贝构造函数用对象作为函数参数就会形成无限递归调用,所以__拷贝构造必须以常引用作为参数__。
|
||||
|
||||
拷贝构造函数在c++编译器中有**缺省的实现**,实现的方式是__按位对内存进行拷贝__(memcpy(this, & cOther, sizeof(string)),如果缺省的实现满足我们的要求那就不需要显式的去实现这个函数,否则就必须实现,判断是否满足位拷贝语义的依据是__类的成员数据中是否需要动态分配其他资源__,比如上面的string类,成员m_pStr需要从堆中分配内存来存放具体的字符串,这块堆内存是位拷贝语义无法正确管理的,所以在string对象进行拷贝/赋值时编程人员需要负责管理这块内存。
|
||||
|
||||
通常在三种情况下会调用拷贝构造函数,
|
||||
1. 一个对象以值传递的方式传入函数;
|
||||
2. 一个对象以值传递的方式从函数返回;
|
||||
3. 一个对象需要通过另外一个对象进行初始化。
|
||||
|
||||
如果你确保对类对象的使用不会出现以上三种情况,那就说明你根本不需要拷贝构造函数,直接__将拷贝构造函数私有化__是最安全的选择。从以上的讨论我们知道,对于拷贝构造函数有三种处理策略(对于赋值运算符重载函数同样适用):1.什么都不写,按缺省的处理;2.显式写拷贝构造;3.将拷贝构造私有化。在写一个类前,我们必须分析类自身的实现方式以及对类对象的使用方式,明确选择一种策略,如果你放弃选择你就为将来可能出现的bug埋下一个伏笔。
|
||||
|
||||
上面讨论了拷贝/赋值函数的选择策略,下面看看它们具体的实现方式。拷贝构造函数的功能由一个对象构造一个新的对象,只要一个Copy操作就可以完成。赋值运算符重载函数的功能是由一个对象替换一个已存在的对象,完成这个功能需要三个操作:自赋值检查、Clear原有对象、Copy新对象。如string类的实现:
|
||||
|
||||
class string
|
||||
{
|
||||
private:
|
||||
char* m_pStr;
|
||||
int m_nSize;
|
||||
|
||||
public:
|
||||
string (); //默认构造
|
||||
string (const char* pStr); //带参数的构造函数
|
||||
~ string ();
|
||||
string (const string & cOther) //实现拷贝构造函数
|
||||
|
||||
{
|
||||
Copy(cOther);
|
||||
}
|
||||
string & operator=(const string & cOther) //实现赋值运算符重载函数
|
||||
{
|
||||
if (__this != & cOther__)
|
||||
{
|
||||
Clear();
|
||||
Copy(cOther);
|
||||
}
|
||||
return *this;
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void Copy(const string & cOther)
|
||||
{
|
||||
m_pStr = new char [cOthre. m_nSize+1];
|
||||
__ strcpy__(m_pStr, cOther. m_pStr);
|
||||
m_nSize = cOther.m_nSize;
|
||||
}
|
||||
|
||||
void Clear()
|
||||
|
||||
{
|
||||
if (m_pStr != NULL)
|
||||
{
|
||||
delete[] m_pStr;
|
||||
m_pStr = NULL;
|
||||
}
|
||||
m_nSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
string类中这两个函数的实现模式可以在其他类中直接套用,只需要改动Copy和Clear()函数即可。
|
||||
|
||||
===== 3 总结 =====
|
||||
|
||||
作为c++程序员每天都要和类、对象以及内存打交道,写一个类实现某项功能不难,但要实现一个健壮的、可重用的、易扩展的类就不是很容易了。很多时候我们写一个类时用的还是c的思维,对类的**四个基本函数考虑**的不够周到仔细,对类对象在不同内存区域运行特点理解不够,容易产生一些低级的bug,而且对后续的代码维护扩展也带来难度。本文中对这些内容做了基本的介绍,希望对大家有些帮助。
|
||||
63
Zim/Programme/C++/拷贝构造函数调用的时机.txt
Normal file
@@ -0,0 +1,63 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-11-01T16:31:06+08:00
|
||||
|
||||
====== 拷贝构造函数调用的时机 ======
|
||||
Created Tuesday 01 November 2011
|
||||
http://www.4ucode.com/Study/Topic/1404959
|
||||
|
||||
拷贝函数何时会被调用呢?
|
||||
|
||||
===== (1) =====
|
||||
最明显的就是用一个类对象初始化另外一个对象的时候。
|
||||
比如X a=X(); 这句语义上就是先创建X()**临时对象**,再调用X的拷贝构造函数“初始化”a,
|
||||
这只是__语义__上的,编译器完全__可能优化掉__临时对象 。注意区分初始化和赋值(调用赋值运算符:assignment operator)
|
||||
|
||||
===== (2) =====
|
||||
第二种情况是函数按值传参数的时候,包括指针在内都是对原有的值的拷贝 。
|
||||
|
||||
===== (3) =====
|
||||
第三种情况是一个对象以__值传递__的方式从函数返回。
|
||||
|
||||
为什么要这个时候会调用拷贝构造函数呢?因为在函数内, 所有的变量都是在函数的栈上的,包括那个参数,等函数完了以后都会销毁所以就必须给它**拷贝一份传回**。 以下面代码为例,我们看看编译器是如何将对象以值传递方式从函数返回的。
|
||||
|
||||
Given a function that returns a class object by value, such as the Matrix addition operator:
|
||||
Matrix operator+( const Matrix &m1, const Matrix &m2)
|
||||
{
|
||||
Matrix sum;
|
||||
// default Matrix constructor applied
|
||||
// do the math
|
||||
return sum;
|
||||
}
|
||||
the function is generally transformed internally into
|
||||
// Psuedo C++ Code:
|
||||
// internal transformation of function
|
||||
void operator+( **Matrix & _retvalue**, const Matrix &m1, const Matrix &m2)
|
||||
{
|
||||
Matrix sum;
|
||||
// invoke default constructor
|
||||
sum.Matrix::Matrix();
|
||||
// do the math
|
||||
// copy construct sum into return value
|
||||
_retvalue.Matrix::Matrix( sum );
|
||||
}
|
||||
|
||||
1.首先声明一个__额外的参数__,类型上类对象的引用,用来存放返回结果,本例中为&_retvalue, 注意,该参数位于调用函数的栈中。
|
||||
2.对这个参数利用返回值进行__拷贝初始化__。过程类似于参数传递,也是要定义一个临时对象sum,用来保存返回值,然后在函数内部调用拷贝构造函数将_retvalue初始化。
|
||||
|
||||
有时编译器为了减少构造析构的次数,会把事先创建好的对象的引用传进来,然后对其修改,这就是所谓的__NRV__(Named Return Value (NRV) optimization,具名返回值优化)。仍以上面代码为例,上述代码可能转换为
|
||||
void operator+( Matrix &_retvalue, const Matrix &m1, const Matrix &m2)
|
||||
{
|
||||
//default constructor invocation
|
||||
__retvalue.Matrix ::Matrix ();
|
||||
// ... process in __retvaluedirectly
|
||||
return;
|
||||
}
|
||||
|
||||
NRV优化的本质是__优化掉拷贝构造函数__,去掉它不是生成它。当然了,因为为了优化掉它,前提就是它存在,这个也就是nrv优化需要有拷贝构造函数存在的原因。 nrv优化会带来副作用,不用它的确造成很大的性能损失,知道这个情况就可以了。
|
||||
|
||||
===== (4) =====
|
||||
情况是非空顺序容器类型的定义,例如vector<string> svec(5); string缺省构造函数创建一个临时对象,然后通过string拷贝构造函数将该临时对象被一次拷贝到vector的五个元素中。
|
||||
|
||||
===== (5) =====
|
||||
情况是将一个类对象插入到容器类型中,例如svec.push_back(string("sfsd"));
|
||||
50
Zim/Programme/C++/拷贝构造函数调用的时机/2.txt
Normal file
@@ -0,0 +1,50 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-06-11T15:36:38+08:00
|
||||
|
||||
====== 2 ======
|
||||
Created Monday 11 June 2012
|
||||
|
||||
------------------ Original ------------------
|
||||
From: "张俊师兄";
|
||||
Date: 2011年11月1日(星期二) 下午4:25
|
||||
To: "534243850"<534243850@qq.com>;
|
||||
Subject: 回复:C++问题请教
|
||||
|
||||
我将你的例子改了一下,这样看的更清楚。
|
||||
以前我对构造函数的理解也不太深刻,中午讨论时没有说清楚。刚才看了一些资料,总结如下:
|
||||
1. 如果一个函数返回一个类对象,在返回前(即函数的return语句执行前)编译器会调用复制构造函数生成一个临时对象,然后析构函数中的栈对象。如:
|
||||
A fool(int x, int y)
|
||||
{
|
||||
//生成对象a,它位于函数fool的栈中。
|
||||
A a(x, y);
|
||||
//由于函数fool返回后栈将释放(这意味着外界不能再访问a对象),所以编译器会调用复制构造函数生成一个临时对象。
|
||||
return a;
|
||||
}
|
||||
|
||||
编译器在编译这个函数时内部使用的形式为:
|
||||
A fool (A& _retval, int x, int y) //这里的_retval即为返回的临时对象的引用(编译器自动添加)
|
||||
{
|
||||
a.A::A(x, y);
|
||||
_retval.A::A(a); //调用复制构造函数生成临时对象
|
||||
a.A::~A(); //析构栈上的对象a
|
||||
}
|
||||
|
||||
2. 编译器为了减少复制构造函数调用的次数,会对相关的代码进行优化(这就是中午我们运行结果中没有看到复制构造函数的原因)。例如,编译器会对上面的函数进行如下的优化:
|
||||
A fool(A& _retval, int x, int y)
|
||||
{
|
||||
_retval.A::A(x, y);
|
||||
return ;
|
||||
}
|
||||
可以看出,优化后的函数会删除生成栈对象a的代码并且直接返回一个临时对象,这就是所谓的NRV(Named Return Value (NRV) optimization,具名返回值优化)。
|
||||
|
||||
3.附件中有两份运行结果,标有VS2010的是没有编译优化的程序运行的结果(我们可以详细地看到复制构造函数的调用过程),另一份是用g++编译时默认优化的结果(编译器对源代码进行了如2过程的优化,这时将看不到复制构造函数的调用)。
|
||||
|
||||
4.还有一个现象需要注意,源代码中的这一句(44行):
|
||||
A b = f('b');
|
||||
f('b')返回一个临时对象,按说这一行代码会调用复制构造函数生成对象b, 但是在class-demo-VS2010的结果中,我们并没有看到这一过程。这可能也是一个编译器优化的结果:因为生成的临时对象和将要生成的对象b都位于main()的栈中,所以就没必要在将临时对象析构前调用复制构造函数生成一个完全一样的对象b,编译器会直接将临时对象与b关联。
|
||||
|
||||
That' all !
|
||||
参考:
|
||||
1.拷贝构造函数调用的时机 http://www.4ucode.com/Study/Topic/1404959
|
||||
2.拷贝构造函数调用总结 http://www.cnblogs.com/howareyou586/archive/2008/11/21/1338273.html
|
||||
36
Zim/Programme/C++/指针和引用.txt
Normal file
@@ -0,0 +1,36 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T15:17:03+08:00
|
||||
|
||||
====== 指针和引用 ======
|
||||
Created Saturday 06 August 2011
|
||||
|
||||
★ 相同点: 经典评论。
|
||||
1. 都是地址的概念;
|
||||
指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的__别名__。
|
||||
|
||||
★ 区别:
|
||||
1. 指针是一个实体,而引用仅是个别名;
|
||||
2. 引用使用时无需__解引用__(*),指针需要解引用;
|
||||
3. 引用只能在定义时被__初始化一次,之后不可变;指针可变__;
|
||||
4. 引用没有 const,指针有 const;
|
||||
5.__ 引用不能为空__,指针可以为空;
|
||||
eg. int &a; //这是错误的,引用在定义时必须初始化。
|
||||
|
||||
6. “sizeof 引用”得到的是__所指向的变量(对象)的大小__,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
|
||||
7. 指针和引用的自增(++)运算意义不一样;
|
||||
8.从内存分配上看:程序为指针变量分配内存区域,而__引用不需要分配内存区域__。
|
||||
|
||||
所以说:__引用不是一个变量而只是一个别名__,声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身__不是一种数据类型__,因此引用本身不占存储单元,系统也不给引用分配存储单元。故:对引用求地址,就是对目标变量求地址。&ra与&a相等。。
|
||||
|
||||
引用的创建和销毁并__不会__调用类的拷贝构造函数
|
||||
|
||||
如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为__指针__,因为这样你可以赋空值给该变量。相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明为引用。
|
||||
|
||||
__不存在指向空值的引用__这个事实意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。
|
||||
|
||||
|
||||
总的来说,在以下情况下你应该使用指针,一是你考虑到存在不指向任何对象的可能(在这种情况下,你能够设置指针为空),二是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么你应该使用引用。
|
||||
还有一种情况,就是当你重载某个操作符时,你应该使用引用。最普通的例子是操作符[]。这个操作符典型的用法是__返回一个目标对象,其能被赋值__。
|
||||
|
||||
|
||||
93
Zim/Programme/C++/指针和引用/深入探讨C++中的引用.txt
Normal file
@@ -0,0 +1,93 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T15:34:28+08:00
|
||||
|
||||
====== 深入探讨C++中的引用 ======
|
||||
Created Saturday 06 August 2011
|
||||
http://bbs.xiakexing.com/cgi-bin/topic.cgi?forum=22&topic=268
|
||||
|
||||
引用是C++引入的新语言特性,是C++常用的一个重要内容之一,正确、灵活地使用引用,可以使程序简洁、高效。我在工作中发现,许多人使用它仅仅是想当然,在某些微妙的场合,很容易出错,究其原由,大多因为没有搞清本源。故在本篇中我将对引用进行详细讨论,希望对大家更好地理解和使用引用起到抛砖引玉的作用。
|
||||
|
||||
==== 引用简介 ====
|
||||
|
||||
引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。
|
||||
|
||||
引用的声明方法:类型标识符 **&引用名**=目标变量名;
|
||||
|
||||
【例1】:int a; int &ra=a; //定义引用ra,它是变量a的引用,即别名
|
||||
|
||||
说明:
|
||||
|
||||
(1)&在此不是求地址运算,而是起标识作用。
|
||||
(2)类型标识符是指目标变量的类型。
|
||||
(3)声明引用时,必须同时对其进行初始化。
|
||||
(4)引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,且不能再把该引用名作为其他变量名的别名。
|
||||
|
||||
ra=1; 等价于 a=1;
|
||||
|
||||
(5)声明一个引用,__不是新定义了一个变量__,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。故:对引用求地址,就是对目标变量求地址。&ra与&a相等。
|
||||
(6)__不能建立数组的引用__。因为数组是一个由若干个元素所组成的集合,所以无法建立一个数组的别名。
|
||||
|
||||
==== 引用应用 ====
|
||||
|
||||
==== 1、引用作为参数 ====
|
||||
|
||||
引用的一个重要作用就是作为函数的参数。以前的C语言中函数参数传递是值传递,如果有大块数据作为参数传递的时候,采用的方案往往是指针,因为这样可以避免将整块数据全部压栈,可以提高程序的效率。但是现在(C++中)又增加了一种同样有效率的选择(在某些特殊情况下又是必须的选择),就是引用。
|
||||
|
||||
【例2】:
|
||||
|
||||
void swap(int &p1, int &p2) //此处函数的形参p1, p2都是引用
|
||||
{ int p; p=p1; p1=p2; p2=p; }
|
||||
|
||||
为在程序中调用该函数,则相应的主调函数的调用点处,直接以变量作为实参进行调用即可,而不需要实参变量有任何的特殊要求。如:对应上面定义的swap函数,相应的主调函数可写为:
|
||||
|
||||
main( )
|
||||
{
|
||||
int a,b;
|
||||
cin>>a>>b; //输入a,b两变量的值
|
||||
swap(a,b); //直接以变量a和b作为实参调用swap函数
|
||||
cout<<a<< ' ' <<b; //输出结果
|
||||
}
|
||||
|
||||
上述程序运行时,如果输入数据10 20并回车后,则输出结果为20 10。
|
||||
|
||||
由【例2】可看出:
|
||||
|
||||
(1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
|
||||
(2)使用引用传递函数的参数,在内存中并__没有产生实参的副本__,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;__如果传递的是对象,还将调用拷贝构造函数__。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
|
||||
(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
|
||||
|
||||
如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用__常引用__。
|
||||
|
||||
==== 2、常引用 ====
|
||||
|
||||
常引用声明方式:const 类型标识符 &引用名=目标变量名;
|
||||
|
||||
用这种方式声明的引用,__不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到了引用的安全性。__
|
||||
|
||||
【例3】:
|
||||
|
||||
int a ;
|
||||
const int &ra=a;
|
||||
ra=1; //错误
|
||||
a=1; //正确
|
||||
|
||||
这不光是让代码更健壮,也有些其它方面的需要。
|
||||
|
||||
【例4】:假设有如下函数声明:
|
||||
|
||||
string foo( ); __ //注意:这并不是利用string类的缺省构造函数创建一个string类型的对像foot,而是声明了一个返回值为string类型的对像函数名为foot的函数原型。__
|
||||
__如果要创建一个string类型的对像foo则必须去掉后面的双括号。__
|
||||
|
||||
void bar(string & s);
|
||||
|
||||
那么下面的表达式将是非法的:
|
||||
|
||||
bar(foo( ));
|
||||
bar("hello world");
|
||||
|
||||
原因在于foo( )和"hello world"串都会产生一个临时对象,__而在C++中,这些临时对象都是const类型的__。因此上面的表达式就是__试图将一个const类型的对象转换为非const类型,这是非法的。也就是说,对临时对像的引用是非法的。__
|
||||
|
||||
引用型参数应该在能被定义为const的情况下,尽量定义为const 。
|
||||
|
||||
|
||||
105
Zim/Programme/C++/指针和引用/谈谈_C++_的引用.txt
Normal file
@@ -0,0 +1,105 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-08-06T15:27:28+08:00
|
||||
|
||||
====== 谈谈 C++ 的引用 ======
|
||||
Created Saturday 06 August 2011
|
||||
|
||||
谈谈 C++ 的引用 作者姓名:tide
|
||||
作者主页:http://tide999.myrice.com/
|
||||
|
||||
引用(reference)是c++的初学者比较容易迷惑的概念。下面我们比较详细地讨论引用。
|
||||
|
||||
===== 一、引用的概念 =====
|
||||
|
||||
引用引入了对象的一个同义词。定义引用的表示方法与定义指针相似,只是用&代替了*。
|
||||
例如: Point pt1(10,10);
|
||||
Point &pt2=pt1; 定义了pt2为pt1的引用。通过这样的定义,**pt1和pt2表示同一对象**。
|
||||
需要特别强调的是__引用并不产生对象的副本__,仅仅是对象的同义词。因此,当下面的语句执行后:
|
||||
pt1.offset(2,2);
|
||||
pt1和pt2都具有(12,12)的值。
|
||||
__引用必须在定义时马上被初始化__,因为它必须是某个东西的同义词。你不能先定义一个引用后才
|
||||
初始化它。例如下面语句是非法的:
|
||||
Point &pt3;
|
||||
pt3=pt1;
|
||||
那么既然引用只是某个东西的同义词,它有什么用途呢?
|
||||
下面讨论引用的两个主要用途:作为函数参数以及从函数中返回左值。
|
||||
|
||||
===== 二、引用参数 =====
|
||||
|
||||
==== 1、传递可变参数 ====
|
||||
传统的c中,函数在调用时参数是通过值来传递的,这就是说函数的参数不具备返回值的能力。
|
||||
所以在传统的c中,如果需要函数的参数具有返回值的能力,往往是通过指针来实现的。比如,实现
|
||||
两整数变量值交换的c程序如下:
|
||||
void swapint(int *a,int *b)
|
||||
{
|
||||
int temp;
|
||||
temp=*a;
|
||||
a=*b;
|
||||
*b=temp;
|
||||
}
|
||||
|
||||
使用引用机制后,以上程序的c++版本为:
|
||||
void swapint(int &a,int &b)
|
||||
{
|
||||
int temp;
|
||||
temp=a;
|
||||
a=b;
|
||||
b=temp;
|
||||
}
|
||||
调用该函数的c++方法为:swapint(x,y); c++自动把x,y的地址作为参数传递给swapint函数。
|
||||
|
||||
==== 2、给函数传递大型对象 ====
|
||||
|
||||
当大型对象被传递给函数时,使用引用参数可使参数传递效率得到提高,因为引用并不产生对象的
|
||||
副本,也就是参数传递时,__对象无须复制__。下面的例子定义了一个有限整数集合的类:
|
||||
const maxCard=100;
|
||||
Class Set
|
||||
{
|
||||
int elems[maxCard]; // 集和中的元素,maxCard 表示集合中元素个数的最大值。
|
||||
int card; // 集合中元素的个数。
|
||||
public:
|
||||
Set () {card=0;} //构造函数
|
||||
__friend Set operator * (Set ,Set ) ; __//重载运算符号*,用于计算集合的交集 用对象作为传值参数
|
||||
// friend Set operator * (Set & ,Set & ) 重载运算符号*,用于计算集合的交集 用对象的引用作为传值参数
|
||||
...
|
||||
}
|
||||
先考虑集合交集的实现
|
||||
Set operator *( Set Set1,Set Set2)
|
||||
{
|
||||
Set res;
|
||||
for(int i=0;i<Set1.card;++i)
|
||||
for(int j=0;j>Set2.card;++j)
|
||||
if(Set1.elems[i]==Set2.elems[j])
|
||||
{
|
||||
res.elems[res.card++]=Set1.elems[i];
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
由于重载运算符不能对指针单独操作,我们必须把运算数声明为 Set 类型而不是 Set * 。
|
||||
|
||||
|
||||
每次使用*做交集运算时,整个集合都被复制,这样效率很低。我们可以用引用来避免这种情况。
|
||||
Set operator *( Set &Set1,Set &Set2)
|
||||
{ Set res;
|
||||
for(int i=0;i<Set1.card;++i)
|
||||
for(int j=0;j>Set2.card;++j)
|
||||
if(Set1.elems[i]==Set2.elems[j])
|
||||
{
|
||||
res.elems[res.card++]=Set1.elems[i];
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
==== 三、引用返回值 ====
|
||||
|
||||
__如果一个函数返回了引用,那么该函数的调用也可以被赋值。__这里有一函数,它拥有两个引用参数并返回一个双精度数的引用:
|
||||
double &max(double &d1,double &d2)
|
||||
{
|
||||
return d1>d2?d1:d2;
|
||||
}
|
||||
|
||||
由于max()函数返回一个对双精度数的引用,那么我们就可以用max() 来对其中较大的双精度数加1:
|
||||
max(x,y)+=1.0;
|
||||
7
Zim/Programme/C++/文件IO.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-17T16:14:37+08:00
|
||||
|
||||
====== 文件IO ======
|
||||
Created Friday 17 February 2012
|
||||
|
||||
766
Zim/Programme/C++/文件IO/C++文件IO.txt
Normal file
@@ -0,0 +1,766 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-17T16:14:48+08:00
|
||||
|
||||
====== C++文件IO ======
|
||||
Created Friday 17 February 2012
|
||||
|
||||
http://blog.cfan.com.cn/html/49/302849-1729970.html
|
||||
|
||||
虽然C++的I/O方法形成了一个完整的系统,但文件I/O(特别是磁盘文件I/O)由于
|
||||
受到本身的限制和特性,因而被当作一种特殊情况专门讲述。因为最普通的文件是磁盘文
|
||||
件,而磁盘文件具有其它设备不具有的性能和特征。但要记住,__磁盘文件I/O只是一般I/O__
|
||||
__系统的一个特例__,且本章讨论的大多数材料也适用于与其它类型的设备相连的流。
|
||||
|
||||
===== 18.1 fstream.h 和文件类 =====
|
||||
要处理文件I/O,程序中必须包含首标文件fstream.h。它定义了的类包括**ifstream、of-**
|
||||
**stream和fstream**。这些类分别从istream和ostream派生而来。记注,istream和ostream是从
|
||||
ios派生来的,所以ifstream、ofstream和fstream也存取ios定义的所有运算。
|
||||
|
||||
===== 18.2 打开和关闭文件 =====
|
||||
在C++里,用户__通过把文件和流联系起来打开文件__。打开文件之前,必须首先获得一个
|
||||
流。流分为三类:**输入、输出和输入/输出**。要创建一个输入流,必须说明它为类ifstream;要
|
||||
创建一个输出流,必须说明它为类ofstream。执行输入和输出操作的流必须说明为类
|
||||
fstream。例如,下面的程序段创建了一个输入流、一个输出流和一个输入/输出流。
|
||||
|
||||
ifstream in; // input
|
||||
ofstream out; // output
|
||||
__fstream__ io;// input and output
|
||||
|
||||
一旦创建了一个流,把流和文件联系起来的一种方法就是使用函数**open()**。该函数是
|
||||
这三个类中每个类的成员。其原型为:
|
||||
void open(__const char *__ filename, int mode, int access=filebuf::openprot);
|
||||
其中,filename为文件名,它可以包含路径说明符。mode值决定文件打开的方式,它必须是
|
||||
下列值中的一个(或多个):
|
||||
ios::app
|
||||
ios::ate
|
||||
ios::binary
|
||||
ios::in
|
||||
ios::nocreate
|
||||
ios::noreplace
|
||||
ios::out
|
||||
ios::trunc
|
||||
|
||||
用户可以把两个或两个以上的值__或在__一起得到它们的复合值。下面看看这些值的含义。
|
||||
* 包含ios::app 导致把所有文件的输出添加在**文件尾**。它只能用于输出文件。包含ios::
|
||||
ate导致文件打开时定位于文件尾。虽然如此,在文件的任何位置都可以进行I/O操作。
|
||||
* 缺省时,文件以__文本方式打开__。使用ios::binary值可以使文件以二进制方式打开。文件
|
||||
以文本方式打开时,会产生不同的字符转换。如**回车/换行转换为换行**。但当文件以二进制方
|
||||
式打开时,__不发生字符转换__。任何文件,不管是包含格式化的文本还是包含原始的二进制数
|
||||
据,均可以文件方式或二进制方式打开,唯一不同的是是否进行字符转换。
|
||||
* ios::in值说明文件有输入能力,ios::out值说明文件有输出能力。用ifstream创建的流
|
||||
隐含为输入,用ofstream创建的流**隐含为输出**。在这些情况下,没有必要提供这些值。
|
||||
* 包含ios::nocreate导致函数open()在文件不存在时失败。ios::noreplace值导致函数
|
||||
open()在文件存在时失败。
|
||||
* ios::trunc值导致已存在的同名文件的内容被破坏且长度被截断为0。
|
||||
注:建议的ANSI C++标准规定方式参数类型为openmode,通常为整型。现在大多数工
|
||||
具简单地将方式参数定义为整型数。
|
||||
|
||||
access值决定**存取文件的方式**,其缺省值为filebuf::openprot,指定为通常文件(filebuf
|
||||
是由streambuf派生的类)。大多数时候允许access缺省。参阅编译程序手册,找出自己所用
|
||||
操作环境中该参数的其它选项。例如,文件共享选项一般定义为在网络环境下使用的access
|
||||
参数。
|
||||
|
||||
下面的程序段打开一个普通输出文件:
|
||||
ofstream out;
|
||||
out.open(“test”, ios::out);
|
||||
由于一般情况下使用的是mode缺省值,所以很少象上面这样调用open()。对于ifstream,
|
||||
mode的缺省值是ios::in;对于ofstream它是ios::out。所以,上面的语句通常表现如下:
|
||||
out.open("test"); // defaults to output and normal file
|
||||
|
||||
如下例所示,要打开一个**供输入和输出的流**,就必须指定mode的值为ios::in和ios::
|
||||
out(无缺省值):
|
||||
fstream mystream;
|
||||
mystream.open("test",__ios::in | ios::out__);
|
||||
|
||||
如果open()失败,则**mystream为0**。所以在使用一个文件之前,应使用如下语句进行测
|
||||
试,以__确保打开操作成功__。
|
||||
if(!mystream){
|
||||
cout<<"Cannot open file.\N";
|
||||
// handle error
|
||||
}
|
||||
|
||||
虽然使用函数open()打开一个文件完全合适,但在大多数情况下,由于ifstream、of-
|
||||
stream和fstream类包含**自动打开文件的构造函数**,所以没有必要调用open()。构造函数有
|
||||
和open()相同的参数和缺省值。最常见的打开文件的方法是:
|
||||
|
||||
ifstream mystream("myfile");// open file for input
|
||||
如前所述,如果由于某种原因不能打开文件,则关于流的变量的值是0。所以,不管用构造函
|
||||
数还是显式地调用open()打开文件,都要测试流的值以保证真正打开了文件。
|
||||
|
||||
要关闭一个文件,使用成员函数close()。例如,用下面的语句关闭关于流mystream的
|
||||
文件:
|
||||
mystream. close();
|
||||
函数close()不带任何参数且无返回值。
|
||||
|
||||
==== 18.3读和写文本文件 ====
|
||||
|
||||
从文本文件读或向文本文件写都非常容易,只要使用在处理控制台I/O时使用的运算
|
||||
符__<<和>>__,并用和文件相关的流代替cin和cout即可。例如,下面的程序建立了一个帐
|
||||
单,它包括每个项目的名称和开支情况。
|
||||
#include<iostream.h>
|
||||
#include<fstream.h>
|
||||
main()
|
||||
{
|
||||
ofstream out("INVNTRY");// output, normal file
|
||||
if(!out){
|
||||
cout<<"Cannot open INVENTORY file.\n";
|
||||
return 1;
|
||||
}
|
||||
out<<"Radios" <<**39.95**<<endl;
|
||||
out<<"Toasters"<<19.95 <<endl;
|
||||
out<<"Mixers" <<24.80<< endl;
|
||||
out.close();
|
||||
return 0;
|
||||
}
|
||||
下面的程序读上面的程序中创建的帐目文件并在屏幕上显示其内容:
|
||||
#include<iostream.h>
|
||||
#include<fstream.h>
|
||||
main()
|
||||
{
|
||||
ifstream in("INVNTRY");// input
|
||||
if(!in){
|
||||
cout<<"Cannot open INVENTORY file.\n";
|
||||
return 1;
|
||||
}
|
||||
__char __item[20];
|
||||
float cost;
|
||||
|
||||
in>>item>>cost;
|
||||
cout<<item<<" "<<cost<<"\n";
|
||||
in>>item>>cost;
|
||||
cout<<item<<" "<<cost<<"\n";
|
||||
in>> item>>cost;
|
||||
cout<<item<<" "<<cost<<"\n";
|
||||
in.close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
从某种意义上讲,使用>>和<<读写文件类似于使用C的函数fprintf()和fscanf()。
|
||||
所有的信息都按屏幕显示的格式存入文件。
|
||||
|
||||
下面是磁盘文件I/O的又一个例子。该程序读取从键盘输入的字符串并写入磁盘。程序
|
||||
在用户输入空行时结束。使用该程序时,要在命令行中指定输出文件名。
|
||||
#include<iostream.h>
|
||||
#include<fstream.h>
|
||||
#include<stdio.h>
|
||||
main(int argc,char * argv[])
|
||||
{
|
||||
if(argc!=2){
|
||||
cout <<"Usage: output<filename>\n";
|
||||
return 1;
|
||||
}
|
||||
ofstream out(argv[C]);// output, normal file
|
||||
if(!out){
|
||||
cout<<"Cannot open output file.\n";
|
||||
return 1;
|
||||
}
|
||||
char str[80];
|
||||
cout<<"Write strings to disk, RETURN to stop.\n";
|
||||
do{
|
||||
cout <<":";
|
||||
gets(str);
|
||||
out<<str<<endl;
|
||||
}while(*str);
|
||||
out.close();
|
||||
return 0;
|
||||
}
|
||||
用运算符>>读文本文件时,将发生__某些字符转换__,如省略空格等。如果要防止任何字
|
||||
符转换,就必须使用C++的二进制I/O函数,我们将在下一节讨论它。输入时如果遇到文件尾,
|
||||
和文件关联的流则为0。
|
||||
|
||||
|
||||
===== 18.4二进制I/O =====
|
||||
向一个文件写和从一个文件读有两种方法,下面就介绍这两种方法。
|
||||
记住:如果要对一个文件进行二进制操作,则必须使用ios::binary方式说明符打开文
|
||||
件。虽然二进制文件函数可以对以文本方式打开的文件进行操作,但可能发生一些字符转
|
||||
换,字符转换会违背二进制文件操作的目的。
|
||||
|
||||
==== 18.4.1 put()和get() ====
|
||||
读写二进制文件的一种方法使用成员函数get()和put()。这些函数以__字节为单位__进行
|
||||
操作。也就是说,get()可以读一个字节数据,put()可以写一字节数据。函数get()有很多形
|
||||
式,但最常用的形式和与之伴随的put()如下所示:
|
||||
istream&get(char &ch);
|
||||
ostream&put(char ch);
|
||||
函数get()从相关的流读一个字符并放入ch,返回对流的引用。函数put()将*写入流
|
||||
并返回流的引用。
|
||||
|
||||
下面的程序在屏幕上显示任何文件的内容,它用到了函数get()。
|
||||
#include <iostream.h>
|
||||
#include<fstream.h>
|
||||
main(int argc, char * argv[])
|
||||
{
|
||||
__char__ ch;
|
||||
if(argc!=2)
|
||||
cout<<"Usage:PR<filename>\n";
|
||||
return 1;
|
||||
}
|
||||
ifstream in(argv[1]);
|
||||
if(!in){
|
||||
cout <<"Cannot open file.\n";
|
||||
return 1;
|
||||
}
|
||||
while(in){//in will be 0 when __eof__ is reached
|
||||
in.get(ch);
|
||||
cout<<ch;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
前面提到过,当到达文件尾时,和文件相关的流变为0。所以,当in到达文件尾时它是0,从而终止while循环。
|
||||
下面给出的这种编码方法使读和显示文件的循环的编码更短:
|
||||
while(in.get(ch))
|
||||
cout << ch;
|
||||
由于get()返回流in的引用,且在遇到文件尾时in为0,所以上述语句能正常工作。
|
||||
|
||||
下面的程序用put()向文件CHARS写入从0到255的所有字符。ASCII字符只占char所
|
||||
容纳的值的一半,其余值一般称为扩展字符值,包括一些外来语和数学符号(并非所有的系
|
||||
统都支持扩展字符集)。
|
||||
#include<iostream.h>
|
||||
#include <fstream. h>
|
||||
main()
|
||||
{
|
||||
int i;
|
||||
ofstream out("CHARS",ios::out | ios::binary);
|
||||
if(!out){
|
||||
cout << "Cannot open output file.\n";
|
||||
return 1;
|
||||
}
|
||||
// write all characters to disk
|
||||
for(i=0; i<256; i++)out.put(char(i));
|
||||
out.close();
|
||||
return 0;
|
||||
}
|
||||
检查一下文件CHARS的内容,看一看自己的计算机有什么扩展字符,是一件很有趣的
|
||||
事情。
|
||||
|
||||
==== 18.4.2 read()和write() ====
|
||||
第二种方法是使用C++的函数read()和write()读写__二进制数据块__,其原型为:
|
||||
istream &read(unsigned char *buf, int num);
|
||||
ostream &write(const unsigned char *buf, int num);
|
||||
函数read() 从相关的流读num个字节并放入buf所指的缓冲区。函数write()把buf所
|
||||
指的缓冲区中的num个字节写入相关的流。
|
||||
注:建议的ANSI C++标准用__streamsize__ 说明num参数的类型,streamsize是整数类型
|
||||
的typedef。如前面的原型所示,大多数C++编译程序简单地将num定义为整型。一般情况
|
||||
下,建议的ANSI C++标准用streamsize作为对象的类型,这些对象说明在输入/输出操作
|
||||
中传送的字节数。
|
||||
|
||||
下面的程序先把一个结构写入磁盘,然后读回:
|
||||
|
||||
#include<iostream.h>
|
||||
#include<fstream.h>
|
||||
#include<string.h>
|
||||
struct status{
|
||||
char name[80];
|
||||
float balance;
|
||||
unsigned long account_num;
|
||||
};
|
||||
main()
|
||||
{
|
||||
struct status acc;
|
||||
__strcpy__(acc.name,"Ralph Trantor");
|
||||
acc.balance=1123.23;
|
||||
acc.account_num=34235678;
|
||||
ofstream outbal("balance",ios::out | ios:: binary);
|
||||
if(!outbal){
|
||||
cout <<"Cannot open file.\n";
|
||||
return 1;
|
||||
}
|
||||
outbal.write(__(unsigned char*)__&acc, sizeof(struct status));
|
||||
outbal.close();
|
||||
// now, readback
|
||||
ifstream inbal("balancen",ios:: | ios:: binary);
|
||||
if(!inbal){
|
||||
cout <<"Cannot open file.\n";
|
||||
return 1;
|
||||
}
|
||||
inbal.read(__(unsigned char*)__&acc, sizeof(struct status));
|
||||
cout <<acc.name <<endl;
|
||||
cout<<"Account # ", <<acc.account_num;
|
||||
cout.__precision__(2);
|
||||
cout. __setf__(ios::fixed);
|
||||
cout<<ena<<" Balance :$" <<acc.balance;
|
||||
inbal.close();
|
||||
return 0;
|
||||
}
|
||||
可以看出,读写整个结构只需调用一次read()和write(),而不必分别读写结构中的每
|
||||
个域。如本例所示,缓冲区可以是任何类型的对象。
|
||||
|
||||
注:当对不是定义为字符数组的缓冲区进行操作时,在调用read()和write()中进行类
|
||||
型强制转换是必要的。由于C++有很强的类型检查能力,所以一种类型的指针不能自动转
|
||||
换成另一种类型的指针。如果还未读到num个字符就到达了文件尾,read()便终止,缓冲区包含了能获得的那
|
||||
些字符。用成员函数gcount()可以得到读取的字符的个数,其原型为:
|
||||
__ int gcount()__;
|
||||
它返回**最后一次**二进制输入操作的字符个数。
|
||||
下面的程序是使用read()和write()的另一个例子,它说明了gcount()的用法。
|
||||
#include <iostream.h>
|
||||
#include<fstream.h>
|
||||
main(void)
|
||||
{
|
||||
float fnum[4] ={99.75,-34.4, 1776.0,200.1};
|
||||
int i;
|
||||
ofstream out("numbers",ios::out | ios:: binary);
|
||||
if(!out){
|
||||
cout << "Cannot open file.";
|
||||
return 1;
|
||||
}
|
||||
out.write((unsigned char * )&fnum, sizeof(fnum);
|
||||
Out.close();
|
||||
for(i=0; i<4;i++)// clear array
|
||||
fnum[i]=0.0;
|
||||
ifstream in("numbers",ios::in | ios::binary);
|
||||
in. read((unsigned char*)&fnum, sizeof fnum);
|
||||
// see how many bytes have been read
|
||||
**cout << in. gcount()**<< "bytes read\n";
|
||||
for(i=0; i<4; i++) // show values read from file
|
||||
cout << fnum[i] <<" ";
|
||||
in.close();
|
||||
return 0;
|
||||
}
|
||||
上面的程序向磁盘写一个浮点值数组然后读回。调用read()之后,用gcount()确定刚才
|
||||
读回多少个字节。
|
||||
|
||||
==== 18.5另外的get()成员函数 ====
|
||||
除了前面所示的形式,还可以用几种不同的方法重载get()。最常见的两种重载形式为:
|
||||
istream&get(char*buf, int num, char delim=’\n’);
|
||||
int get();
|
||||
|
||||
第一种重载形式读取字符并放到buf所指的数组中,直到读满num-1个字符或遇到delim所指定的字符为止。
|
||||
get()使buf所指的数组__以空结束__。如果没指定delim参数,缺省的定界符就是换行符。__在输入流中碰到定界字符时,并不读取它__,而是继续留在流中直到下次读入
|
||||
操作。 get()的第二种重载形式返回流中的下一个字符,到达文件尾时返回EOF。这种形式的get()类似于C的函数getc()。
|
||||
|
||||
==== 18.6 getline()成员函数 ====
|
||||
执行输入的另一个成员函数是getline(),其原型为:
|
||||
istream&getline(char * buf, int num, char delim=’\n’);
|
||||
可以看出,这个函数实质上和get()的get(buf、num、delim)形式是一样的。它从输入流中读取字符到buf所指的数组中,直到读满num个字符或遇到delim所指定的字符为止。如果没指定delim,其缺省值就是换行符。buf所指的数组__以空结束__。get(buf、num、delim)和getline()的区别在于getline()__从输入流中读入并移去定界符__。
|
||||
|
||||
下面的程序介绍了函数getline(),它逐行读并显示一个文本文件。
|
||||
// Read and display a text file line by line.
|
||||
#include <iostream. h>
|
||||
#include<fstream.h>
|
||||
main(int argc, char * argv[])
|
||||
{
|
||||
if(argc!=2){
|
||||
cout <<"Usage: Display<filename>\n";
|
||||
return 1;
|
||||
}
|
||||
ifstream in(argv[1]);// input
|
||||
if(!in){
|
||||
cout <<"Cannot open input file.\n";
|
||||
return 1;}
|
||||
char str[255];
|
||||
while(in){
|
||||
in. getline(str, 255);// delim defaults to’\n’
|
||||
cout << str<<endl;
|
||||
}
|
||||
in.close();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
===== 18.7跟踪EOF =====
|
||||
使用成员函数eof()可以跟踪何时到达文件尾,其原型为:
|
||||
int eof();
|
||||
当到达文件尾时,eof()的返回值__不为0__,否则为0。
|
||||
注:建议的ANSI C++标准规定eof()返回值类型为布尔型。但目前大多数C++编译程
|
||||
序还不支持布尔数据类型。实际上,将eof()返回值说明为布尔类型还是整型无关紧要,因为
|
||||
在任何表达式中的布尔型可以自动转换为整型。
|
||||
|
||||
下面的程序以十六进制的ASCII形式显示一个文件的内容。
|
||||
/* Display contents of specified file
|
||||
in both ASCII and in hex.
|
||||
*/
|
||||
#include<iostream.h>
|
||||
#include<fstream.h>
|
||||
#include<ctype.h>
|
||||
#include <__iomanip.h__>
|
||||
#include<stdio.h>
|
||||
main(int argc,char * argv[])
|
||||
{
|
||||
if(argc!=2){
|
||||
cout << "Usage: Display <filename>\n";
|
||||
return 1;
|
||||
}
|
||||
ifstream in(argv[1], ios::in | ios::binary);
|
||||
if(!in){
|
||||
cout << "Cannot open input file.\n";
|
||||
return 1;
|
||||
}
|
||||
register int i, j;
|
||||
int count=0;
|
||||
char c[16];
|
||||
cout. __setf__(ios: : uppercase);
|
||||
while(!in. eof()){
|
||||
for(i=0; i<16&&!in.eof(); i++){
|
||||
in. get(c[i]);
|
||||
}
|
||||
if(i<16) i--;// get rid of eof
|
||||
for(j=0;j<i;j++)
|
||||
cout << setw(3) << __hex__ <<(int)c[j];
|
||||
for(;j<16;j++) cout<<" " ;
|
||||
|
||||
cout<<"\t";
|
||||
for(j=0;j<i;j++)
|
||||
if(**isprint**(c[j])) cout<<c[j];
|
||||
else __cout <<"."__;
|
||||
cout <<endl;
|
||||
count++;
|
||||
if(count==16){
|
||||
count=0;
|
||||
cout<<"Press __ENTER__ to continue:";
|
||||
cin.get();
|
||||
cout<<endl;
|
||||
}
|
||||
}
|
||||
in.close();
|
||||
return 0;
|
||||
}
|
||||
如果用这个程序显示它自己的话,则第一屏内容为:
|
||||
2F 2A 20 44 69 73 70 6C 61 79 20 63 6F 6E 74 65 / * Display conte
|
||||
6E 74 73 20 6F 66 20 73 70 65 63 69 66 69 65 64 nts of specified
|
||||
20 66 69 6C 65 D A 20 20 20 69 6E 20 62 6F 74 file. . in bot
|
||||
68 20 41 53 43 49 49 20 616E 64 20 69 6E 20 68 h ASCII and in h
|
||||
65 78 2E D A 2A 2F D A 23 69 6E 63 6C 75 64 ex. . */ . . # includ
|
||||
65 20 3C 69 6F 73 74 72 65 61 6D 2E 68 3E D A e <iostream. h>. .
|
||||
23 69 6E 63 6C 75 64 65 20 3C 66 73 74 72 65 61 # Include<fstrea
|
||||
6D 2E 68 3E D A 23 69 6E 63 6C 75 64 65 20 3C m. h>. . # include<
|
||||
63 74 79 70 65 2E 68 3E D A 23 69 6E 63 6C 75 ctype. h>. . # inclu
|
||||
64 65 20 3C 69 6F 6D 61 6E 69 70 2E 68 3E D A de <iomanip. h>. .
|
||||
23 69 6E 63 6C 75 64 65 20 3C 73 74 64 69 6F 2E # include<stdio.
|
||||
68 3E D A D A 6D 6l 69 6E 28 69 6E 74 20 61 h>. . . . main(int a
|
||||
72 67 63 2C 206368 61 72 20 2A 61 72 67 76 5B rsc, char * argv[
|
||||
5D 29 D A 780A20 20 69 66 28 61 72 67 63]).{.. if(argc
|
||||
21 3D 32 29 20 78DA20 20 20 20 63 6F 75 74 1=2){. . cout
|
||||
20 3C 3C 20 22 55 73 6167 65 3A 2044 69 73 70 <<"Usage: Disp
|
||||
Press ENTER to continue:
|
||||
|
||||
===== 18.8ignore()函数 =====
|
||||
使用成员函数ignore()可以从输入流中读取并丢弃字符,其原型为:
|
||||
istream &ignore(int num=1, int delim=EOF);
|
||||
它读取并丢弃字符直到忽略num个(缺省为一个)字符或遇到由delim指定的字符(缺省为EOF)为止。如果遇到了定界字符,__不从输入流中移去它__。
|
||||
下面的程序读文件TEST。它忽略字符直到遇到空格或读完10个字符,然后显示文件的 剩余部分。
|
||||
#include <iostream. h>
|
||||
#include<fstream.h>
|
||||
main()
|
||||
{
|
||||
ifstream in("test");
|
||||
if(!in){
|
||||
cout << "Cannot open file.\n";
|
||||
return 1;
|
||||
}
|
||||
/* Ignore up to 10 characters or until first
|
||||
space is found. */
|
||||
in.ignore(10,’’);
|
||||
char c;
|
||||
while(in){
|
||||
in.get(c);
|
||||
cout<<c;
|
||||
}
|
||||
in.close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
===== 18.9 peek()和putback() =====
|
||||
使用peek()可以从输入流中获取下一个字符,但不移去它,其原型为:
|
||||
int peek();
|
||||
它返回流中的下一个字符或者到达文件尾时返回EOF。
|
||||
使用putback()可以获得从一个流中读取的最后一个字符,其原型为:
|
||||
istream&putback(char c);
|
||||
其中,c为读取的最后一个字符。
|
||||
|
||||
===== 18.10 flush() =====
|
||||
在执行输出时,并不立即将数据写入和流相关的物理设备,而是把它们存储在内部缓冲
|
||||
区里,直到缓冲区满为止时才将内容写入磁盘。如果使用flush()就可以在缓冲区装满之前
|
||||
强行地将其内容写入磁盘,其原型为:
|
||||
ostream&flush();
|
||||
当在恶劣的环境下使用程序时(如在经常掉电的地方),调用flush()是很必要的。
|
||||
注:关闭文件或程序正常终止均清除所有的缓冲区。
|
||||
|
||||
===== 18.11随机存取 =====
|
||||
在C++的I/O系统中,用户使用函数seekg()和seekp()执行随机存取。它们的一般形
|
||||
式为:
|
||||
istream &seekg(streamoff offset, seek_dir origin);
|
||||
ostream &seekp(streamoff offset, seek_dir origin);
|
||||
其中,streamoff是在iostream.h中定义的一种类型,它能包含offset所能容纳的最大有效
|
||||
值。同时,seek_dir是具有下列值的一个枚举:
|
||||
ios: :beg
|
||||
ios::cur
|
||||
ios::end
|
||||
C++的I/O系统管理和文件相关的两类指针。一类是__取指针__(get pointer),它指出在文
|
||||
件中进行下次**输入**操作的位置;另一类是__送指针__(put pointer),它指出在文件中进行下次**输**
|
||||
**出**操作的位置。每进行一次输入和输出操作,相应的指针就自动顺序前进。使用函数seekg()
|
||||
和seekp()可以按非顺序方式存取文件。
|
||||
|
||||
函数seekg()把文件的当前取指针移动到距指定的origin offset个字节的位置。origin必
|
||||
须是下列值之一:
|
||||
ios::beg 文件首
|
||||
ios::cur 当前位置
|
||||
ios::end 文件尾
|
||||
函数seekp()把相关文件的当前送指针定位到距指定的origin offset个字节的位置。ori-
|
||||
gin也必须是上述值之一。
|
||||
|
||||
下面的程序介绍了函数seekp()。它可以__改变一个文件中的特定字符__。命令行参数为文
|
||||
件名、要改变的文件中的第几个字节和新的字符值。注意,文件打开为读/写操作。
|
||||
|
||||
#include<iostream.h>
|
||||
#include<fstream.h>
|
||||
#include<stdio.h>
|
||||
main(int argc, char * argv[])
|
||||
{
|
||||
if(argc!=4){
|
||||
cout<<"Usage:CHANGE<mename><byte> <char>\n"
|
||||
return 1;
|
||||
}
|
||||
fstream out(argv[1], **ios::in | ios::out** | ios: :binary);
|
||||
if(!out){
|
||||
cout << "Cannot open file.\n";
|
||||
return 1;
|
||||
|
||||
out.seekp(__atoi__(argv[2]), ios::beg);
|
||||
out. put(* argv[3]);
|
||||
out.close();
|
||||
return 0;}
|
||||
列如,用这个程序把文件TEST中的第12个字节换成Z,行命令为:
|
||||
change test 12 Z
|
||||
|
||||
下面的程序使用了seekg(),它显示指定文件从指定位置开始的内容。
|
||||
#include<iostream.h>
|
||||
#include<fstream.h>
|
||||
#include<stdlib.h>
|
||||
main(int argc,char * argv[]){
|
||||
Char ch;
|
||||
if(argc!=3){
|
||||
cout <<"Usage: SHOW <filename><starting location>\n";
|
||||
return 1;
|
||||
}
|
||||
ifstream in(argv[1], ios::in| ios::binary);
|
||||
if(!in) {
|
||||
cout <<"Cannot open file.\n";
|
||||
return 1;
|
||||
}
|
||||
in.seekg(atoi(argv[2], ios::beg);
|
||||
while(in, get(ch))
|
||||
cout << ch;
|
||||
return 0;
|
||||
}
|
||||
|
||||
下面的程序用seekp()和seekg()反转一个文件中的前(num>个字符:
|
||||
#include<iostream.h>
|
||||
#include<fstream.h>
|
||||
#include<stdlib.h>
|
||||
main(int argc, char*argv[])
|
||||
{
|
||||
if(argc!=3) {
|
||||
cout << "Usage: Reverse<filename> <num>\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
ifstream inout (argv[1],ios::in|ios::out | ios::binary);
|
||||
if(!inout){
|
||||
cout<<"Cannot open input file.\n";
|
||||
return 1;
|
||||
}
|
||||
long e,i,j;
|
||||
char c1,c2;
|
||||
e=atol(arg[2]);
|
||||
for(i=0,j=0;i<1; i++,j--){
|
||||
inout.seekg(i, ios::beg);
|
||||
inout.get(c1);
|
||||
inout.seekg(j, ios::beg);
|
||||
inout.get(c2);
|
||||
inout.seekp(i, ios::beg);
|
||||
inout.put(c2);
|
||||
inout.seekp(j , ios::beg);
|
||||
inout.put(c1);
|
||||
}
|
||||
inout.close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
使用这个程序的时侯,必须指定要反转的文件名和字符个数。例如,要反转文件TEST
|
||||
的头10个字符,行命令为:
|
||||
reverse test 10
|
||||
假设文件包含如下内容:
|
||||
This is a test.
|
||||
执行程序后,文件的内容变为:
|
||||
a si sihTtest.
|
||||
|
||||
===== 18.11.1得到当前文件的位置 =====
|
||||
使用下列函数可以确定每个文件指针的当前位置:
|
||||
streampos tellg();
|
||||
streampos tellp();
|
||||
其中,__streampos__是在iostream.h中定义的一种类型,它能容纳每个函数返回的最大值。
|
||||
可以用tellg()和tellp()返回值作为下面形式的seekg()和seekp()的变元:
|
||||
istream &seekg(streampos pos);
|
||||
ostream&seekp(streampos pos);
|
||||
这些函数提供用户保存当前文件的位置,转而执行其它文件操作,然后可__将文件重设到__
|
||||
__先前保存的位置__。
|
||||
|
||||
===== 18.12 I/O状态 =====
|
||||
C++I/O系统维护每次I/O操作结果的状态信息。I/O系统的当前状态放在一个整数
|
||||
中,其中有下列标志及编码:
|
||||
名称 含义
|
||||
eofbit 1:到达文件尾 0:否则
|
||||
failbit 1:非致命I/O错 0:否则
|
||||
badbit 1:致命I/O错 0:否则
|
||||
这些标志一一列举在ios中。定义在ios中的goodbit值为0。
|
||||
|
||||
获取I/O状态信息有两种方法。第一种是调用成员函数rdstate(),其原型为:
|
||||
int rdstate();
|
||||
它返回一个反映错误标志状态的整数。根据前面的标志表,可以料到在不出现错误时rdstate
|
||||
()返回值是0,否则就有标志位被置位。
|
||||
注:建议的ANSI C++标准将rdstate()的返回值类型说明为iostate,即一个整型值的
|
||||
typedef。当前,大多数C++编译程序将rdstate()的返回值类型说明为整型。
|
||||
|
||||
下面的程序介绍了rdstate(),它显示一个文本文件的内容。如果出现错误,则用check-
|
||||
status()报警。
|
||||
#include<iostream.h>
|
||||
#include<fstream.h>
|
||||
void checkstatus(ifstream&in);
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
if(argc!=2){
|
||||
cout << "Usage: Display <filename>\n";
|
||||
return 1;
|
||||
}
|
||||
ifstream in(argv[1]);
|
||||
if(!in){
|
||||
cout << "Cannot open input file.\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
char c;
|
||||
while(in.get(c)){
|
||||
cout<<c;
|
||||
__checkstatus(in)__;
|
||||
}
|
||||
CheCkstatus(in);//Check final status
|
||||
in.close();
|
||||
return 0;
|
||||
}
|
||||
void Checkstatus(ifstream&in)
|
||||
{
|
||||
int i;
|
||||
i=**in.rdstate**();
|
||||
if(i&__ios::eofbit__)
|
||||
cout<<"EOF encountered\n";
|
||||
else if(i&ios::failbit)
|
||||
cout<<" Non-Fatal I/O error\n";
|
||||
else if(i&ios::badbit)
|
||||
cout<<"Fatal I/O error\n";
|
||||
}
|
||||
该程序总要报一个“错误”。while循环结束后,还调用了一次checkstatus(),正如所期望
|
||||
的,程序报告遇到了EOF。以后读者会发现在编写程序时函数checkstatus的用途很大。
|
||||
|
||||
确定出错的另一种方法是使用下列函数中的一个或几个:
|
||||
int bad();
|
||||
int eof();
|
||||
int fail();
|
||||
int good();
|
||||
|
||||
前面已经讨论过eof(),这里不再赘述。badhit置位时函数bad()返回真。failbit置位时函
|
||||
数fail()返回真。无错误发生时,函数good()返回真,否则返回假。
|
||||
注:建议的ANSI C++标准规定,bad()、eof()、fail()和good()函数的返回值类型为布
|
||||
尔型。但现在大多数C++编译程序将它们的返回值类型说明为整型。从实际的观点看,这种
|
||||
差别是无关紧要的,因为在任何表达式中布尔类型自动转换为整型。
|
||||
|
||||
一旦有错误发生,在程序继续运行之前有必要__使用函数clear()清除错误标志__,其原型
|
||||
为:
|
||||
void clear(int flags=0);
|
||||
如果flags为0(缺省值),则清除所有的错误标志位(复位为0),否则将flags设置为所需
|
||||
的清除值。
|
||||
|
||||
|
||||
===== 18.13定制的I/O和文件 =====
|
||||
|
||||
在第十六章里,我们学习了如何重载关于自定义的类的插入和提取运算符。第十六章只
|
||||
介绍了控制台I/O.但是,由于所有c++的流都是一样的,所以无需做任何改变就可以用同
|
||||
样的重载插入函数向屏幕或文件输出。下面的例子再现了第十六章中那个电话簿的例子,不
|
||||
过这里把表存到了磁盘上。这个程序非常简单,它可以向表中添加名字或在屏幕上显示表的
|
||||
内容。作为一个练习,把程序做一些改进,使它能寻找指定的成员和删除不要的成员。
|
||||
#include<iostream.h>
|
||||
#include <fstream.h>
|
||||
#include<string.h>
|
||||
class phonebook{
|
||||
char name[80];
|
||||
char areacode[4];
|
||||
char prefix[4];
|
||||
char num[5];
|
||||
public:
|
||||
phonebook(){};
|
||||
phonebook(char * n,char * a,char * p, char * nm)
|
||||
{
|
||||
__strcpy__(name,n);
|
||||
strcpy(areacode,a);
|
||||
strcpy(prefix,p);
|
||||
strcpy(num , nm);
|
||||
}
|
||||
friend ostream&operator<<(ostream&stream,phonebook o);
|
||||
friend istream &Operator>>(ostream &stream, phonebook &o);
|
||||
};
|
||||
// Display name and phone number.
|
||||
__ostream&operator<<__(ostream &stream, phonebook o)
|
||||
{
|
||||
stream <<o.name <<" ";
|
||||
stream<<"(" <<o.areacode<<")";
|
||||
stream<<o.prefix<<"-";
|
||||
stream<<o.num<<"\n";
|
||||
return stream ;// must return stream
|
||||
}
|
||||
// Input name and telephone number.
|
||||
istream &operator>>(istream &stream, phonebook &o)
|
||||
{
|
||||
cout << "Enter name:";
|
||||
stream >> o. name;
|
||||
cout <<"Enter area code:";
|
||||
stream >> o. areacode;
|
||||
cout <<"Enter prefix :";
|
||||
stream >> o.prefix;
|
||||
cout <<"Enter number :";
|
||||
|
||||
stream>>o. num;
|
||||
cout<<"\n";
|
||||
return stream;
|
||||
}
|
||||
main()
|
||||
{
|
||||
phonebook a;
|
||||
char c;
|
||||
fstream pb("phone”, ios::in|ios::out | ios::app);
|
||||
if(!pb){
|
||||
cout << "Cannot open phonebook file.\n";
|
||||
return 1;
|
||||
}
|
||||
for (;;){
|
||||
char c;
|
||||
do{
|
||||
cout<<"1.Enter numbers\n";
|
||||
cout << "2. Display numbers\n";
|
||||
cout<<"3.Quit\n";
|
||||
cout <<"\nEnter a choice:";
|
||||
cin>>c;
|
||||
}while(c<’1’||c>’3’);
|
||||
switch(c){
|
||||
case’1’:
|
||||
cin>>a;
|
||||
cout<<"Entry is:";
|
||||
cout << a;//show on screen
|
||||
pb <<a;// write to disk
|
||||
break;
|
||||
case’2’:
|
||||
Char ch;
|
||||
pb.seekg(0, ios::beg);
|
||||
while(!pb.eof()){
|
||||
pb.get(ch);
|
||||
cout <<ch;
|
||||
}
|
||||
pb.clear();// reset eof
|
||||
cout<<end;
|
||||
break;
|
||||
case’3’:
|
||||
pb.close();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
注意,重载的运算符((不用做任何改变就可用于写磁盘文件,也可用于写屏幕。这是
|
||||
C++I/O方法的最重要也是最有用的特征之一。
|
||||
7
Zim/Programme/C++/杂思.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-16T21:01:01+08:00
|
||||
|
||||
====== 杂思 ======
|
||||
Created Thursday 16 February 2012
|
||||
|
||||
68
Zim/Programme/C++/杂思/C++杂思录——风格的选择.txt
Normal file
@@ -0,0 +1,68 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-16T20:52:46+08:00
|
||||
|
||||
====== C++杂思录——风格的选择 ======
|
||||
Created Thursday 16 February 2012
|
||||
|
||||
http://blog.csdn.net/myan/article/details/1916
|
||||
|
||||
===== 风格的选择 =====
|
||||
|
||||
【警告】我目前从事嵌入式开发,文章中的观点受到我浅薄开发经验的强烈影响,各位请抱着批判的观点看待。另外,此文的以项目实际开发为衡量标准,请不要以仅在理论中存在的理想标准来评价本文的观点。
|
||||
|
||||
【大师语录】
|
||||
|
||||
Herb Sutter(1998):...在我们公司的开发中,**大量使用的是封装,包容,访问控制,ADT,不大用继承和多态。我们经常使用STL,template...至于异常**,我通常都用new (nothrow) Classname!;-) ...
|
||||
|
||||
Douglas Lea(1995): 也许对C++最好的看法是把它看成一系列较小、较简单的语言。包括:一种接口定义语言,__一种数据抽象语言__,一种静态类型的面向对象语言,一种过程化语言(也就是C)。它的每个方面都有一些缺陷...,让这些模型和特性彼此交互协作会导致严重的复杂性。通常,对付这种复杂性的最佳手段是__始终坚持使用少量的几个设计和编程惯用技术__(就是今天所谓的模式——译者),这些技术又组合成为更加通用和有效的技术。(设计开发中的)一个首要目标是尽可能远离这个语言中的阴暗角落。
|
||||
|
||||
【正文】
|
||||
|
||||
|
||||
Bjarne Stroustrup说,C++有四个部分:__better C,ADT,OO,和GP__。虽然现在也有一些新的风格被证明可以在C++中运用,比如functional, generative,meta programming等等,但是在实际工程中,主流的风格就是这四种。我们通常使用OO风格进行软件开发,但是,严格来说,目前C++中的所谓OO风格是一种典型的混合风格。
|
||||
|
||||
是不是可以这样认为,C++实际上是几种不同风格的语言集合。也就是说,你可以把它看成几种不同的语言,可以只使用其中一种语言进行完整的软件开发。如果同时杂合使用一种以上的风格,则复杂性会大幅度地增加。我个人认为,这是C++在实践中难于控制的一个主要原因。混合使用不同风格,就好像在一个源文件里混合使用多种不同的语言,复杂和不一致性必然暴露。当然,C++独特的魅力正在于混合风格编程的强大威力。这正是一把双刃剑,虽然具有潜在的强大威力,但是通常来说也是导致项目混乱的重要原因。
|
||||
|
||||
我认为以下面的原则进行实际开发,将可以在一定程度上规避风险:
|
||||
|
||||
1). 在任何一个单个的时间点,__只使用一种编程风格__。
|
||||
2). 以一种风格为主风格,用它来组织整体模块的开发。
|
||||
3). 在遇到特别适合另一种风格的**典型场景**,可以用一个子模块包装该场景,然后在该子模块中使用该风格,但记住遵循要求1,避免混合风格。此外,必须通过封装手段将该模块包装起来,以符合主体风格的要求。比如说,主风格是better C,在某个子模块中用到了面向对象,则应当使这个子模块从整体上看来像是一个普通的C过程。
|
||||
4). 在个别场合,混合风格的确有很大的好处。但是,这种情形是比较少见的,一般来说比较成功的实践已经总结成patterns,所以在工程实际中,可以强行规定,只有在符合某个patterns的情况下才可以谨慎地使用混合风格,严禁擅自创造新的混合用法。
|
||||
|
||||
我将C++划分为三个半子语言:
|
||||
|
||||
1) Better C, 只增加函数重载、引用类型、缺省参数等简单特性的类C子集。对应ANSI C语言。
|
||||
2) __ADT C++__,即C with Class,整个程序__由平面化的具体类(concrete class)对象构成__,无继承,无多态。对应Ada 83语言。
|
||||
3) IDL C++,我称之为Interface-Oriented,典型范例是COM组件模型。
|
||||
3.5) GP C++, 利用模板技术形成了一种库和组件的实现语言。这不是一种完整子语言,一方面因为可以把它看成是ADT C++的一种延伸,另一方面它必须依附于其他风格而发挥作用。
|
||||
|
||||
显然,我这里遗留了一个最重要的风格,也就是我们通常所说的“传统面向对象”风格,由Smalltalk,Java等语言所展示的,由MFC等类库经过多年实践论证了的一种风格:**靠庞大的继承树抽象和组织各种数据类型,靠继承和组合实现代码复用**。这种风格为什么没有被我提及呢?
|
||||
|
||||
因为我认为这种风格实际上是一种混合风格!可以认为是在试图融合上述第2、3和3.5种风格。在前述的三条原则里,它严重地违背了第二条。由于C++的静态本质,由于C++缺乏天然的类库和垃圾收集机制,使得在C++中进行Smalltalk风格的编程非常非常困难,以至于为了克服这些困难,C++实际上发展出了一套不同于Smalltalk、Java风格的独特的“面向对象”编程风格。这套风格历经近15年实践,应该说有成功有失败,虽然出版了大量的著作,至今没有形成简单的、一致的、可仿效的风格指导。从某种意义上说,如此多的C++面向对象编程指导书籍十几年常盛不衰,恰恰说明这种风格的困难程度和难以仿效性。__就我个人而言,我已经不再以这种风格为指导思想了。我不会再拼命地构造继承树,思考哪些函数应该是虚函数这类问题了__。
|
||||
|
||||
你可以认为“为了复用代码而进行的继承”是这种风格的标志。请注意,__ADT C++允许组合,对于继承则应该想尽一切办法避免__。而IDL C++的典型代表COM,根本就不支持这种继承,它支持的只是接口的复用。
|
||||
|
||||
当然,这并不是要否定十几年来C++在面向对象方面发展的成绩。但是,如果你现在从头开始规划一个完整的项目,那么我认为如果选择这种杂合风格,是不太明智的。但是这种风格也有两个典型的使用场景:
|
||||
|
||||
1) **有一个完整的框架支持**。比如MFC。虽然这种风格本身有很多技术难点,但是MFC这样的框架已经帮你克服了一部分,给你营造了一个类似Smalltalk那样的、相对舒适环境,这时候可以使用这种风格。但是通常要认识到,这类框架在克服不少技术难点的同时,引入了一些新的问题,有时是更加难以对付的问题,所以要明智,并且做好充分准备。
|
||||
|
||||
2) 符合经典模式。如果遇到某个典型的“面向对象”场景,已经有了成熟的、优秀的、现成的、文档化了的设计解决方案,则可以有选择的、谨慎地使用之。我指的主要就是GoF和其他一些设计模式。这里所谓的“经典模式”数量绝对不会太多,但是却大量地、反复地出现在设计中,并且往往复合出现。这样的情况用已经经过验证的设计方案来解决是非常合适的。我个人在这里有一些实践,觉得应该注意几个问题。第一是要谨慎,我遇到过大量的情形,看上去很适合用某个模式来解决,但是真的用了才发现并不是这么回事。在不适合的地方套用了错误的模式,会把事情弄得一团糟;二是最好将设计方案局部化,包装起来,从外面看不出你使用了什么模式。三是注意内存问题。使用OO风格的最大障碍其实就是内存问题。
|
||||
|
||||
其实这个口子一开,最终的设计里仍然会出现大量的“传统OO”风格,因为经典模式实在是太普遍了。所以主要问题是控制和包装,因为四处泛滥的模式实际上等于重新回到混合风格。
|
||||
|
||||
值得指出的是,其实在大部分的经典模式里,并不存在“为了复用代码而进行的继承”。我们可以认为,__凡是合理的面向对象,必然具有接口继承的特征,必然出现抽象类__,很可能出现多态包容。
|
||||
|
||||
就我个人而言,由于从事嵌入式应用软件和高层系统软件的开发,出于嵌入式系统对于效率的极端关注,我比较倾向于下面的组合:
|
||||
|
||||
__1) 以ADT C++作为主风格。__
|
||||
__2) 利用GP辅助设计良好的ADT。以GP和组合实现代码复用。__
|
||||
__3) 将可能的OO包装在平面式的类中。__
|
||||
__4) 禁止使用异常。这一点将会有专门的反思文章。__
|
||||
|
||||
对于PC和大型项目的开发,我觉得以IDL C++的风格作为主程序应该是更合适的。目前我还没有这方面的实践。但是我希望能够尽快有一些尝试。
|
||||
|
||||
|
||||
结语:
|
||||
有人可能会质疑我对“复用性”的重视不足。因为传统OO的一大立足点。对此我不予否认。我认为目前很多程序员对复用性问题不是考虑不足,而是考虑过度。尤其是应用程序员,花费太多的精力去让自己的组件满足未来可能的需求变化,很可能是在浪费时间。对于“复用性”这个话题,我也有一些想法,容后再述。
|
||||
97
Zim/Programme/C++/杂思/The_C++_Style_Sweet_Spot.txt
Normal file
@@ -0,0 +1,97 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-07T20:17:49+08:00
|
||||
|
||||
====== The C++ Style Sweet Spot ======
|
||||
Created Tuesday 07 February 2012
|
||||
|
||||
http://www.artima.com/intv/goldilocks.html
|
||||
|
||||
A Conversation with Bjarne Stroustrup, Part I
|
||||
by Bill Venners
|
||||
October 13, 2003
|
||||
|
||||
Page 1 of 4 >>
|
||||
|
||||
===== Summary =====
|
||||
Bjarne Stroustrup talks with Bill Venners about the perils of staying too low level and venturing too object-oriented in C++ programming style.
|
||||
|
||||
On September 22, 2003, Bill Venners met with Bjarne Stroustrup at the JAOO conference in Aarhus, Denmark. In this interview, which will be published in multiple installments on Artima.com, Stroustrup gives insights into C++ best practice. In this first installment, Stroustrup describes how C++ programmers can reconsider their style of C++ use to gain maximum benefit from the language.
|
||||
|
||||
===== Climbing Above C-Level =====
|
||||
**Bill Venners:** In an interview, you said, "The C++ community has yet to internalize the facilities offered by standard C++. By reconsidering __the style of C++ __use, major improvements in ease of writing, correctness, maintainability, and efficiency can be obtained." How should C++ programmers reconsider their style of C++ use?
|
||||
|
||||
**Bjarne Stroustrup: **It's always easier to say what not to do, rather than what to do, so I'll start that way. A lot of people see C++ as C with a few bits and pieces added. They write code with a lot of __arrays and pointers__. They tend to use new the way they used malloc. Basically, the abstraction level is low. Writing C-style code is one way to get into C++, but it's not using C++ really well.
|
||||
|
||||
I think a better way of approaching C++ is __to use some of the standard library facilities__.
|
||||
|
||||
For example, __use a vector rather than an array__. A vector knows its size. An array does not. You can extend a vector's size implicitly or explicitly. To get an array of a different size, you must explicity deal with memory using realloc, malloc, memcpy, etc. Also, __use inline functions rather than macros__, so you don't get into the macro problems. __Use a C++ string class rather than manipulating C strings directly__. And if you've got a lot of casts in the code, there's something wrong. You have dropped from the level of types, a high level of abstraction, down to a level of bits and bytes. You shouldn't do that very often.
|
||||
|
||||
To get out of writing low level code, you needn't start writing a lot of classes. Instead, __start using facilities provided in libraries__. The standard library is the first and most obvious source, but there are also good libraries for things like math or systems programming. You** don't have to do threading at the C level**. You can use a C++ threading library, for example, __Boost.Threads__. There are quite a few threading libraries. If you want callbacks, don't use just plain C functions. Get __libsigc++,__ and you'll have a proper library that deals with callbacks—callback classes, slots and signals, that kind of stuff. It's available. It's conceptually closer to what you're thinking about anyway. And you don't have to mess with error prone details.
|
||||
|
||||
Most of these techniques are criticized unfairly for being inefficient. The assumption is that if it is elegant, if it is higher level, it must be slow. It could be slow in a few cases, so deal with those few cases at the lower level, but start at a higher level. In some cases, you simply don't have the overhead. For example, vectors really are as fast as arrays.
|
||||
|
||||
===== Object-Orientaphilia =====
|
||||
The other way people get into trouble is exactly the opposite. They believe that C++ should be an extremely high level language, and everything should be object-oriented. They believe that you should do everything by creating a class as part of a class hierarchy with lots of virtual functions. This is the kind of thinking that's reflected in a language like Java for instance, but__ a lot of things don't fit into class hierarchies__. An integer shouldn't be part of a class hierarchy. It doesn't need to. It costs you to put it there. And it's very hard to do elegantly.
|
||||
|
||||
__You can program with a lot of free-standing classes__. If I want a complex number, I write a complex number. It doesn't have any virtual functions. It's not meant for derivation.
|
||||
|
||||
You should use inheritance only when a class hierarchy makes sense __from the point of view of your application, from your requirements__. For a lot of graphics classes it makes perfect sense. The oldest example in the book is the shape example, which I borrowed from Simula. It makes sense to have a hierarchy of shapes or a hierarchy of windows, things like that. But for many other things you shouldn't plan for a hierarchy, because you're not going to need one.
|
||||
|
||||
So you can __start with much simpler abstractions__. Again the standard library can provide some examples: vector, string, complex number. __Don't go to hierarchies until you need them.__ Again, one indication that you've gone too far with class hierarchies is you have to write casts all the time, casting from base classes to derived classes. In really old C++, you would do it with a C style cast, which is unsafe. In more modern C++, you use __a dynamic cast__, which at least is safe. But still better design usually leads you to use casting only when you get objects in from outside your program. If you get an object through input, you may not know what it is until a bit later, and then you have to cast it to the right type.
|
||||
|
||||
**Bill Venners: **What is the cost of going down either of those two paths, being too low-level or too enamored with object-orientation? What's the problem?
|
||||
|
||||
**Bjarne Stroustrup:** The problem with the C way is that if you write code C-style, you get C-style problems. You will get buffer overflows. You will get pointer problems. And you will get hard to maintain code, because you're working at a very low level. So the cost is in development time and maintenance time.
|
||||
|
||||
Going to the big class hierarchy is again, you write more code than you need to, and you get too much connection between different parts. __I particularly dislike classes with a lot of get and set functions__. That is often an indication that it shouldn't have been a class in the first place. It's just **a data structure**. And if it really is a data structure, make it a data structure.
|
||||
|
||||
===== Classes Should Enforce Invariants =====
|
||||
invariant [in'veriənt]adj.不变的 n.不变量
|
||||
|
||||
**Bjarne Stroustrup:** My rule of thumb is that you should have a real class with an interface and a hidden representation if and only if__ you can consider an invariant for the class__.
|
||||
|
||||
我的原则是:__当且仅当你需要考虑类中的契约(指的是类中各字段的约束规则)时你才应该定义一个类,它包含接口和隐藏的状态(代表)。__
|
||||
|
||||
**Bill Venners:** What do you mean by invariant?
|
||||
|
||||
**Bjarne Stroustrup:** What is it that makes the object a valid object? An invariant allows you to say when __the object's representation(对象的状态) is good and when it isn't.__ Take a vector as a very simple example. A vector knows that it has n elements. It has a pointer to n elements. The invariant is exactly that: the pointer points to something, and that something can hold n elements. If it holds n+1 or n-1 elements, that's a bug. If that pointer is zero, it's a bug, because it doesn't point to anything. That means it's **a violation of an invariant**. So you have to be able to state __which objects make sense__. Which are good and which are bad. And __you can write the interfaces so that they maintain that invariant__. That's one way of keeping track that your member functions are reasonable. It's also a way of keeping track of which operations need to be member functions. Operations that don't need to mess with the representation are better done __outside__ the class. So that you get a clean, small interface that you can understand and maintain.
|
||||
|
||||
__定义接口的目的是保证契约能够被遵守,从而保障对象的正确性。其它不涉及到对象契约的函数或运算符操作可已在类外面实现(例如,实现一个简单的函数库)。__
|
||||
|
||||
**Bill Venners: **So the invariant justifies the existence of a class, because the class takes the responsibility for maintaining the invariant.
|
||||
|
||||
**Bjarne Stroustrup:** That's right.
|
||||
|
||||
**Bill Venners: **The invariant is a relationship between different pieces of data in the class.
|
||||
|
||||
**Bjarne Stroustrup: **Yes. If every data can have **any value**, then it doesn't make much sense to have a class. Take a single data structure that has a name and an address. Any string is a good name, and any string is a good address. If that's what it is, __it's a structure__. Just call it a struct. Don't have anything private. Don't do anything silly like having a hidden name and address field with get_name and set_address and get_name and set_name functions. Or even worse, make a virtual base class with virtual get_name and set_name functions and so on, and override it with the one and only representation. That's just elaboration. It's not necessary.
|
||||
|
||||
**Bill Venners:** It's not necessary because t**here's one and only representation(对类而言)**. __The justification is usually that if you make it a function, then you can change the representation.有些成员函数并不改变对象的状态,但是可能也是必需的。__
|
||||
|
||||
**Bjarne Stroustrup: **Exactly, but some representations you** don't** change. You don't change the representation of an integer very often, or a point, of a complex number. You have to make design decisions somewhere.
|
||||
|
||||
And the next stage, where you go from the** plain data structure** to **a real class** with real class objects, could be that name and address again. You probably wouldn't call it name_and_address. You'll maybe call it personnel_record or mailing_address. At that stage you believe name and address are not just strings. Maybe you break the name down into first, middle, and last name strings. Or you decide the semantics should be that the one string you store really has first, middle, and last name as parts of it. You can also decide that the address really has to be a valid address. Either you validate the string, or you break the string up into first address field, second address field, city, state, country, zip code, that kind of stuff.
|
||||
|
||||
When you start breaking it down like that, you get into the possibilities of different representations. You can start deciding, does it really add to have private data, to have a hierarchy? Do you want a plain class with one representation to deal with, or do you want to provide an abstract interface so you can represent things in different ways? But you have to make those design decisions. You don't just randomly spew classes and functions around. And you have to have some semantics that you are defending before you start having private data.
|
||||
|
||||
The way the whole thing is conceived is that the constructor establishes the environment for the member functions to operate in, in other words,__ the constructor establishes the invariant.__ And since to establish the invariant you often have to acquire resources, you have the destructor to pull down the operating environment and release any resources required. Those resources can be **memory, files, locks, sockets**, you name it—anything that you have to get and put back afterwards.
|
||||
|
||||
===== Designing Simple Interfaces =====
|
||||
**Bill Venners: **You said that__ the invariant helps you decide what goes into the interface__. Could you elaborate on how? Let me attempt to restate what you said, and see if I understand it. __The functions that are taking any responsibility for maintaining the invariant should be in the class.__
|
||||
|
||||
**Bjarne Stroustrup:** Yes.
|
||||
|
||||
**Bill Venners:** Anything that's just using the data, but not defending the invariant, doesn't need to be in the class.
|
||||
|
||||
**Bjarne Stroustrup:** Let me give an example. There are some operations you really can't do without having direct access to the representation. If you have an operation that changes the size of a vector, then you'd better be able to make changes to the number of elements stored. You move the elements and change the size variable. If you've just got to read the size variable, well, __there must be a member function for that__. But there are other functions that
|
||||
|
||||
==== can be built on top of existing functions ====
|
||||
. For example, given efficient element access, a find function for searching in a vector is best provided as a non-member.
|
||||
|
||||
Another example would be a Date class, where the operations that actually change the day, month, and year have to be members. But the function that finds the next weekday, or the next Sunday, **can be put on top of it**. I have seen Date classes with 60 or 70 operations, because they built everything in. Things like find_next_Sunday. Functions like that don't logically belong in the class. If you build them in, they can touch the data. That means if you want to change the data layout, you have to review 60 functions, and make changes in 60 places.
|
||||
|
||||
Instead, if you build a relatively simple interface to a Date class, you might have five or ten member functions that are there because __they are logically necessary__, __or for performance reasons.__ It's hard for me to imagine a performance reason for a Date, but in general that's an important concern. Then you get these five or ten operations, and __you can build the other 50 in a supporting library__. That way of thinking is fairly well accepted these days. Even in Java, you have the containers and then the supporting library of static methods.
|
||||
|
||||
I've been preaching this song for the better part of 20 years. But people got very keen on putting everything in classes and hierarchies.
|
||||
I've seen the Date problem solved by having a base class Date with some operations on it and the data protected, with utility functions provided by deriving a new class and adding the utility functions. You get really messy systems like that, and __there's no reason for having the utility functions in derived classes.__ You want the utility functions to the side so you can combine them freely. How else do I get your utility functions and my utility functions also? The utility functions you wrote are independent from the ones I wrote, and so they should be independent in the code. If I derive from class Date, and you derive from class Date, a third person won't be able to easily use both of our utility functions, because we have built dependencies in that didn't need to be there. So you can overdo this class hierarchy stuff.
|
||||
17
Zim/Programme/C++/杂思/写C++程序要清楚自己的角色.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-16T21:38:39+08:00
|
||||
|
||||
====== 写C++程序要清楚自己的角色 ======
|
||||
Created Thursday 16 February 2012
|
||||
http://blog.csdn.net/myan/article/details/754239
|
||||
|
||||
今天跟一些朋友在信件里讨论C++的使用。一个还在学习C++的朋友,认为要把重点放在虚函数、多态性、STL上。我认为学习的时候这样考虑肯定是对的,但是真正开发的时候,不能因为你掌握了OO、generic这些先进武器,就非要用上这些东西,以示区别不可。谨慎合理地使用语言的机制是开发良好C++程序的关键,至少在心态上是关键。
|
||||
|
||||
下面是信件内容的摘选:
|
||||
|
||||
你写C++的时候,一定要想清楚,你是在__做基础设施还是在应用__。如果是基础设施,比如类库、框架、底层功能的class wrapper,那么可以允许你大胆地使用C++中的各种技巧,关键的要求是你得__暴露出来一个clean的interface__,让别人好用。这一点并不容易,特别是有的时候你觉得很好用的接口人家就觉得很别扭。所以比较省心的做法就是__把接口设计成流行的风格__。比如模仿STL的风格,模仿Java的风格,模仿COM的风格,甚至模仿MFC的风格,可能都比你自己发明一种新风格要“好用”。
|
||||
|
||||
可是做应用开发的时候,手就要把紧点,别自鸣得意地滥用高级技巧。应用开发很大程度上受基础设施的制约,总的来说,__使用函数、POD对象、concrete class,从framework中派生出来的class,再加上一点点用来节省打字的template,足以满足应用开发的需要__。特别是当你的下面没有很完备的class library或者framework的时候,千万不要一边写应用,一边又想着怎么让自己的这些东西“为万世开太平”,那样的话很容易就会把程序结构作的过于复杂。最后往往是应用没写好,也没有可复用性。以前我没有经验的时候,最容易犯的错误就是这个。
|
||||
|
||||
做基础设施的开发,那叫“设计”,是要为以后考虑的,为了长远利益可以牺牲眼前的进度、简单性。可是做应用,那眼前利益是第一位的,你先把手头的东西又快又稳地run起来,才谈得上以后有复用的可能。眼前的东西作的一塌糊涂,说里面有的模块设计得超级棒,绝对能复用,你自己都不相信。代码要一丝不苟,该写注释写注释,该写assert写assert,该怎么样怎么样,不能因为想着“反正也就是一锤子买卖”就马马虎虎。至于能不能复用,那是以后的事情。所谓Design for today, code for tomorrow,就是这个意思。
|
||||
81
Zim/Programme/C++/杂思/回复几个问题.txt
Normal file
@@ -0,0 +1,81 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-16T21:27:00+08:00
|
||||
|
||||
====== 回复几个问题 ======
|
||||
Created Thursday 16 February 2012
|
||||
|
||||
http://blog.csdn.net/myan/article/details/5884695
|
||||
|
||||
|
||||
有人问,你要讲的那三个C++特性,网上资料一大堆,你就免了吧。我说,如果我有时间,我会写的跟网上所有人的写法都不一样。比如说,讲 function/bind/lambda/closure,我会从 callback 讲起,回顾一下 C++中的 virtual function和成员函数指针,MFC中的message mapping,Borland C++Builder中的event handling,WTL的 CRTP 模拟虚函数,然后谈谈为什么 Java 需要加入 inner class,为什么C#把 delegate 作为first class citizen,以及 Qt 是如何用 meta-object 系统打造出迄今为止C++最漂亮的GUI框架,以及所有以上一切,在简单的 C 前是多么的多余和废柴。比如说,如果讲 rvalue reference,我会从孤魂野鬼似的匿名临时对象讲起,从 const T&,讲到 RVO/NRVO,说不定还会触及 expression template。
|
||||
|
||||
|
||||
但是,我知道我没那么多时间来把这些东西都写下来,写下来也用处不大。这些东西已经不是今天技术的主流。“why”现在越来越不重要,“how”压倒一切。
|
||||
“既然如此,你怎么还在鼓吹C++的那些奇技淫巧?你没看到C++已经越来越边缘了吗?”
|
||||
|
||||
这个,得容我自辨几句。
|
||||
|
||||
|
||||
首先,我现在只有在业余时间看看技术,所以当然选自己最熟悉的领域。
|
||||
|
||||
|
||||
其次,我不认为C++就没有用武之地了。C++会长期存在下去,而且在一些特殊的领域里仍然充当主角。比如,我们不应该小看 Qt 在移动开发中的发展空间,而在一系列我称之为“对抗性应用”的领域,C++还将占据优势,直到出现真正的替代者——肯定不是D,比较有希望的是Go。只是希望Rob Pike老人家坚持到底,不要跟当年plan 9一样,easy come easy go.
|
||||
|
||||
第三,最重要的,我鼓吹的不是奇技淫巧。相反,我可能比那些没学过C++或者被C++难倒的人都更讨厌C++中那些奇技淫巧,因为它曾浪费了我大量的时间、精力和热情。
|
||||
|
||||
为了讲清楚这个问题,我得谈谈C++的风格,这个老掉牙的话题。
|
||||
|
||||
上周末跟老朋友聚会,谈到技术的时候,有一个共识,软件开发方面真正有价值的进步,应当是有利于用户、有利于项目管理、有利于解决领域问题,而不是有利于程序员。多年以来,主流语言和系统的很多改进,其目的都是为了让写程序的人感觉更爽,而与用户、管理和解决问题毫无关系。C++在这方面是带了一个很坏的头,又要追求强大的表达能力,又要追求不打折扣的效率,结果搞出一大堆诸如操作符重载,template meta-programming之类的东西。老实讲,我也觉得:
|
||||
|
||||
Matrix4f a, b, c; c = a * (b – a);
|
||||
要比:
|
||||
Matrix4f a, b, c; c = a.mul(b.sub(a));
|
||||
|
||||
更清爽,但是就算是第二种形式,又能怎么样呢?无非多敲几个字而已,能有多大不了的事?哪个合格的程序员敢说无法理解?可为了让程序员的视觉更清爽一点,C++花了n年时间,弄出来一大堆奇技淫巧,再与之前之后诸多特性相互干扰,复杂度成倍增加,有人可能会跳出来反驳说,如果表达能力不重要,那么你干脆回去写汇编好了。
|
||||
|
||||
所以我得把话说全了。__程序的表达能力,只有在反映了其抽象能力的提高时,才是重要的,否则就是自娱自乐__。
|
||||
|
||||
__ 抽象是程序开发的全部意义所在__。之所以C比汇编是一个伟大的进步,是因为C建立了一个机器抽象,把诸如寄存器、cache、寻址方式、位对齐之类的细节都透明掉了,由此而带来的表达能力提升,当然是伟大的。但是你看看上面我举的那个例子,有抽象层次的提升吗?没有!有的只是YY暗爽值的提升。这种表达能力提升,如果得来全不费工夫,当然也无伤大雅,如果是呕心沥血,伤人一万,自损三千,窃以为大可不必也。
|
||||
|
||||
软件搞了60年,我认为真正被实践证明了的抽象,一共有四个半,分别是:
|
||||
|
||||
__1. 机器抽象__,或者称语言抽象,构造一台新的计算机或程序语言,使其能理解领域特定的语言,从而最妥帖地解决问题。这是最有力的抽象,是软件开发中的“火箭科技”。
|
||||
__2. 过程抽象__,把一件事情看成是__一系列嵌套和串接执行的标准化过程的总和__,就像流水线一样。这是极为有力的抽象,因此C语言无所不能。但是层次偏低,规模增大以后带来一些挑战。
|
||||
__3. 函数抽象__,最玄妙的一种,这个我不多说,有兴趣的去看 Structure and Interpretation of Computer Programs.
|
||||
__4. 面向对象抽象__,把一件事情看成是__一组各负其责的对象彼此之间相互收发消息,根据消息相互协作完成工作的过程__。这个抽象也极为有力。
|
||||
4.5 僵化的面向对象抽象,把世界看成是由**层次分明**、庞杂万端的__类型体系“实例化”__而出的对象组成的,把事情看成是这些对象之间互相收发消息、协作而成的过程。
|
||||
|
||||
问题出在这最后一个抽象上。由于面向对象早期的主要应用场合是__ GUI 和仿真__,是特别适合于**建立类型体系**的应用领域,结果人们误以为这种抽象模型可以应用于所有领域。成长于这个时期的C++受了这种思想的毒害,建立了一个极其严苛、吹毛求疵的类型系统。
|
||||
|
||||
事实上,后来的实践表明,拿__静态的类型系统去套多姿多彩、变化万千的现实世界__,纯粹是人类的狂妄自大。除了在少数几个完全是人造出来的领域(典型的就是GUI,什么窗口、控件、菜单…,完全是人自造的)还是可以的,在很多其他领域则是无力的。所以只能算半个抽象。
|
||||
|
||||
C++就是被这个半吊子抽象所害,以至今天,积重难返。至于用template泛型去放松类型的约束,实属亡羊补牢。template可谓毁誉参半,其中的精妙之处固然令人激赏,其中诡异无聊之处也令人愤懑。现在boost和其他一些C++库之中,把template舞得虎虎生风,我也不知道抽象到几天云外去了,但是仔细看来,往往是自取其乐,跟实际效果并无干系,反而有碍管理与交流。
|
||||
|
||||
无论如何,C++已经走到了今天。作为一个提供了较强抽象能力,同时性能又不打折扣的语言,还是不失其地位。
|
||||
|
||||
-----------------------
|
||||
貌似很有道理.
|
||||
现实世界,实际业务千变万化,将如此繁杂的问题域试图以class完全映射到计算机域,实在力不从心.
|
||||
|
||||
------------------------
|
||||
回复 heguo:不是 class 的问题,是 class hierachy 的问题。僵化的面向对象要求我们在动手构造系统之前先把系统背后的静态结构给总结出来,这个实在是强人所难。
|
||||
-----------------------
|
||||
回复 myan:我觉得你可能对封装理解的不够深刻,面向对象并不是一次性的就把所有的静态结构都总结出来的,事物都是在不断变化发展的,在没有了解清楚之前,我们不需要定义class,只需要定义我们搞清楚的,不清楚的可以封装在class内部
|
||||
-----------------------
|
||||
回复 book_uynixoac:
|
||||
这一点我同意myan的说法。
|
||||
用类来封装数据和行为并且在一组类之间通过信息交互以完成某一个事情,这是一个进步。类做为状态及其行为的复合体,其实提高了抽象的粒度,减少了对软件理解的复杂度。
|
||||
但是形形色色的类之间的交互,将在系统中造成的强耦合。由于各个类的类型迥异,将不可能构造一个可用的函数(必须对每个具体的类实现一个——看看C里面各种函数后缀)。为了解决这些问题,类的继承体系被提了出来,虚函数、抽象方法、抽象类、接口等面向对象的概念逐渐被翻出来。他们被提出,只是为了解决对象之间的耦合性以及给对象一个统一的身份,以让函数可以一个同一个的基础类来处理,并且通过虚函数来实现不同的行为。在当时看来,这些选择是却当的,它们也构成了面向对象理论的基石。
|
||||
时过境迁,当我们再来看这些选择,发现它们确实是很僵硬。是面向对象强加给现实世界的一个荒谬的逻辑。一个不太却当的比方:人可以喝水,马也可以喝水,马喝水的时候可曾想到过自己是一个可以喝水的动物群中的一个个体?人讲话的时候是否会在意自己是长有嘴的动物群中的一个个体?他们只是渴了,张开嘴喝水而已。经典的面向对象理论如果不搞出一个人和马一个统一的身份标示,将无法统一处理他们喝水这一个动作。
|
||||
僵硬的面向对象的理论映射到软件开发的过程中,就是你建模一个系统的时候,必须对每个对象的接口及其背后的继承树有着深入的把握且有明确的了解。如果在开发过程中发现两个继承体系中的类有相似的行为需要统一处理,那么就必须搞出一个很诡异很抽象很荒谬的接口,然后两个继承体系所有相关的接口都要做相应的调整。一个新的接口出来了,依赖于该接口抽象的函数或者方法总得引用一个或多个接口的方法。然而,在一个继承体系中很明显的抽象可能再另一个抽象体系中没有相应的概念。怎么办?把它们的行为提取出一个虚幻的接口,并且把相应的属性全部提到抽象类的层次来处理。各种始料不及的变动纷至沓来,原来精心设计的类体系结构在瞬间被破坏的面目全非。
|
||||
|
||||
为什么很多人会喜欢C?C虽然有缺点,但是遇到上述问题的修改时,涉及到的修改面却要小得多。由于没有了多个类在虚函数之间的互相调用,整个逻辑相对也会清晰很多。对代码维护而言,更是不知道要轻松多少。
|
||||
|
||||
面向对象中的封装是必要的,但是抽象和继承以及由此引起的又长又臭的继承树却是毒瘤。
|
||||
|
||||
个人认为,泛型是解决这一问题的良药,C++中的模板,以及各种动态语言都是为了避免上述单一面向对象的机制带来的问题。python作为一种强类型的面向对象的语言用起来非常舒服的原因,就是因为它没有C++/Java/C#过分强调类继承的问题,而是采用了泛型的手法优雅的解决了上述问题。
|
||||
|
||||
C++的问题是,它一开始是依照经典的面向对象理论来设计的,不仅引入了继承还引入了多继承。在实践中弊端越来越多时,硬生生的加上了模板这一块,从而使得经典风格和现代风格格格不入,就像华山的剑宗和气宗一样,很别扭。
|
||||
|
||||
就算如此,以模板和泛型为理论基础的STL中的容器和算法也要比java/C#最初版本中纯面向对象接口的容器好用不知道多少倍。
|
||||
11
Zim/Programme/C++/杂思/基于对象和面向对象的区别.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-15T22:12:19+08:00
|
||||
|
||||
====== 基于对象和面向对象的区别 ======
|
||||
Created Wednesday 15 February 2012
|
||||
http://blog.csdn.net/lzcx/article/details/600434
|
||||
|
||||
很多人没有区分“面向对象”和“基于对象”两个不同的概念。
|
||||
|
||||
面向对象的三大特点(封装,继承,多态)却一不可,通常“基于对象”使用对象,但是无法利用现有的对象模板产生新的对象类型,继而产生新的对象,也就是说“基于对象”没有继承的特点,而“多态”是表示为父类类型的子类对象实例,没有了继承的概念也就无从谈论“多态”。现在的很多流行技术都是基于对象的,它们使用一些封装好的对象,调用对象的方法,设置对象的属性。但是它们无法让程序员派生新对象类型。他们只能使用现有对象的方法和属性。所以当你判断一个新的技术是否是面向对象的时候,通常可以使用后两个特性来加以判断。“面向对象”和“基于对象”都实现了“封装”的概念,但是面向对象实现了“继承和多态”,而“基于对象”没有实现这些,的确很饶口。
|
||||