最近在看这个:LearnCpp
主要是一些我自己容易忽视的地方
记一些笔记在下面,还有错漏地方请不吝赐教
The preprocessor copies the contents of the included file into the including file at the point of the #include directive
Conditional Compilation:
#ifndef SOME_UNIQUE_NAME_HERE
#define SOME_UNIQUE_NAME_HERE
#endif
Favor implicit initialization over explicit initialization
int nValue = 5; // explicit initialization
int nValue(5); // implicit initialization
Uniform(list) initialization in C++11
int value{}; // default initialization to 0
int value{4.5}; // error: an integer variable can not hold a non-integer value
If you’re using a C++11 compatible compiler, favor uniform initialization
While short int, long int, and long long int are valid, the shorthand versions short, long, and long long should be preferred. In addition to being less typing, adding the prefix int makes the type harder to distinguish from variables of type int. This can lead to mistakes if the short or long modifier is inadvertently missed.
long long int lli; // valid
long long ll; // preferred
All integer variables except char are signed by default. Char can be either signed or unsigned by default (but is usually signed for conformity).
Generally, the signed keyword is not used (since it’s redundant), except on chars (when necessary to ensure they are signed).
Favor signed integers over unsigned integers
<cstdint>:fixed width integers: int8_t/uint8_t/...uint64_t
Until this is clarified by a future draft of C++, you should assume that int8_t and uint8_t may or may not behave like char types.
int can be used when the integer size doesn’t matter and isn’t going to be large.
Fixed-width integers should be used in all other cases.
Only use unsigned types if you have a compelling reason.
Summation precision: Kahan summation algorithm
#include <iomanip> // for std::setprecision()
Float values have between 6 and 9 digits of precision, with most float values having at least 7 significant digits. Double values have between 15 and 18 digits of precision, with most double values having at least 16 significant digits. Long double has a minimum precision of 15, 18, or 33 significant digits depending on how many bytes it occupies.
Favor double over float unless space is at a premium, as the lack of precision in a float will often lead to challenges.
Rounding Error matters 0.1+...+0.1 \neq 1.0. Ref. CH3.5 -- Relational operators
Note that even though cin will let you enter multiple characters, ch will only hold 1 character. Consequently, only the first input character is placed in ch. The rest of the user input is left in the input buffer that cin uses, and can be accessed with subsequent calls to cin.
\n and endl:
Use std::endl when you need to ensure your output is output immediately (e.g. when writing a record to a file, or when updating a progress bar). Note that this may have a performance cost, particularly if writing to the output device is slow (e.g. when writing a file to a disk).
Use ‘\n’ in other cases.
wchar_t should be avoided in almost all cases (except when interfacing with the Windows API). Its size is implementation defined, and is not reliable. It has largely been deprecated.
You won’t need to use char16_t(UTF-16) or char32_t(UTF-32) unless you’re planning on making your program Unicode compatible and you are using 16-bit or 32-bit Unicode characters.
Making a function parameter const does two things. First, it tells the person calling the function that the function will not change the value of myValue. Second, it ensures that the function doesn’t change the value of myValue.
Any variable that should not change values after initialization should be declared as const (or constexpr in C++11).
Avoid using #define to create symbolic constants, but use const variables to provide a name and context for your magic numbers.
A recommended way:
Use the scope resolution operator (::) to access your constants in .cpp files
Prior to C++11, if either of the operands of integer division are negative, the compiler is free to round up or down! For example, -5 / 2 can evaluate to either -3 or -2, depending on which way the compiler rounds. However, most modern compilers truncate towards 0 (so -5 / 2 would equal -2). The C++11 specification changed this to explicitly define that integer division should always truncate towards 0 (or put more simply, the fractional component is dropped).
Also prior to C++11, if either operand of the modulus operator is negative, the results of the modulus can be either negative or positive! For example, -5 % 2 can evaluate to either 1 or -1. The C++11 specification tightens this up so that a % b always resolves to the sign of a.
Be aware of undefined expressions like:x = x++;
Don’t use a variable that has a side effect (if it modifies some state) applied to it more than once in a given statement.
Avoid using the comma operator, except within for loops.
Only use the conditional operator for simple conditionals where it enhances readability.
It’s worth noting that the conditional operator evaluates as an expression, whereas if/else evaluates as statements. This means the conditional operator can be used in some places where if/else can not.
For example, when initializing a const variable:
bool inBigClassroom = false;
const int classSize = inBigClassroom ? 30 : 20;
There’s no satisfactory if/else statement for this, since const variables must be initialized when defined, and the initializer can’t be a statement.
Directly comparing floating point values using any of these operators is dangerous. This is because small rounding errors in the floating point operands may cause unexpected results.
Donald Knuth, a famous computer scientist, suggested the following method in his book “The Art of Computer Programming, Volume II: Seminumerical Algorithms (Addison-Wesley, 1969)”:
#include
// return true if the difference between a and b is within epsilon percent of the larger of a and b
bool approximatelyEqual(double a, double b, double epsilon)
{
return fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}
The author suggests the following approach
// return true if the difference between a and b is less than absEpsilon, or within relEpsilon percent of the larger of a and b
bool approximatelyEqualAbsRel(double a, double b, double absEpsilon, double relEpsilon)
{
// Check if the numbers are really close -- needed when comparing numbers near zero.
double diff = fabs(a - b);
if (diff <= absEpsilon)
return true;
// Otherwise fall back to Knuth's algorithm
return diff <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * relEpsilon);
}
Comparison of floating point numbers is a difficult topic, and there’s no “one size fits all” algorithm that works for every case.
Any non-zero integer value evaluates to true when used in a boolean context. Mixing integer and boolean operations can be very confusing, and should be avoided!
Short circuit evaluation presents another opportunity to show why operators that cause side effects should not be used.
When dealing with bit operators, use unsigned integers.
Note that the results of applying the bitwise shift operators to a signed integer are compiler dependent.
Bit mask and bit flags:
Bit flags are typically used in two cases:
manage bitflags:std::bitset
Bit mask: one application: color channel
Note that variables inside nested blocks can have the same name as variable inside outer blocks. When this happens, the nested variable “hides” the outer variable. This is called name hiding or shadowing.
By convention, many developers prefix global variable names with “g_” to indicate that they are global. This both helps identify global variables as well as avoids naming conflicts with local variables.
By default, non-const variables declared outside of a block are assumed to be external. However, const variables declared outside of a block are assumed to be internal.
Encapsulate the global variable.
Static variables offer some of the benefit of global variables (they don’t get destroyed until the end of the program) while limiting their visibility to block scope. This makes them much safer for use than global variables.
When applied to a global variable, the static keyword defines the global variable as having internal linkage, meaning the variable cannot be exported to other files.
When applied to a local variable, the static keyword defines the local variable as having static duration, meaning the variable will only be created once, and will not be destroyed until the end of the program.
Don’t use the “using” keyword in the global scope. This includes header files!
Implicit Conversion: Numeric Promotion (no data loss) and Numeric Conversion (might lose data)
Conversion in the compiler:
Const casts and reinterpret casts should generally be avoided because they are only useful in rare cases and can be harmful if used incorrectly.
Because C-style casts are not checked by the compiler at compile time, C-style casts can be inherently misused, thus: Avoid C-style casts
The main advantage of static_cast
is that it provides compile-time type checking, making it harder to make an inadvertent error. Static_cast
is also (intentionally) less powerful than C-style casts, so you can’t inadvertently remove const or do other things you may not have intended to do.
Using casts to make implicit type conversions clear
Use std::getline()
to input text of a whole line
To read a full line of input into a string, you’re better off using the std::getline() function instead.
If reading numeric values with std::cin
, it’s a good idea to remove the extraneous newline using std::cin.ignore(). (std::cin.ignore(32767, '\n');)
Best practice: Don’t assign specific values to your enumerators.
Rule: Don’t assign the same value to two enumerators in the same enumeration unless there’s a very good reason.
C++11 defines a new concept, the enum class (also called a scoped enumeration), which makes enumerations both strongly typed and strongly scoped.
Typedefs allow you to change the underlying type of an object without having to change lots of code.
One big advantage of typedefs is that they can be used to hide platform specific details. On some platforms, an integer is 2 bytes, and on others, it is 4. Thus, using int to store more than 2 bytes of information can be potentially dangerous when writing platform independent code.
Warning: One of the easiest mistakes to make in C++ is to forget the semicolon at the end of a struct declaration. This will cause a compiler error on the next line of code. Modern compilers like Visual Studio 2010 will give you an indication that you may have forgotten a semicolon, but older or less sophisticated compilers may not, which can make the actual error hard to find.
C++ supports a faster way to initialize structs using an initializer list
Employee joe = { 1, 32, 60000.0 }; // joe.id = 1, joe.age = 32, joe.wage = 60000.0
Employee frank = { 2, 28 }; // frank.id = 2, frank.age = 28, frank.wage = 0.0 (default initialization)
Uniform initialization:
Employee joe { 1, 32, 60000.0 }; // joe.id = 1, joe.age = 32, joe.wage = 60000.0
Employee frank { 2, 28 }; // frank.id = 2, frank.age = 28, frank.wage = 0.0 (default initialization)
A function can also return a struct, which is one of the few ways to have a function return multiple variables.
It turns out, we can only say that the size of a struct will be at least as large as the size of all the variables it contains. But it could be larger! For performance reasons, the compiler will sometimes add gaps into structures (this is called padding).