[C++ Primer Reading Notes] Day 4

Scope

Chapter 2.5-2.6

Review

Chapter 2.5 introduces more facilities that can deal with types. They are required to obey the rules that applied to built-in types and compound types we have learned. Chapter 2.6 introduces how can we write a safe header file for our data structure.

Notes

1. definitions of type aliases

A type alias is a name that is a synonym for another type. Type aliases let us simplify complicated type definitions, making those types easier to use.

The first way to define a type alias is using a keyword typedef:

The keyword typedef may appear as part of the base type of a declaration. Declarations that include typedef define type aliases rather than variables.

typedef double wages;
typedef wage base, *p;  // base is a double, p is a double *

The second way to define alias is using alias declaration:

An alias declaration starts with the keyword using followed by the alias name and an =.

using SI = Sales_item;  // SI is a synonym for Sales_item

2. the using of pointers, const, and type aliases together

It can be tempting, albeit incorrect, to interpret a declaration that use a type alias by conceptually replacing the alias with its corresponding type.

For example,

typedef char *pstring;
const pstring cstr = 0;  // cstr is a const pointer
const pstring *ps;  // ps is pointer to a const pointer to char

For the second line, if we just conceptually replace “pstring” by “char *”, which makes the declaration looks like

const char *cstr = 0;

According to this incorrect understanding, cstr becomes a pointer to const, and “const char” becomes the base type of this declaration.

According to the C++ rules, we should take the alias name as a single type name. In this way, the
“const” is used to modify “pstring”, and the base type of this declaration is “pstring”. As a result, cstr is a const pointer.

3. the auto type specifier

we can let the compiler figure out the type for us by using the auto specifier.
auto tells the compiler to deduce the type from the initializer.

By implication, a variable that uses auto as its type specifier must have an initializer.

Because a declaration can involve only a single base type, the initializers for all the variables in the declaration must have types that are consistent with each other.
a reference or a printer is part of a particular declarator and not part of the base type for the declaration. As usual, the initializers must provide consistent auto-deduced types.

For example,

auto sz = 0, pi = 3.14;  

int i = 0;
const int ci = i;
auto &n = i, *p2 = &ci;

In the first line, the first initializer is int, but the second initializer is double or float, which is not consistent with the first one. It would lead to an error.

In the fourth line, the first initializer is int, but the seconde initializer is const int, which will cause an error too.

4. the using of compound types, const, and auto

There are two rules need to be careful when we use auto. The first one is

when we use a reference as an initializer, the initializer is the corresponding object. The compiler uses that object’s type for auto’s type deduction.

The second rule is

auto ordinarily ignores top-level consts. As usual in initializations, low-level consts, such as when an initializer is a pointer to const, are kept.

For example,

int i = 0, &r = i;
const int ci = i, &cr = ci;
auto c = cr;

In the third line, cr is a reference, but in this auto declaration, the object that cr refers to is used, that is a const int. This const is a top-level const, so the compiler would give the int type to variable c.

5. the decltype type specifier

Sometimes we want to define a variable with a type that the compiler deduces from an expression but do not want to use that expression to initialize the variable. For such case, the new standard introduced a second type specifier, decltype, which returns the type of its operand. The compiler analyzes the expression to determine its type but does not evaluate the expression.

That is to say, no matter what expression is written in the decltype declaration, the expression won’t be calculated or executed.

There are a few matters that should be noticed when using decltype:

It worth noting that decltype is the only context in which a variable defined as a reference it not treated as a synonym for the object to which it refers.

some expressions will cause decltype to yield a reference type. Generally speaking, decltype returns a reference type for expressions that yield objects that can stand on the left-hand side of the assignment
the dereference operator is an example of an expression for which decltype returns a reference.
When we apply decltype to a variable without any parentheses, we get the type of that variable. If we wrap the variable’s name in one or more sets of parentheses, the compiler will evaluate the operand as an expression (that yield objects that can stand on the left-hand side of the assignment). As a result, decltype on such an expression yields a reference.

For example,

int i = 42, *p = &i, &r = i;
decltype(r+0) b;  // int
decltype(*p) c = &i; // int&

decltype((i)) d = &i;  // int&
decltype(i) d;  // int

In the second line, r is a reference, so decltype® is a reference type. However, the result of the expression r+0 is an int. We can give variable b the same type as the object that r refers. r+0 is not an expression that yields an object that can stand on the left-hand side of the assignment. So decltype(r+0) won’t give variable b the reference type.

In the third line, “*p” is an expression that yield objects that can stand on the left-hand side of the assignment. Therefore, decltype(*p) would give variable c the reference type.

In the fourth line, “(i)” is an expression that yields an object that can stand on the left-hand side of the assignment, not a single variable, in this case, an lvalue expression, whose value is identical to the value of the variable i. (the definition of lvalue will be introduced in later chapters) Therefore, decltype((i)) gives variable d the reference type.

It should be noted that, in a decltype declaration, we have to give initializers to those which are definitly reference. This is a rule that all kinds of reference have to obey.

6. differences between auto and decltype

The functions of decltype and auto are similar. Both of them make the compiler deduce the type of a variable.

However, there are some differences between them

autodecltype
ignore the top-level const. when an initializer is a reference, the object that the reference refers to decide the typedecltype returns the type of that variable, including top-level const and references.
the expression in the initializer would be calculated when compilingthe expression given to decltype woun’t be calculated or executed when compiling
the expression is used as the initializerthe expression is used in a pair of parantheses which looks like the parameter of the decltype
initializer is a mustinitializer is not a must

7. using header files safely

programs that use Sales_data will include the string header twice: once directly and once as a side effect of including Sales_data.h. Because a header might be included more than once, we need to write our headers in a way that is safe even if the header is included multiple times.

I believe that many rules in C++ are created due to different kinds of problems. The rules are reasonable.

Here is the solution to headers being included multiple times: head guards, which relies on the preprocessor.

The preprocessor–which C++ inherits from C–is a program that runs before the compiler and changes the source text of our programs.
When the preprocessor sees a #include, it replaces the #include with the contents of the specified header.

That is to say, after preprocessing, all the #include in the program will be replaced by the contents in the .h file. During this procedure, the head guards in the .h files work.

Head guards rely on preprocessor variables. Preprocessor variables have one or two possible states: defined or not defined.
The #define directive takes a name and defines that name as a preprocessor variable.
There are two other directives that test whether a given preprocessor variable has or has not been defined: #ifdef and #ifndef. If the test is true, then everything following the #ifndef is processed up to the matching #endif.

For example,

#ifndef SALES_DATA_H  
#define SALES_DATA_H
#include <string>
struct Sales_data {
	std::string bookNo;
	unsigned units_sold = 0;
	double revenue = 0.0;
};
#endif

The name of the preprocess variable is usually the uppercase version of the .h file name. It is recommended that every .h file has a header guard.

Extra Summaries

1. types that need an initializer

typereason
all referenceswe cannot rebind a reference to the second variable
all constthe value of all const variables cannot be changed
variable that uses auto as its type specifierthe compiler have to deduce the type from the initializer

2. the effect of const level in copying and initialization

No matter in copying or in initialization, the most important is :
low-level const matters more than top-level const.

In copying between two variables with different types, the top-level const can be ignored, but the low-level const decide whether this copying or conversion is legal or not.

In the initialization of an auto type, the top-level const of the initializer can be ignored, which means the top-level const feature won’t be passed to the new variable. Instead, the low-level const feature would be kept in the new variable.

Word List

type alias 类型别名
type specifier 类说明符
preprocessor 预处理器
head guard 头文件保护符

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值