exiler

exiler

C++ Introduction - 1

Basic Built-in Types#

Different compilers and platforms may have variations. The following table is an example using MinGw GCC 9.2.0 32_bit in a Windows_X64 environment:

TypeMeaningMinimum SizeRange
boolBoolean Type8bitstrue or false
charCharacter8bits-2^7 ~ 2^7-1
wchar_tWide Character16bits0 ~ 2^16 - 1
char16_tUnicode Character16bits0 ~ 2^16 - 1
char32_tUnicode Character32bits0 ~ 2^32 - 1
shortShort Integer16bits-2^15 ~ 2^15-1
intInteger32bits-2^31 ~ 2^31-1
longLong Integer32bits-2^31 ~ 2^31-1
long longLong Integer64bits-2^63 ~ 2^63-1
floatSingle Precision Float32bitsApproximately 7 significant digits
doubleDouble Precision Float64bitsApproximately 15 significant digits
long doubleExtended Precision Float96bitsHigher significant digits

You can compile and run the following code in the IDE to check the sizes of arithmetic types on your machine.

// Example using MinGw GCC 9.2.0 32_bit in a Windows_X64 environment
// 1Byte = 8bit
#include<iostream>
using namespace std;
int main(){
  cout<< "int * "<<sizeof(int*)*8<<endl; // 32 bits
  cout<< "bool "<<sizeof(bool)*8<<endl;  // 8 bits
  cout<< "char "<<sizeof(char)*8<<endl;  // 8bits
  cout<< "wchar_t "<<sizeof(wchar_t)*8<<endl;  // 16bits
  cout<< "char16_t "<<sizeof(char16_t )*8<<endl;  // 16bits
  cout<< "char32_t "<<sizeof(char32_t )*8<<endl;  // 32bits
  cout<< "short "<<sizeof(short )*8<<endl;  // 16bits
  cout<< "int "<<sizeof(int )*8<<endl;  // 32bits
  cout<< "long "<<sizeof(long )*8<<endl;  // 32bits
  cout<< "long long"<<sizeof(long long)*8<<endl;  // 64bits
  cout<< "float "<<sizeof(float)*8<<endl;  // 32bits
  cout<< "double "<<sizeof(double)*8<<endl; // 64bits
  cout<< "long double "<<sizeof(long double)*8<<endl; //96bits
return 0;
}

Machine Implementation of Built-in Types#

A byte is the smallest addressable memory block, 1 byte = 8 bits.

A word is the basic unit of storage, 1 word = 4 bytes or 8 bytes.

Each byte in memory is associated with a number (called an address), and we can use an address to represent a bit string of varying sizes starting from that address, such as the word at address 736424 or the byte at address 736424. However, it is necessary to know the data type stored at a certain address to give the memory at that address a clear meaning; the type determines the number of bits occupied by the data and how to interpret the contents of those bits.

Signed and Unsigned Types#

Other integer types:

  • Except for boolean and extended character types, other integer types can be divided into signed and unsigned.

  • int, short, long, long long are signed.

  • Adding unsigned before the type name yields an unsigned type.

  • For example, unsigned long.

  • unsigned int can be abbreviated as unsigned.

Character types:

  • Divided into three types: char, signed char, unsigned char.

  • Whether char is signed or unsigned is determined by the compiler.

How to Choose Types#

  1. When the value cannot be negative, such as age, length, etc., use unsigned types;
  2. Use int type for integer calculations; generally, long is the same size as int, while short seems too small. If handling larger integers, choose long long;
  3. Do not use bool or char in arithmetic expressions, as signs can easily cause problems;
  4. Use double for floating-point data operations;

Type Conversion#

The range of values that a type can represent determines the conversion process:

  • Assigning a non-boolean type to a boolean type, if the initial value is 0, the result is false; otherwise, it is true.
#include<iostream>
using namespace std;
int main() {
	int i = 42;
	if (i) {
		i = 0;
	}
	cout << "i = " << i << endl;  // i=0
	return 0;
}
  • Assigning a boolean type to a non-boolean type, if the initial value is false, the result is 0; if true, the result is 1.
  • Assigning a floating-point number to an integer, it is approximated, retaining the part before the decimal point.
  • Assigning an integer to a floating-point type, the decimal part is set to 0; if the integer exceeds the capacity of the floating-point type, precision may be lost.
  • Assigning an out-of-range value to an unsigned number results in the initial value modulo the maximum value represented by the unsigned type.
  • Assigning an out-of-range value to a signed number results in undefined behavior.
//test01.cpp
#include<iostream>
using namespace std;
int main() {
	int i = 42;
	if (i) {
		i = 0;
	}
	cout << "i = " << i << endl;  // i=0
	return 0;
}
//test02.cpp
#include<iostream>
int main() {
	unsigned u = 10;
	int i = -42;
	std::cout << i + i << std::endl; //-84
	std::cout << u + i << std::endl; //4294967264
	return 0;
}
//test03.cpp
#include<iostream>
int main() {
	for (int i = 10; i >= 0; --i) {
		std::cout << i << std::endl;
	}
	return 0;
} //Normal output
//test04.cpp
#include<iostream>
int main() {
	for (unsigned u = 10; u >= 0; --u) {   // Unsigned number will not be less than 0
		std::cout << u << std::endl;    // Infinite loop, ctrl+c to exit
	}
	return 0;
}
//test05.cpp
#include<iostream>
int main() {
	unsigned u = 10, u2 = 42;
	std::cout << u2 - u << std::endl;  //32
	std::cout << u - u2 << std::endl;  //4294967264
	int i = 10, i2 = 42;
	std::cout << i2 - i << std::endl;  //32
	std::cout << i - i2 << std::endl;  //-32
	std::cout << i - u << std::endl;  //0
	std::cout << u - i << std::endl; //0
	return 0;
}

Literal Constants#

  1. Integer Literal Constants:
  • Decimal: 42
  • Octal: 052 (starts with 0)
  • Hexadecimal: 0x2A or 0X2A (starts with 0x or 0X)
  • Binary: 0b101010 or 0B101010 (starts with 0b or 0B)

Error example:

#include<iostream>
int main() {
	int month = 9, day = 7;
	int month1 = 09, day1 = 07; // Starting with 0 will be treated as octal assignment
    // [Error] invalid digit "9" in octal constant
	return 0;
}
  1. Floating-point Literal Constants:
  • Decimal: 3.14 or 2.0
  • Scientific notation: 3.0e8 (represents 3.0 multiplied by 10 to the power of 8)
  1. Character Literal Constants:
  • Character: 'A' or 'x'
  • Escape character: '\n' (represents newline) or '\\' (represents backslash)
Escape sequences:
Newline '\n'   Horizontal tab '\t'   Bell '\a'
Vertical tab '\v'  Backspace '\b'  Double quote '\"'
Backslash '\\'  Question mark '\?'  Single quote '\''   Carriage return '\r' 
Generalized escape sequences: '\x' followed by one or more hexadecimal digits, or '\' followed by 1, 2, or 3 octal digits
'\7' Bell '\12' Newline '\40' Space
'\0' Null character '\115' Character M '\x4d' Character M
//test05.cpp
#include<iostream>
int main() {
	std::cout << '\n';  // Newline
	std::cout << "\tHi!\n";  // Tab, Hi!, Newline
	std::cout << "\v\?\abc\b\n";  // Vertical tab, ?, Bell, bc, Backspace, Newline
	std::cout << "Hi \x4dO\115!\n";  // Hi MOM!, Newline
	std::cout << '\115' << '\n';  // M, Newline
	return 0;
}
  1. String Literal Constants: String literal constants represent a sequence of characters, enclosed in double quotes. For example:
  • String: "Hello, World!"

The actual type of a string is a character array, ending with the '\0' character. The string type is essentially an array of constant characters, ending with '\0', so the string type is actually one character longer than its content.

  1. Boolean Literal Constants: Boolean literal constants represent true or false, which can be true or false.

  2. Pointer Literal: nullptr

  3. Specifying Literal Types:

Character and string literals:

PrefixMeaningType
uUnicode16 Characterchar16_t
UUnicode32 Characterchar32_t
LWide Characterwchar_t
u8UTF-8 (string literal)char

Integer literals:

SuffixMinimum Matching Type
u or Uunsigned
l or Llong
ll or LLlong long

Floating-point literals:

SuffixType
f or Ffloat
l or Llong double

Variables#

Variables provide a named storage space that can be operated on by the program. In C++, variables and objects can generally be used interchangeably.

Define#

  • Definition format: Type specifier + a list of one or more variable names;

  • Initialization: An object is given a specific value when created.

  • Initialization is not assignment; initialization = creating a variable + giving it an initial value, assignment = erasing the current value of the object + replacing it with a new value.

  • List initialization: Using curly braces {}

  • If list initialization has a risk of losing information, the compiler will report an error.

    int a{3.14};
    
    // error: narrowing conversion from ‘double’ to ‘int’ 
    
  • Default initialization: If no initial value is specified at definition, it will be default initialized;

  • Built-in type variables inside a function body will not be initialized.

  • If a class object is not explicitly initialized, its value is determined by the class definition.

#include<iostream>
std::string global_str;  // Define global variable global_str, initial value is an empty string
int global_int;  // Define global variable global_int, initial value is 0
int main() {
	int local_int; // Local variable uninitialized, initial value is undefined
	std::string local_str; 
    // local_str is an object of the string class, its value is determined by the class, which is an empty string.
	return 0;
}

It is recommended to initialize every built-in type variable!

The Relationship Between Variable Declaration and Definition#

Breaking the program into multiple logical parts for writing, C++ supports separate compilation mechanisms, where the program is divided into several files, each of which can be compiled independently. To support separate compilation, C++ distinguishes between declaration and definition.

A declaration makes the name known to the program, while a definition is responsible for creating the entity associated with the name.

A declaration specifies the variable type and name. A definition allocates storage space and may also assign an initial value to the variable. To declare a variable without defining it, add the keyword extern before the variable name, and do not explicitly initialize the variable.

A variable can only be defined once, but it can be declared multiple times. Definitions only appear in one file, while other files that use the variable need to declare it.

extern int i;  // Declare i without defining it 
int j;  // Declare and define j 
// Including a declaration with explicit initialization makes it a definition:
extern double pi = 3.14;  // Definition
// Inside a function body, attempting to initialize a variable marked with the extern keyword will result in an error.
extern int ix = 1024; // Definition
int iy;  // Definition
extern int iz;  // Declaration

Variable Identifiers and Naming Conventions#

Identifiers:

  1. Composed of letters, digits, and underscores, must start with a letter or underscore.
  • Length is unlimited.

  • Case-sensitive.

  1. Reserved names cannot be used as identifiers.

  2. User-defined identifiers

  • Cannot have two consecutive underscores
  • Cannot start with an underscore followed by a capital letter
  • Identifiers defined outside of function bodies cannot start with an underscore
  • Use camelCase naming convention
// C++ keywords
alignas  continue  friend  register  true
alignof  decltype  goto  reinterpret_cast  try
asm  default  if  return  typedef
auto  delete  inline  short  typeid
bool  do  int  signed  typename
break  double  long  sizeof  union
case  dynamic_cast  mutable  static  unsigned
catch  else  namespace  static_assert  using
char  enum  new  static_cast  virtual
char16_t explicit  noexcept  struct  void
char16_t export  nullptr  switch  volatile
class  extern  operator  template  wchar_t
const  false  private  this  while
constexpr  float  protected  thread_local
const_cast  for  public  throw
// C++ operator alternative names
and  bitand  compl  not_eq  or_eq  xor_eq
and_eq  bitor  not  or  xor

Naming conventions:

  1. Reflect actual meaning
  2. Variable names and function names use camelCase naming convention, such as myVariable
  3. Custom class names start with a capital letter, such as Sales_item
  4. If an identifier consists of multiple words, there should be clear distinctions between words, such as student_loan, studentLoan, do not use studentloan
 int double = 3.14;  // error
 int _;
 int catch-22;  // error
 int 1_or_2 = 1;  // error
 double Double = 3.14;

Scope of names, separated by {}

  • Global scope
  • Block scope

Define a variable the first time it is used, making it easier to find and giving it a more reasonable initial value.

//scope.cpp
#include <iostream>
// Program for illustration purposes only: It is bad style for a function
// to use a global variable and also define a local variable with the same
int reused = 42; // reused has global scope
int main() {
	int unique = 0; // unique has block scope
// output #1: uses global reused; prints 42 0
	std::cout << reused << " " << unique << std::endl;
	int reused = 0; // new, local object named reused hides global reused
// output #2: uses local reused; prints 0 0
	std::cout << reused << " " << unique << std::endl; 
    // At this point, the global variable is overshadowed by the local variable with the same name
// output #3: explicitly requests the global reused; prints 42 0
	std::cout << ::reused << " " << unique << std::endl;  
    // Using the global variable
	return 0;
}

Nested scopes:

When both global and local variables exist, the scope of the defined local variable can explicitly access the global variable using ::. When using a global variable, try not to use a local variable with the same name.

#include <iostream>
int i = 42;
int main() {
	int i = 100;
	int j = i;
// j's value is 100, the local variable i overshadows the global variable i
	std::cout << j << std::endl; // 100
	return 0;
}
#include <iostream>
int main() {
// Is the following program legal? If legal, what will it output?
	int i = 100, sum = 0;
	for (int i = 0; i != 10; ++i)
		sum += i;  // The variable i here only works within the for loop
	std::cout << i << " " << sum << std::endl;
// Legal output: 100 45 
	return 0;
}

L-values and R-values#

  • L-value (l-value) can appear on the left or right side of an assignment statement, such as a variable;

  • R-value (r-value) can only appear on the right side of an assignment statement, such as a constant.

References#

Generally, references refer to l-value references.

References: A reference gives another name to an object, reference type: refers to another type.

 int &refVal = val; 
  1. References must be initialized:
 int &refVal2;  // Error, references must be initialized 
  1. A reference and its initial value are bound together, not copied. Once defined, it cannot be changed to bind to another object.
  2. The reference type must match the bound object.
  3. A reference can only bind to an object, not to a literal or the result of an expression.

For more detailed explanations, refer to: References in C++

Pointers#

A pointer is a composite type that "points to" another type. It is itself an object and does not require an initial value upon definition.

Defining Pointer Types#

int *ip1,*ip2;
// ip1 and ip2 are pointers to int type objects
double dp,*dp2
// dp2 is a pointer to a double type object, dp is of double type

Getting the Address of an Object#

A pointer stores the address of an object. To get the address of an object:

int i=42;
int *p = &i; 

& is the address-of operator. The type of the pointer must match the type of the object it points to (both must be of the same type int, double, etc.).

int ival = 42;
int *p = &ival; // p stores the address of ival, p is a pointer to val

double dval;
double *pd = &dval; // Correct
double *pd2 = pd; // Correct

int *pi = pd; // Error, type mismatch
pi = &dval; // Error, trying to assign the address of a double object to an int pointer

The value (i.e., address) of a pointer can have four states:

  1. Pointing to an object;
  2. Pointing to the next position after an object;
  3. Null pointer;
  4. Invalid pointer.

Operations on invalid pointers will lead to errors;

The second and third states, while valid, may lead to unpredictable consequences when accessing the pointer object.

Accessing Objects via Pointers#

#include <iostream>
int main() {
// If the pointer points to an object, use the dereference operator ('*') to access the object
	int ival = 42;
	int *p = &ival;
	std::cout << *p; // Outputs the data of the object pointed to by p,
	*p = 0;
	std::cout << ival; // 0
	return 0;
}

Dereferencing only applies to valid pointers that indeed point to an object.

Null Pointers#

int *p1=nullptr; // Using a null pointer.
int *p2 = 0; // p2 initialized to the literal constant 0
// include<cstdlib>
int *p3 = NULL; // int *p3=0 NULL preprocessor variable
int zero = 0;
p1 = zero; // Error, cannot directly assign an int variable to a pointer

It is recommended to initialize all pointers!

Differences Between Pointers and References#

  • A reference is not an object; once defined, it cannot be rebound to another object.
  • Pointers do not have this restriction.
// Illustrating the main differences between pointers and references
    
// A reference is an alias for another object, while a pointer is itself an object.
// A reference must be initialized, and once defined, it cannot be rebound to another object.
// A pointer does not need an initial value upon definition and can be reassigned to point to other objects.
int i = 42;
int *pi = 0; // pi initialized, does not point to any object
int *pi2 = &i; // pi2 points to i
int *pi3; // If pi3 is defined within a block, its value is indeterminate

pi3 = pi2; // pi3 and pi2 point to the same object i
pi2 = 0; // pi2 does not point to any object anymore

// The left side of an assignment statement always changes the object on the left.
pi = &ival; // pi points to ival
*pi = 0; // ival's value changes, pi remains unchanged
// Please explain the following definitions. Are any of these definitions illegal? If so, why?
int i = 0;
(a) double* dp = &i;
(b) int *ip = i;
(c) int *p = &i;
(a): Illegal. Cannot assign a pointer to double to an int.
(b): Illegal. Cannot assign an int variable to a pointer.
(c): Legal.

Other Pointer Operations#

int ival = 1024;
int *p1=0;
int *p2=&ival;
// Pointer value 0, condition evaluates to false
if(p1)
	//...
// p2 pointer is non-zero, condition evaluates to true
if(p2)
	//...
// Pointer comparison
if(p1==p2)
	//...

Invalid pointers will lead to unpredictable consequences!

// A single statement defines different types of variables
int i=1024,
*p=&i,&r=i;

int* p; // Legal but misleading
int* p1,p2; // p1 is a pointer, p2 is an int
int *p1,*p2; // Both p1 and p2 are pointers
or
int *p1;
int *p2;

Pointer to Pointer#

#include <iostream>
using namespace std;
int main() {
	int ival = 1024;
	int *pi = &ival;  // pi points to int type
	int **ppi = &pi; // ppi points to int type pointer
// Dereference
	cout << "The value of ival\n"  
	     << "direct value: " << ival << "\n"
	     << "indirect value: " << *pi << "\n"
	     << "doubly indirect value: " << **ppi
	     << endl;
	return 0;
	// The value of ival
	// direct value: 1024
	// indirect value: 1024
	// doubly indirect value: 1024
}

Reference to Pointer#

Read from right to left for the definition of r.

int i=42;
int *p;
int *&r = p;  // r is a reference to pointer p
r = &i;  // p points to i
*r=0;  // i=0
Explain the types and values of the following variables.
(a) int* ip, i, &r = i;
(b) int i,*ip = 0;
(c) int* ip, ip2;

(a): ip is a pointer to int, i is an int, r is a reference to i.
(b): i is int, ip is a null pointer.
(c): ip is a pointer to int, ip2 is an int.

Void Pointers#

A void pointer can store the address of any object. Since it has no type, it can only operate on memory space and cannot directly operate on the object pointed to by a void * pointer.

double obj = 3.14,
*pd = &obj;
void *pv = &obj; // obj can be any type of object
pv = pd; // void* can store any type, type casting can be done when used

CONST Qualifier#

const: Defines variables whose values cannot be changed. A const object, once created, cannot change its value, so it must be initialized and cannot be changed.

const int bufSize = 512;
bufSize = 512; // error, trying to write value to const object

const int i= getSize();
const int j=42;
const int k; // error, k is an uninitialized constant

int i=42;
const int ci=i;
int j=ci;

CONST objects are only valid within the file:

  • When multiple files have the same named const variable, it is equivalent to defining independent variables in different files.

  • To share const variables across multiple files, both the definition and declaration should include the extern keyword.

//file_1.cc defines and initializes a constant that can be accessed by other files
extern const int bufSize = fcn();

//file_1.h header file
extern const int bufSize; // This is the same bufSize defined in file_1.cc
const int buf;  // Illegal, const objects must be initialized
int cnt = 0;  // Legal
const int sz = cnt;  // Legal
++cnt; ++sz;  // Illegal, const objects cannot be changed

Const References#

  • Binding a reference to a const object is called a reference to a constant.
  • Unlike ordinary references, a reference to a constant cannot be used to modify the object it is bound to.
const int ci=1024;
const int &r1=ci;
r1=42;  // error, r1 is a reference to a constant
int &r2 = ci;  // error, trying to let a non-const reference point to a constant object

Initialization and Const References#

The reference type must match the type of the object it is used with.

  • Exception: When initializing a constant reference, it is allowed to use any expression as the initial value, as long as the result of the expression can be converted to the type of the reference.
  • It is allowed for a constant reference to bind to a non-constant object, literal, or a general expression.
int i=42;
const int &r1=i;  // Allowed to bind const int& to int object
const int &r2=42; // r2 is a constant reference
const int &r3=r1*2; // r3 is a constant reference
int &r4=r1*2; // error, r4 is a normal non-const reference.


// What happens when a constant reference is bound to another type?
double dval = 3.14;
const int &ri=dval;
// The compiler transforms the above code into the following form
const int temp=dval; // Generates a temporary int variable
const int &ri=temp; // Binds ri to this temporary variable
  • Temporary objects: When the compiler needs a space to temporarily store the value of an expression result, an unnamed variable is created temporarily.
  • Referencing a temporary object is erroneous behavior:
double dval = 3.14;
int &ri = dval; // error

A const reference is likely to reference a non-const object.

  • A constant reference only restricts the operations that can be performed on the reference; it does not restrict whether the object being referenced is constant.
int i = 42;
int &r1 = i; // r1 binds to i
const int &r2 = i;  // r2 binds to i, modification through r2 is not allowed
r1 = 0;  // ok, r1 is non-const
r2 = 0;  // error, r2 is a constant reference

Like constant references, pointers to constants also do not require the object being pointed to to be constant. They only require that the value of the object cannot be changed through the pointer.

Const Pointers#

Constant pointers must be initialized and can be defined as constants:

int errNumb=0;
int *const curErr=&errNumb; // curErr is a constant, always pointing to errNumb (address cannot change)
const double pi=3.1415;
const double *const pip= &pi; // pip is a constant, *pip is also constant,
// pip is a constant pointer to a constant object (neither value nor address can change)
*pip = 2.72; // error, *pip is constant
if(*curErr){
	errorHandler();
	*curErr=0; // ok, *curErr is not constant
}
// Which of the following initializations are legal? Please explain why.
int i = -1, &r = 0; // Illegal, r must reference an object
int *const p2 = &i2; // Legal, constant pointer
const int i = -1, &r = 0; // Legal
const int *const p3 = &i2; // Legal
const int *p1 = &i2; // Legal
const int &const r2; // Illegal, r2 is a reference, no top-level const
const int i2 = i, &r = i; // Legal
// Explain what the following definitions mean, and pick out any that are illegal.
int i,*const cp; // Illegal, const pointer must be initialized
int *p1,*const p2; // Illegal, const pointer must be initialized
const int ic, &r = ic; // Illegal, const int must be initialized
const int *const p3; // Illegal, const pointer must be initialized
const int *p; // Legal. A pointer pointing to const int
// Assuming the variables defined in the previous exercise exist, which of the following statements are legal? Please explain why.
i = ic; // Legal, constant assigned to a normal variable
p1 = p3; // Illegal, p3 is a const pointer, cannot assign to a normal pointer
p1 = &ic; // Illegal, normal pointer cannot point to a constant

const int * const p3 = &ic; // Legal, p3 is a constant pointer pointing to a constant
p3 = &ic; // Illegal now, p3 is constant

int * const p2 = p1; // Legal, can assign a normal pointer to a constant pointer
p2 = p1; // Illegal now, p2 is constant

const int ic = *p3; // Legal, taking value from p3 results in an int which is then assigned to ic
ic = *p3; // Illegal now, ic is constant

Top-level Const#

  • Top-level const: The pointer itself is a constant.
  • Bottom-level const: The object pointed to by the pointer is a constant. During copying, strict requirements for the same bottom-level const qualifications.
int i=0;
int *const p1=&i; // p1 is constant, top-level const
const int ci=42; // ci is constant, top-level const
const int *p2=&ci; // *p2 is constant, bottom-level const
const int *const p3=p2; // right side top-level, left side bottom-level const
const int &r =ci; // const used to declare references are all bottom-level const

// Copying does not affect top-level const
i=ci; // Copying ci's value, ci top-level const, no effect
p2=p3; // p2 and p3 point to the same object type, p3's top-level const part does not affect

// Copying strictly requires the same bottom-level const qualifications, data types must be convertible.
int *p =p3; // error, p3 contains bottom-level const definition, p has none
p2=p3; // ok, p2 and p3 are both bottom-level const
p2=&i; // ok, int* can convert to const int*
int &r=ci; // error, normal int& cannot bind to int constant
const int &r2=i; // ok, const int& can bind to int
// For the following statements, explain whether the object is declared as top-level const or bottom-level const?
const int v2 = 0; int v1 = v2;
int *p1 = &v1, &r1 = v1;
const int *p2 = &v2,
*const p3 = &i, &r2 = v2;
// v2 is top-level const, p2 is bottom-level const, p3 is both top-level and bottom-level const, r2 is bottom-level const.
// Assuming the variables defined in the previous exercise exist, which of the following statements are legal? 
// Please explain how top-level const and bottom-level const are reflected in each example.
r1 = v2; // Legal, top-level const is unaffected during copying
p1 = p2; // Illegal, p2 is bottom-level const, if copying is required, p1 must also be bottom-level const
p2 = p1; // Legal, int* can convert to const int*
p1 = p3; // Illegal, p3 is a bottom-level const, p1 is not
p2 = p3; // Legal, p2 and p3 are both bottom-level const, copying ignores top-level const

Constexpr and Constant Expressions#

  • Constant expressions: Expressions whose values do not change and can be computed during compilation.
// Whether an object (expression) is a constant expression is determined by its data type and initial value
const int max_file=30;  // max_file is a constant expression
const int limit = max_file+1;  // limit is a constant expression
int staff_size = 7;  // staff_size is not a constant expression
const int sz = get_size();  // sz is a constant expression, depending on get_size()
  • The new C++11 standard allows variables to be declared as constexpr types so that the compiler can verify whether the variable's value is a constant expression.
constexpr int mf = 20;  // 20 is a constant expression
constexpr int limit = mf+1;  // mf+1 is a constant expression
constexpr int sz = get_size(); // Depends on whether get_size() is a constexpr function

Literal Types#

The value of a constant expression is computed at compile time, with a simple type and easily obtainable value, referred to as "literal types."

  • Arithmetic types
  • References, pointers, nullptr, 0 or fixed positions

Pointers and Constexpr#

  • constexpr makes the defined object top-level.
const int *p=nullptr; // p is a pointer to a constant
constexpr int *q=nullptr; // q is a constant pointer
// constexpr makes the defined object top-level
constexpr int *nq=nullptr; // nq is a constant pointer
int j=0;
constexpr int i=42; // i is a constant
// i and j are defined outside the function
constexpr const int *p=&i; // p is a constant pointer pointing to the constant integer i
constexpr int *p1=&j; // p1 is a constant pointer pointing to integer j

Handling Types#

  • As programs become more complex, types become more complex. Types are difficult to spell, their intended meanings are unclear, and it is hard to know what type is needed.

Type Aliases#

  • Traditional alias: Use typedef to define synonyms for types.

    typedef double wages; 
    wages hour, weekly;  // double hour, weekly;
    
  • New standard alias: Alias declaration:

    using SI = Sales_item;  // (C++11)
    SI items; // Sales_item items;
    

Pointers, Constants, and Type Aliases#

// For composite types (pointers, etc.), it cannot be understood by reverting to the original form
typedef char *pstring;  // pstring is an alias for char*
const pstring cstr = 0; // cstr is a constant pointer to char
const pstring *ps; // ps is a pointer to a constant pointer to char
// Basic data types are pointers, not const char.

AUTO Type Specifier (C++ 11)#

Knowing the type of an expression clearly is not easy, hence the introduction of auto.

  • The auto type specifier allows the compiler to infer the type. Clearly, an auto-defined variable must have an initial value.
  • A single statement can only have one basic data type:
 auto sz = 0, pi =3.14; // Error, types are not the same

Composite Types, Constants, and Auto#

int i = 0, &r = i; 
auto a = r; // a's inferred type is int

// Will ignore top-level const
const int ci = i;
auto b = ci; // infers int, ignoring top-level const

const int ci = 1; const auto f = ci;
// infers type as int, if top-level const is desired, it needs to be added manually

DECLTYPE Type Hint (C++ 11)#

decltype is a keyword introduced in C++11 that is used to obtain the type of an expression. Its syntax is as follows:

decltype(expression)

Where expression can be a variable, function call, type conversion, etc.

The purpose of decltype is to infer the type of a variable or expression based on the type of the expression and return that type. For example:

int x = 42;
decltype(x) y; // y's type is int

double square(double value) {
    return value * value;
}

decltype(square(3.14)) result; // result's type is double

struct Point {
    int x;
    int y;
};

Point p;
decltype(p.x) a; // a's type is int

In the above examples, decltype(x) returns int type because the expression x is of type int. decltype(square(3.14)) returns double type because the expression square(3.14) has a return type of double. decltype(p.x) returns int type because p.x is a member variable of the Point structure.

decltype is very useful when writing generic code, as it can infer the return type of a function based on the types of the parameters, avoiding the need to manually specify the return type. For example:

template<typename T, typename U>
auto multiply(T a, U b) -> decltype(a * b) {
    return a * b;
}

int main() {
    int x = 5;
    double y = 3.14;
    auto result = multiply(x, y); // result's type is double

    return 0;
}

In the above example, the multiply function uses decltype(a * b) to infer the return type, which will automatically be inferred as double based on the types of the passed parameters.

decltype is a very useful keyword that allows the compiler to infer the type of a variable or function based on the type of an expression, improving the flexibility and readability of the code.

  • Infers the type of the variable to be defined from the type of the expression.

  • decltype: Selects and returns the data type of the operand, without evaluating the expression's value.

decltype(f()) sum = x; // Infers sum's type as the return type of function f.
  • Will not ignore top-level const.
  • If parentheses are added to a variable, the compiler will treat it as an expression, decltype((i)) results in a reference.
  • Assignments are a typical expression that produces references; the type of the reference is the type of the left value. That is, if i is int, then the type of the expression i=x is int&.
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.