B. Stroustrup - The C++ Programming Language (794319), страница 17
Текст из файла (страница 17)
The size of an array must be a constant expression(§2.2.3). A pointer variable can hold the address of an object of the appropriate type:char∗ p = &v[3];char x = ∗p;// p points to v’s four th element// *p is the object that p points toIn an expression, prefix unary ∗ means ‘‘contents of’’ and prefix unary & means ‘‘address of.’’ Wecan represent the result of that initialized definition graphically:p:0:1:2:3:4:5:v:Consider copying ten elements from one array to another:void copy_fct(){int v1[10] = {0,1,2,3,4,5,6,7,8,9};int v2[10];// to become a copy of v1for (auto i=0; i!=10; ++i) // copy elementsv2[i]=v1[i];// ...}This for-statement can be read as ‘‘set i to zero; while i is not 10, copy the ith element and incrementi.’’ When applied to an integer variable, the increment operator, ++, simply adds 1.
C++ also offersa simpler for-statement, called a range-for-statement, for loops that traverse a sequence in the simplest way:void print(){int v[] = {0,1,2,3,4,5,6,7,8,9};for (auto x : v)cout << x << '\n';// for each x in vfor (auto x : {10,21,32,43,54,65})cout << x << '\n';// ...}The first range-for-statement can be read as ‘‘for every element of v, from the first to the last, placea copy in x and print it.’’ Note that we don’t have to specify an array bound when we initialize itwith a list. The range-for-statement can be used for any sequence of elements (§3.4.1).If we didn’t want to copy the values from v into the variable x, but rather just have x refer to anelement, we could write:46A Tour of C++: The BasicsChapter 2void increment(){int v[] = {0,1,2,3,4,5,6,7,8,9};for (auto& x : v)++x;// ...}In a declaration, the unary suffix & means ‘‘reference to.’’ A reference is similar to a pointer,except that you don’t need to use a prefix ∗ to access the value referred to by the reference.
Also, areference cannot be made to refer to a different object after its initialization. When used in declarations, operators (such as &, ∗, and []) are called declarator operators:T a[n];T∗ p;T& r;T f(A);// T[n]: array of n Ts (§7.3)// T*: pointer to T (§7.2)// T&: reference to T (§7.7)// T(A): function taking an argument of type A returning a result of type T (§2.2.1)We try to ensure that a pointer always points to an object, so that dereferencing it is valid. Whenwe don’t have an object to point to or if we need to represent the notion of ‘‘no object available’’(e.g., for an end of a list), we give the pointer the value nullptr (‘‘the null pointer’’).
There is onlyone nullptr shared by all pointer types:double∗ pd = nullptr;Link<Record>∗ lst = nullptr;int x = nullptr;// pointer to a Link to a Record// error : nullptr is a pointer not an integerIt is often wise to check that a pointer argument that is supposed to point to something, actuallypoints to something:int count_x(char∗ p, char x)// count the number of occurrences of x in p[]// p is assumed to point to a zero-terminated array of char (or to nothing){if (p==nullptr) return 0;int count = 0;for (; ∗p!=0; ++p)if (∗p==x)++count;return count;}Note how we can move a pointer to point to the next element of an array using ++ and that we canleave out the initializer in a for-statement if we don’t need it.The definition of count_x() assumes that the char∗ is a C-style string, that is, that the pointerpoints to a zero-terminated array of char.In older code, 0 or NULL is typically used instead of nullptr (§7.2.2).
However, using nullptreliminates potential confusion between integers (such as 0 or NULL) and pointers (such as nullptr).Section 2.3User-Defined Types472.3 User-Defined TypesWe call the types that can be built from the fundamental types (§2.2.2), the const modifier (§2.2.3),and the declarator operators (§2.2.5) built-in types. C++’s set of built-in types and operations isrich, but deliberately low-level. They directly and efficiently reflect the capabilities of conventionalcomputer hardware. However, they don’t provide the programmer with high-level facilities to conveniently write advanced applications. Instead, C++ augments the built-in types and operationswith a sophisticated set of abstraction mechanisms out of which programmers can build such highlevel facilities. The C++ abstraction mechanisms are primarily designed to let programmers designand implement their own types, with suitable representations and operations, and for programmersto simply and elegantly use such types.
Types built out of the built-in types using C++’s abstractionmechanisms are called user-defined types. They are referred to as classes and enumerations. Mostof this book is devoted to the design, implementation, and use of user-defined types. The rest ofthis chapter presents the simplest and most fundamental facilities for that. Chapter 3 is a morecomplete description of the abstraction mechanisms and the programming styles they support.Chapter 4 and Chapter 5 present an overview of the standard library, and since the standard librarymainly consists of user-defined types, they provide examples of what can be built using the language facilities and programming techniques presented in Chapter 2 and Chapter 3.2.3.1 StructuresThe first step in building a new type is often to organize the elements it needs into a data structure,a struct:struct Vector {int sz;// number of elementsdouble∗ elem; // pointer to elements};This first version of Vector consists of an int and a double∗.A variable of type Vector can be defined like this:Vector v;However, by itself that is not of much use because v’s elem pointer doesn’t point to anything.
To beuseful, we must give v some elements to point to. For example, we can construct a Vector like this:void vector_init(Vector& v, int s){v.elem = new double[s]; // allocate an array of s doublesv.sz = s;}That is, v’s elem member gets a pointer produced by the new operator and v’s size member gets thenumber of elements. The & in Vector& indicates that we pass v by non-const reference (§2.2.5,§7.7); that way, vector_init() can modify the vector passed to it.The new operator allocates memory from an area called the free store (also known as dynamicmemory and heap; §11.2).48A Tour of C++: The BasicsChapter 2A simple use of Vector looks like this:double read_and_sum(int s)// read s integers from cin and return their sum; s is assumed to be positive{Vector v;vector_init(v,s);// allocate s elements for vfor (int i=0; i!=s; ++i)cin>>v.elem[i];// read into elementsdouble sum = 0;for (int i=0; i!=s; ++i)sum+=v.elem[i];return sum;// take the sum of the elements}There is a long way to go before our Vector is as elegant and flexible as the standard-library vector.In particular, a user of Vector has to know every detail of Vector’s representation.
The rest of thischapter and the next gradually improve Vector as an example of language features and techniques.Chapter 4 presents the standard-library vector, which contains many nice improvements, and Chapter 31 presents the complete vector in the context of other standard-library facilities.I use vector and other standard-library components as examples• to illustrate language features and design techniques, and• to help you learn and use the standard-library components.Don’t reinvent standard-library components, such as vector and string; use them.We use . (dot) to access struct members through a name (and through a reference) and −> toaccess struct members through a pointer. For example:void f(Vector v, Vector& rv, Vector∗ pv){int i1 = v.sz;// access through nameint i2 = rv.sz;// access through referenceint i4 = pv−>sz;// access through pointer}2.3.2 ClassesHaving the data specified separately from the operations on it has advantages, such as the ability touse the data in arbitrary ways.
However, a tighter connection between the representation and theoperations is needed for a user-defined type to have all the properties expected of a ‘‘real type.’’ Inparticular, we often want to keep the representation inaccessible to users, so as to ease use, guarantee consistent use of the data, and allow us to later improve the representation.
To do that we haveto distinguish between the interface to a type (to be used by all) and its implementation (which hasaccess to the otherwise inaccessible data). The language mechanism for that is called a class. Aclass is defined to have a set of members, which can be data, function, or type members. The interface is defined by the public members of a class, and private members are accessible only throughthat interface. For example:Section 2.3.2Classesclass Vector {public:Vector(int s) :elem{new double[s]}, sz{s} { }double& operator[](int i) { return elem[i]; }int size() { return sz; }private:double∗ elem; // pointer to the elementsint sz;// the number of elements};49// construct a Vector// element access: subscriptingGiven that, we can define a variable of our new type Vector:Vector v(6);// a Vector with 6 elementsWe can illustrate a Vector object graphically:Vector:elem:sz:0:1:2:3:4:5:6Basically, the Vector object is a ‘‘handle’’ containing a pointer to the elements (elem) plus the number of elements (sz).
The number of elements (6 in the example) can vary from Vector object toVector object, and a Vector object can have a different number of elements at different times(§3.2.1.3). However, the Vector object itself is always the same size. This is the basic technique forhandling varying amounts of information in C++: a fixed-size handle referring to a variable amountof data ‘‘elsewhere’’ (e.g., on the free store allocated by new; §11.2). How to design and use suchobjects is the main topic of Chapter 3.Here, the representation of a Vector (the members elem and sz) is accessible only through theinterface provided by the public members: Vector(), operator[](), and size().
The read_and_sum()example from §2.3.1 simplifies to:double read_and_sum(int s){Vector v(s);for (int i=0; i!=v.size(); ++i)cin>>v[i];double sum = 0;for (int i=0; i!=v.size(); ++i)sum+=v[i];return sum;// make a vector of s elements// read into elements// take the sum of the elements}A ‘‘function’’ with the same name as its class is called a constructor, that is, a function used to construct objects of a class. So, the constructor, Vector(), replaces vector_init() from §2.3.1. Unlike anordinary function, a constructor is guaranteed to be used to initialize objects of its class. Thus,defining a constructor eliminates the problem of uninitialized variables for a class.50A Tour of C++: The BasicsChapter 2Vector(int) defines how objects of type Vector are constructed. In particular, it states that it needsan integer to do that. That integer is used as the number of elements.
The constructor initializesthe Vector members using a member initializer list::elem{new double[s]}, sz{s}That is, we first initialize elem with a pointer to s elements of type double obtained from the freestore. Then, we initialize sz to s.Access to elements is provided by a subscript function, called operator[]. It returns a referenceto the appropriate element (a double&).The size() function is supplied to give users the number of elements.Obviously, error handling is completely missing, but we’ll return to that in §2.4.3.