ITPub博客

首页 > 应用开发 > Java > 《C++ Primer第五版》读书笔记(4)

《C++ Primer第五版》读书笔记(4)

原创 Java 作者:davidwang9527 时间:2014-02-13 16:38:10 0 删除 编辑

1      Expressions


Because there are no guarantees for how the sign bit is handled, we strongly recommend using unsigned types with the bitwise operators.



2      Statements


2.1     Null Statements


;// null statement(为啥不搞个null;之类的呢,导致下面这种容易出错的场景出来)



// disaster: extra semicolon: loop body is this null statement



while (iter != svec.end()) ; // the while body is the empty statement



++iter;  // increment is not part of the loop



Best Practices: Null statements should be commented. That way anyone reading the code can see that the statement was omitted intentionally.



 


2.2    Jump Statements


Jump statements interrupt the flow of execution. C++ offers four jumps: break, continue, and goto and return.



2.3    try Blocks &Exception handling


throw expressions, which the detecting part uses to indicate that it encountered something it can’t handle. We say that a throw raises an exception.



try blocks, which the handling part uses to deal with an exception. A try block starts with the keyword tryand ends with one or more catch clauses.



Exceptions thrown from code executed inside a tryblock are usually handled by one of the catch clauses. Because they “handle” the exception, catch clauses are also known as exception handlers.



A set of exception classes that are used to pass information about what happened between a throw and an associated catch.



In a more realistic program, the part that adds the objects might be separated from the part that manages the interaction with a user. In this case, we might rewrite the test to throw an exception rather than returning an error indicator:



If no appropriate catch is found, execution is transferred to a library function named terminate. The behavior of that function is system dependent but is guaranteed to stop further execution of the program.



3      Functions


3.1     function basics


function prototype:一个函数包括四个部分:一个返回类型、一个函数名、一个参数列表(parameter list)和函数体。前三个部分合在一起称为function prototype(函数原型),即function declaration,四个部分合在一起即function definition.



Parameters and Arguments: Arguments are the initializers for a function’s parameters.



3.2     Argument Passing


3.2.1        Passing Arguments by Value


When the argument value is copied, the parameter and argument are independent objects. We say such arguments are “passed by value” or alternatively that the function is “called by value.”



指针形参与其他非引用形参一样,对该指针的改变也是仅改变其局部拷贝,但是可以通过解引用,改变指针所指向的值。



// function that takes a pointer and sets the pointed-to value to zero



void reset(int *ip)



{



*ip = 0;  // changes the value of the object to which ip points



ip = 0;  // changes only the local copy of ip; the argument is unchanged



}



Programmers accustomed to programming in C often use pointer parameters C++ to access objects outside a function. In C++, programmers generally use reference parameters instead.



3.2.2        Passing Arguments by Reference


Reference parameters are often used to allow a function to change the value of one or more of its arguments.



// function that takes a reference to an int and sets the given object to zero
void reset(int &i)  // i is just another name for the object passed to reset
{
  i = 0;  // changes the value of the object to which i refers
}



 


Using References to Avoid Copies. It can be inefficient to copy objects of large class types or large containers. Moreover, some class types (including the IO types) cannot be copied. Functions must use reference parameters to operate on objects of a type that cannot be copied.



Reference parameters that are not changed inside a function should be references to const(而且加上const还有更多的优势,如果参数不加const,则传入的实参只能是同类型的非const类型):



// compare the length of two strings
bool isShorter(const string &s1, const string &s2)
{
 return s1.size() < s2.size();
}



 


如果要改变指针,那么可以使用指向指针的引用,语法如下:int *&v1;这种定义从右至左理解:v1是一个引用,它与指向int型的对象相关联。



Vector和其他容器类型的形参,从避免复制的角度,应该传递引用,然而C++程序员更倾向于传递容器要处理的元素的迭代器,一般是开始和结束元素。



3.2.3     Array Reference Parameters


数组形参与其他形参有两个不同:不能复制数组,使用数组名时自动转换为第一个元素的指针。数组形参的定义有三种方式:void printvalue(int *); void printValues(int []); int printValue(int [10]);第三种方式很容易引起误解,因为实际上编译器并不检查该长度,也不会限制传入的参数的长度。通过数组形参的任何改变都会改变数组本身,因此在不需要修改数组形参的元素时,应将形参定义为const



通过引用传递数组时,写法为int (&attr)[10],这时长度是有意义的,限制实参必须为这个长度的数组。另外,这种写法必须加括号, int &attr[10]是一个引用数组,而不是对一个数组的引用。



// ok: parameter is a reference to an array; the dimension is part of the type
void print(int (&arr)[10])
{
for (auto elem : arr)
   cout << elem << endl;



}



3.2.4        Functions with Varying Parameters


Sometimes we do not know in advance how many arguments we need to pass to a function. The new standard provides two primary ways to write a function that takes a varying number of arguments: If all the arguments have the same type, we can pass a library type named initializer_list. If the argument types vary, we can write a special kind of function, known as a variadic template.



clip_image002



void error_msg(initializer_list il)



{



for (auto beg = il.begin(); beg != il.end(); ++beg)



cout << *beg << " " ;



cout << endl;



}



 


C++ also has a special parameter type, ellipsis, that can be used to pass a varying number of arguments. However, it is worth noting that this facility ordinarily should be used only in programs that need to interface to C functions.



Ellipsis parameters are in C++ to allow programs to interface to C code that uses a C library facility named varargs. Generally an ellipsis parameter should not be used for other purposes.



void foo(parm_list, ...);



void foo(...);



3.3     Return Types and the return Statement


3.3.1        Functions That Return a Value


1.Failing to provide a return after a loop that contains a return is an error. However, many compilers will not detect such errors(太不人性化了,g++4.8.2也不检测,为毛啊):



bool str_subrange(const string &str1, const string &str2)
{
// same sizes: return normal equality test
if (str1.size() == str2.size())
return str1 == str2;  // ok: == returns bool
// find the size of the smaller string; conditional operator, see § 4.7 (p. 151)
auto size = (str1.size() < str2.size())? str1.size() : str2.size();
// look at each element up to the size of the smaller string
for (decltype(size) i = 0; i != size; ++i) {
   if (str1[i] != str2[i])
   return; // error #1: no return value; compiler should detect this error



}



// error #2: control might flow off the end of the function without a return



// the compiler might not detect this error



}



 


2.We can return a value or a reference or a point, but Never Return a Reference or Pointer to a Local Object. One good way to ensure that the return is safe is to ask: To what preexisting object is the reference referring?



For the same reasons that it is wrong to return a reference to a local object, it is also wrong to return a pointer to a local object.



3. Whether a function call is an lvalue depends on the return type of the function. Calls to functions that return references are lvalues; other return types yield rvalues. In particular, we can assign to the result of a function that returns a reference to nonconst:



char&get_val(string &str, string::size_type ix)
{
  return str[ix]; // get_val assumes the given index is valid
}



int main()
{
   string s("a value");
   cout << s << endl;  // prints a value
   get_val(s, 0) = 'A'; // changes s[0] to A
   cout << s << endl;  // prints A value
   return 0;
}



If the return type is a reference to const, then (as usual) we may not assign to the result of the call:



shorterString("hi","bye") = "X"; // error: return value is const



 


4.List Initializing the Return Value:



Under the new standard, functions can return a braced list of values.



vector process()



{
// . . .
// expected and actual are strings
if (expected.empty())
    return {};  // return an empty vector
else if (expected == actual)



return {"functionX", "okay"}; // return list-initialized vector



else



return {"functionX", expected, actual};



}



3.3.2        Returning a Pointer to an Array


Because we cannot copy an array, a function cannot return an array. However, a function can return a pointer or a reference to an array.



int (*func(int i))[10];



To understand this declaration, it can be helpful to think about it as follows:



? func(int) says that we can call func with an int argument.
? (*func(int)) says we can dereference the result of that call.
? (*func(int))[10] says that dereferencing the result of a call to func yields an array of size ten.
? int  (*func(int))[10] says the element type in that array is int.



Fortunately, there are ways to simplify such declarations.



1.The most straightforward way is to use a type alias



typedef int arrT[10];  // arrT is a synonym for the type array of ten ints
using arrtT = int[10]; // equivalent declaration of arrT; see § 2.5.1 (p. 68)
arrT* func(int i);  // func returns a pointer to an array of ten ints



2. Under the new standard, another way to simplify the declaration of func is by using a trailing return type. A trailing return type follows the parameter list and is preceded by ->. To signal that the return follows the parameter list, we use auto where the return type ordinarily appears:



auto func(int i) -> int(*)[10];



Because the return type comes after the parameter list, it is easier to see that func returns a pointer and that that pointer points to an array of ten ints.



3.As another alternative, if we know the array(s) to which our function can return a pointer, we can use decltype to declare the return type. For example, the following function returns a pointer to one of two arrays, depending on the value of its parameter:



int odd[] = {1,3,5,7,9};
int even[] = {0,2,4,6,8};
// returns a pointer to an array of five int elements
decltype(odd) *arrPtr(int i)
{
  return (i % 2) ? &odd : &even; // returns a pointer to the array
}



3.4     Overloaded functions


C++中,出现在相同作用域的两个函数,如果函数名相同,但是参数列表不同,则为函数重载。



如果仅仅是返回值不同,则C++认为是非法的,因为在传统的C语言中每个函数在调用时,都可以不取其返回值。



functions taking const and nonconst references or pointers have different parameters:
Record lookup(Account&);  // function that takes a reference to Account
Record lookup(const Account&); // new function that takes a const reference



Record lookup(Account*);  // new function, takes a pointer to Account
Record lookup(const Account*); // new function, takes a pointer to const



Advice: Although overloading lets us avoid having to invent (and remember) names for common operations, we should only overload operations that actually do similar things. There are some cases where providing different function names adds information that makes the program easier to understand.



 


the compiler will prefer the nonconst versions when we pass a nonconst object or pointer to nonconst.



 


const_casts are most useful in the context of overloaded functions



// return a reference to the shorter of two strings



const string &shorterString(const string &s1, const string &s2)
{
 return s1.size() <= s2.size() ? s1 : s2;
}



string &shorterString(string &s1, string &s2)
{
  auto &r = shorterString(const_cast(s1),
  const_cast(s2));
  return const_cast(r);



}



重载有作用域问题,如果在局部又声明了一个与外层作用域同函数名的函数,那么函数将被覆盖而不是被重载,即外层所有定义的函数都将失效,因为In C++, name lookup happens before type checking.  Bad practice: usually it's a bad idea to declare functions at local scope.



3.5     Features for Specialized Uses


3.5.1        Default Arguments


默认参数默认参数必须出现在参数列表的最后部分。



个人认为默认参数最好使的地方在于:如果需要对一个函数增加几个参数时,后面新增加的参数都设置为有默认参数,则使得前面已经写好的客户代码都不需要改动,这是非常有意义的。



3.5.2        inline &constexpr function


A function specified as inline(usually) is expanded “in line” at each call to Avoid Function Call Overhead. The call would be expanded during compilation, the run-time overhead of making shorter String a function is thus removed.



The inline specification is only a request to the compiler. The compiler may choose to ignore this request.



In general, the inlinemechanism is meant to optimize small, straight-line functions that are called frequently. Many compilers will not inline a recursive function. A 75-line function will almost surely not be expanded inline.



A constexpr function is a function that can be used in a constant expression.



A constexpr function is permitted to return a value that is not a constant(A constexpr function is not required to return a constant expression):



// scale(arg) is a constant expression if arg is a constant expression



constexpr size_t scale(size_t cnt) { return new_sz() * cnt; }



The scale function will return a constant expression if its argument is a constant expression but not otherwise.



Put inline and constexpr Functions in Header Files: The compiler needs the definition, not just the declaration, in order to expand the code. inline and constexpr functions normally are defined in headers.



3.5.3        Aids for Debugging


This approach uses two preprocessor facilities: assert and NDEBUG.



assert is a preprocessor macro(The assertmacro is defined in the cassertheader.):



assert(expr);



The assert macro is often used to check for conditions that “cannot happen.” but should not be used to substitute for runtime logic checks or error checking that the program should do.



 


The NDEBUG Preprocessor Variable



The behavior of assert depends on the status of a preprocessor variable named NDEBUG. If NDEBUG is defined, assert does nothing. By default, NDEBUG is not defined, so, by default, assert performs a run-time check.



We can “turn off” debugging by providing a #define to define NDEBUG. Alternatively, most compilers provide a command-line option that lets us define preprocessor variables:



$CC -D NDEBUG main.C



It can be useful as an aid in getting a program debugged but should not be used to substitute for runtime logic checks or error checking that the program should do.



In addition to using assert, we can write our own conditional debugging code using NDEBUG. If NDEBUG is not defined, the code between the #ifndef and the #endif is executed. If NDEBUG is defined, that code is ignored.



C++ compiler defines a local static array of const charthat holds the name of the function:_ _func_ _



the preprocessor defines four other names that can be useful in debugging:



__FILE_ _     string literal containing the name of the file



_ _LINE_ _    integer literal containing the current line number



_ _TIME_ _   string literal containing the time the file was compiled



__DATE_ _   string literal containing the date the file was compiled



3.6     Pointers to Functions


3.6.1        declaration


A function pointer is just that—a pointer that denotes a function rather than an object. Like any other pointer, a function pointer points to a particular type. A function’s type is determined by its return type and the types of its parameters. The function’s name is not part of its type.



// compares lengths of two strings
bool lengthCompare(const string &, const string &);



has type bool(const string&, const string&). To declare a pointer that can point at this function, we declare a pointer in place of the function name:



bool (*pf)(const string &, const string &);  // uninitialized



Starting from the name we are declaring, we see that pfis preceded by a *, so pf is a pointer. To the right is a parameter list, which means that pfpoints to a function. Looking left, we find that the type the function returns is bool. Thus, pfpoints to a function that has two const string¶meters and returns bool.



The parentheses around *pfare necessary. If we omit the parentheses, then we declare pf as a function that returns a pointer to bool:



// declares a function named pf that returns a bool*
bool *pf(const string &, const string &);



3.6.2        using pointers to functions


pf= lengthCompare;  // pf now points to the function named lengthCompare
pf = &lengthCompare; // equivalent assignment: address-of operator is optional(
飞机,又见飞机)



bool b1 = pf("hello", "goodbye");  // calls lengthCompare
bool b2 = (*pf)("hello", "goodbye"); // equivalent call
bool b3 = lengthCompare("hello", "goodbye"); // equivalent call



3.6.3     Function Pointer Parameters


Just as with arrays, we cannot define parameters of function type, but can have a parameter that is a pointer to function. As with arrays, we can write a parameter that looks like a function type, but it will be treated as a pointer:



// third parameter is a function type and is automatically treated as a pointer to function
void useBigger(const string &s1, const string &s2,bool pf(const string &, const string &));



// equivalent declaration: explicitly define the parameter as a pointer to function
void useBigger(const string &s1, const string &s2, bool (*pf)(const string &, const string &));



// automatically converts the function lengthCompare to a pointer to function
useBigger(s1, s2, lengthCompare);



别名:



// Func and Func2 have function type



typedef bool Func(const string&, const string&);
typedef decltype(lengthCompare) Func2; // equivalent type



 


// FuncP and FuncP2 have pointer to function type



typedef bool(*FuncP)(const string&, const string&);
typedef decltype(lengthCompare) *FuncP2;  // equivalent type



It is important to note that decltype returns the function type; the automatic conversion to pointer is not done.



Because decltypereturns a function type, if we want a pointer we must add the * ourselves.



We can redeclare useBigger using any of these types:



// equivalent declarations of useBigger using type aliases
void useBigger(const string&, const string&, Func);
void useBigger(const string&, const string&, FuncP2);



Both declarations declare the same function. In the first case, the compiler will automatically convert the function type represented by Functo a pointer.



3.6.4        Returninga Pointer to Function


we must write the return type as a pointer type;the compiler will not automatically treat a function return type as the corresponding pointer type.



by far the easiest way to declare a function that returns a pointer to function is by using a type alias:



usingF = int(int*, int);  // F is a function type, not a pointer



using PF = int(*)(int*, int); // PF is a pointer type



PF  f1(int); // ok: PF is a pointer to function; f1 returns a pointer to function
F  f1(int);  // error: F is a function type; f1 can't return a function
F  *f1(int); // ok: explicitly specify that the return type is a pointer to function



Of course, we can also declare f1directly, which we’d do as



int(*f1(int))(int*, int);



Reading this declaration from the inside out, we see that f1has a parameter list, so f1 is a function. f1 is preceded by a *so f1returns a pointer. The type of that pointer itself has a parameter list, so the pointer points to a function. That function returns an int.



it’s worth noting that we can simplify declarations of functions that return pointers to function by using a trailing return:
auto f1(int) -> int (*)(int*, int);



Using auto or decltype for Function Pointer Types



string::size_type sumLength(const string&, const string&);
string::size_type largerLength(const string&, const string&);
// depending on the value of its string parameter,
// getFcn returns a pointer to sumLength or to largerLength
decltype(sumLength) *getFcn(const string &);



local static objects:Local objects whose value persists across calls to the function. Local static objects that are created and initialized before control reaches their use and are destroyed when the program ends.



4      Classes


The fundamental ideas behind classes are data abstraction and encapsulation. Data abstraction is a programming (and design) technique that relies on the separation of interface and implementation. The interface of a class consists of the operations that users of the class can execute. The implementation includes the class’ data members, the bodies of the functions that constitute the interface, and any functions needed to define the class that are not intended for general use.



Encapsulation enforces the separation of a class’ interface and implementation. A class that is encapsulated hides its implementation—users of the class can use the interface but have no access to the implementation.



A class that uses data abstraction and encapsulation defines an abstract data type. In an abstract data type, the class designer worries about how the class is implemented. Programmers who use the class need not know how the type works.They can instead think abstractlyabout what the type does.



4.1     Defining Abstract Data Types


4.1.1        User的概念


Programmers tend to think about the people who will run their applications as users.Similarly a class designer designs and implements a class for users of that class. In this case, the user is a programmer, not the ultimate user of the application.



When we refer to a user, the context makes it clear which kind of user is meant. If we speak of user code or the user of the Sales_dataclass, we mean a programmer who is using a class. If we speak of the user of the bookstore application, we mean the manager of the store who is running the application.



C++ programmers tend to speak of users interchangeably as users of the application or users of a class.



4.1.2     const Member Functions


std::string isbn() const { return this->bookNo; }



The keyword const that follows the parameter list is to modify the type of the implicit this pointer. A const following the parameter list indicates that this is a pointer to const. Member functions that use const in this way are const member functions.



The fact that this is a pointer to const means that const member functions cannot change the object on which they are called. Thus, isbn may read but not write to the data members of the objects on which it is called.



Objects that are const, and references or pointers to const objects, may call only const member functions.



Defining a Member Function outside the Class:



double Sales_data::avg_price() const {…}



4.1.3     Defining a Function to Return “This” Object


Sales_data& Sales_data::combine(const Sales_data &rhs)
{
    units_sold += rhs.units_sold; // add the members of rhs into
    revenue += rhs.revenue;  // the members of ''this'' object
    return *this; // return the object on which the function was called
}



To return an lvalue, our combinefunction must return a reference. Because the left-hand operand is a Sales_dataobject, the return type is Sales_data&.



4.1.4     Defining Nonmember Class-Related Functions


Although nonmember Class-Related Functions are conceptually part of the interface of the class, they are not part of the class itself.Functions that are conceptually part of a class, but not defined inside the class, are typically declared (but not defined) in the same header as the class itself.



istream &read(istream &is, Sales_data &item)
{
  double price = 0;
  is >> item.bookNo >> item.units_sold >> price;
  item.revenue = price * item.units_sold;
  return is;
}



The IO classes are types that cannot be copied, so we may only pass them by reference. Moreover, reading or writing to a stream changes that stream, so both functions take ordinary references, not references to const. The second thing to note is that print does not print a newline. Ordinarily, functions that do output should do minimal formatting.



4.1.5     Constructors


The default constructor is one that takes no arguments. Thecompiler-generated constructor is known as the synthesized default constructor.



Synthesized default constructor(有这个功能但是又有第二条风险,干脆取消算了)



1.The compiler generates a default constructor automatically only if a class declares no constructors.



2.Members of built-in type that are default initialized have undefined value,so Classes that have members of built-in or compound type usually should rely on the synthesized default constructor onlyif all such members have in-class initializers.



3.A third reason that some classes must define their own default constructor is that sometimes the compiler is unable to synthesize one. For example, if a class has a member that has a class type, and that class doesn’t have a default constructor, then the compiler can’t initialize that member.



Constructor Initializer List



The constructor initializer is a list of member names, each of which is followed by that member’s initial value in parentheses (or inside curly braces). Multiple member initializations are separated by commas.



Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(p*n) { }



4.2     Access Control and Encapsulation


4.2.1        access specifier


    access specifiers to enforce encapsulation: public,protected,private



它有两个重要意义:一是对于客户程序而言,它只需要关心public部分的定义;二是它使得编写该应用的人可以在提供应用程序接口后,随意更改其中的实现本身。这使得在设计类的时候,最主要的部分在于设计public部分,也就是与客户程序的接口部分。



Structclass唯一的区别在于,struct默认的类成员和成员函数都是公共的,而class默认的都是私有的。



4.2.2        friend


A class can allow another class or function to access its nonpublic members by making that class or function a friend.



class Sales_data {
// friend declarations for nonmember Sales_data operations added
friend Sales_data add(const Sales_data&, const Sales_data&);
friend std::istream &read(std::istream&, Sales_data&);
friend std::ostream &print(std::ostream&, const Sales_data&);
// other members and access specifiers as before
public:
   Sales_data() = default;
   Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n),        revenue(p*n) { }
   Sales_data(const std::string &s): bookNo(s) { }
   Sales_data(std::istream&);
   std::string isbn() const { return bookNo; }
   Sales_data &combine(const Sales_data&);
private:
  std::string bookNo;
  unsigned units_sold = 0;
  double revenue = 0.0;



};



// declarations for nonmember parts of the Sales_data interface



Sales_data add(const Sales_data&, const Sales_data&);
std::istream &read(std::istream&, Sales_data&);
std::ostream &print(std::ostream&, const Sales_data&);



 


Friendship between Classes



class Screen {



// Window_mgr memberscan access the private parts of class Screen



friend class Window_mgr;



// ... rest of the Screen class



}



 


Making A Member Function a Friend



class Screen {



// Window_mgr::clear must have been declared before class Screen



friend void Window_mgr::clear(ScreenIndex);



// ... rest of the Screen class



};



4.3     Additional features


4.3.1        Mutable Data Members


Mutable Data Members: It sometimes (but not very often) happens that a class has a data member that we want to be able to modify, even inside a const member function. We indicate such members by including the mutable keyword in their declaration.



A mutable data member is never const, even when it is a member of a const object. Accordingly, a const member function may change a mutable member.



As an example, we’ll give Screena mutablemember named access_ctr, which we’ll use to track how often each Screen member function is called:



classScreen {
public:
    void some_member() const;
private:
    mutable size_t access_ctr; // may change even in a const object
    // other members as before



};



void Screen::some_member() const
{
   ++access_ctr;  // keep a count of the calls to any member function
   // whatever other work this member needs to do
}



access_ctr member is a mutable member, so any member function, including const functions, can change its value.



4.3.2     Initializers for Data Members of Class Type


Under the new standard, the best way to initiliaze a member of class type is as an in-class initializer:



Class Window_mgr {
private:
// Screens this Window_mgr is tracking
// by default, a Window_mgr has one standard sized blank Screen
std::vector screens{Screen(24, 80, ' ') };
};



A const member function that returns *this as a reference should have a return type that is a reference to const.



Every class defines a unique type. Two different classes define two different types even if they define the same members.



4.3.3        delegating constructor


A delegating constructor uses another constructor from its own class to perform its initialization. It is said to “delegate” some (or all) of its work to this other constructor.



class Sales_data {
public:
    // nondelegating constructor initializes members from corresponding arguments
    Sales_data(std::string s, unsigned cnt, double price):bookNo(s), units_sold(cnt), revenue(cnt*price) {}
    // remaining constructors all delegate to another constructor
    Sales_data(): Sales_data("", 0, 0) {}
    Sales_data(std::string s): Sales_data(s, 0,0) {}
    Sales_data(std::istream &is): Sales_data(){ read(is, *this); }
    // other members as before



};



In practice, it is almost always right to provide a default constructor if other constructors are being defined



4.3.4        Implicit Class-Type Conversions


A constructor that can be called with a single argument defines an implicit conversion from the constructor’s parameter type to the class type. Such constructors are sometimes referred to as converting constructors.



string null_book = "9-999-99999-9";
// constructs a temporary Sales_data object



// with units_sold and revenue equal to 0 and bookNo equal to null_book
item.combine(null_book);



We can prevent the use of a constructor in a context that requires an implicit conversion by declaring the constructor as explicit:



class Sales_data {
public:
    Sales_data() = default;
    Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(p*n) { }
    explicit Sales_data(const std::string &s): bookNo(s) { }
    explicit Sales_data(std::istream&);
    // remaining members as before



};



When a constructor is declared explicit, it can be used only with the direct form of initialization. We cannot use an explicit constructor with this form of initialization:



Sales_dataitem1 (null_book);  // ok: direct initialization
// error: cannot use the copy form of initialization with an explicit constructor
Sales_data item2 = null_book;



4.3.5        Literal Classes(没有搞懂啥用处,暴汗)


An aggregate class gives users direct access to its members and has special initialization syntax:



For example, the following class is an aggregate:
structData {
   int ival;
   string s;
};



Data val1 = { 0, "Anna" };



classes that are literal types may have function members that are constexpr. Such members must meet all the requirements of a constexpr function. These member functions are implicitly const.



Anaggregate class (§ 7.5.5, p. 298) whose data members are all of literal type is a literal class.



A nonaggregate class, that meets the following restrictions, is also a literal class:



?The data members all must have literal type.
?The class must have at least one constexprconstructor.
?If a data member has an in-class initializer, the initializer for a member of builtin type must be a constant expression, or if the member has class type, the initializer must use the member’s own constexpr constructor.
?The class must use default definition for its destructor, which is the member
that destroys objects of the class type.



Although constructors can’t be const, constructors in a literal class can be constexpr functions. Indeed, a literal class must provide at least one constexpr constructor.



class Debug {
public:
   constexpr Debug(bool b = true): hw(b), io(b), other(b) {}
   constexpr Debug(bool h, bool i, bool o):hw(h), io(i), other(o) {}
   constexpr bool any() { return hw || io || other; }
   void set_io(bool b) { io = b; }
   void set_hw(bool b) { hw = b; }
   void set_other(bool b) { hw = b; }



private:
   bool hw;  // hardware errors other than IO errors
   bool io;  // IO errors
   bool other; // other errors



};



A constexpr constructor must initialize every data member. The initializers must either use a constexprconstructor or be a constant expression.



constexpr Debug io_sub(false, true, false);  // debugging IO
if (io_sub.any())  // equivalent to if(true)
   cerr << "print appropriate error messages" << endl;
constexpr Debug prod(false); // no debugging during production
if (prod.any())  // equivalent to if(false)
   cerr << "print an error message" << endl;



4.4     static Class Members


As an example, we’ll define a class to represent an account record at a bank:



class Account {
public:
   void calculate() { amount += amount * interestRate; }
   static double rate() { return interestRate; }
   static void rate(double);



private:
   std::string owner;
   double amount;
   static double interestRate;
   static double initRate();



};



static member functions are not bound to any object; they do not have a this pointer. As a result, static member functions may not be declared as const, and we may not refer to this in the body of a static member. This



We can access a static member directly through the scope operator:



double r;
r = Account::rate(); // access a static member using the scope operator



Even though staticmembers are not part of the objects of its class, we can use an object, reference, or pointer of the class type to access a staticmember.Member functions can use staticmembers directly, without the scope operator. (多此一举,徒增复杂性,全都要求scope operator就结了)



Because static data members are not part of individual objects of the class type,they are not defined when we create objects of the class. As a result, they are not initialized by the class’ constructors. Moreover, in general, we may not initialize a static member inside the class. Instead, we must define and initialize each static data member outside the class body. Like any other object, a static data member may be defined only once.



Like global objects staticdata members are defined outside any function. Hence, once they are defined, they continue to exist until the program completes.



// define and initialize a static class member
double Account::interestRate = initRate();



The best way to ensure that the object is defined exactly once is to put the definition of staticdata members in the same file that contains the definitions of the class noninline member functions.



static Members Can Be Used in Ways Ordinary Members Can’t



1.a static data member can have incomplete type. In particular, a static data member can have the same type as the class type of which it is a member. A nonstatic data member is restricted to being declared as a pointer or a reference to an object of its class:



class Bar {
  public:
     // ...
private:
     static Bar mem1; // ok: static member can have incomplete type
     Bar  *mem2;  // ok: pointer member can have incomplete type
     Bar  mem3;  // error: data members must have complete type



};



2. Another difference between static and ordinary members is that we can use a static member as a default argument:



class Screen {
public:
    // bkground refers to the static member
    // declared later in the class definition
    Screen& clear(char = bkground);



private:
    static const char bkground;
};



A nonstatic data member may not be used as a default argument because its value is part of the object of which it is a member.



来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/29464142/viewspace-1080983/,如需转载,请注明出处,否则将追究法律责任。

下一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2014-01-28

  • 博文量
    3
  • 访问量
    21560