1. 类型增强

1.1 类型检查更严格

比如,把一个const 类型的指针赋给非const 类型的指针。c 语言中可以通的过,但是在c++中则编不过去。

  1. #include <iostream>
  2. int main()
  3. {
  4. const int a = 10;
  5. int b = a;
  6. const int* pa = &a;
  7. // int* pb = pa; // C++ 类型的值不能用于初始化 类型的实体
  8. return 0;
  9. }

1.2 布尔类型(bool)

c 语言的逻辑真假用0 和非0 来表示。而c++中有了具体的类型。

  1. #include <iostream>
  2. int main()
  3. {
  4. bool flag = true;
  5. if (flag != false) {
  6. printf("i know bool type now\n");
  7. }
  8. printf("bool size = %d\n", sizeof(bool)); // bool size = 1
  9. return 0;
  10. }

1.3 真正的枚举(enum)

c 语言中枚举本质就是整型,枚举变量可以用任意整型赋值。而c++中枚举变量,只能的元素初始化。

  1. #include <iostream>
  2. enum season { SPR,
  3. SUM,
  4. AUT,
  5. WIN };
  6. int main()
  7. {
  8. enum season s = SPR;
  9. // s = 0; // 只能使用被枚举查出来的元素初始化
  10. return 0;
  11. }

1.4 表达式的值可被赋值

c 语言中表达式通常不能作为左值的,即不可被赋值,c++中某些表达式是可以赋值的。比如:

  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. int a, b = 5;
  6. (a = b) = 10; // a=b; a=10;
  7. cout << "a = " << a << " b = " << b << endl; // a = 10 b = 5
  8. (a < b ? a : b) = 200;
  9. cout << "a = " << a << " b = " << b << endl; // a = 10 b = 200
  10. return 0;
  11. }

2 输入与输出(cin /cout)

第一个真正意义上的c++程序,c++程序的后缀名为cpp。假设程序名叫xxx 则应该写成xxx.cpp。

2.1 cin && cout

cin 和cout 是C++的标准输入流和输出流。他们在头文件iostream 中定义。

流名 含义 隐含设备 流名 含义 隐含设备
cin 标准输入 键盘 cerr 标准错误输出 屏幕
cout 标准输出 屏幕 clog cerr的缓冲输出 屏幕
  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. char name[30];
  6. int age;
  7. cout << "pls input name and age:" << endl;
  8. cin >> name;
  9. cin >> age;
  10. // cin >> name >> age;
  11. cout << "your name is: " << name << endl; // your name is : tom
  12. cout << "your age is: " << age << endl; // your age is : 12
  13. return 0;
  14. }

2.2 格式化

c 语言中printf 拥有强大的格式化控制。c++亦可以实现,略复杂。

2.2.1 设置域宽及位数

对于实型,cout 默认输出六位有效数据,setprecision(2) 可以设置有效位数,setprecision(n)<<setiosflags(ios::fixed)合用,可以设置小数点右边的位数。

  1. #include <iomanip>
  2. #include <iostream>
  3. using namespace std;
  4. /*
  5. a
  6. 100
  7. 120.000000
  8. a
  9. 100
  10. 120.00
  11. a
  12. 100
  13. 120.00
  14. */
  15. int main()
  16. {
  17. printf("%c\n%d\n%f\n", 'a', 100, 120.00);
  18. printf("%5c\n%5d\n%6.2f\n", 'a', 100, 120.00);
  19. cout << setw(5) << 'a' << endl
  20. << setw(5) << 100 << endl;
  21. cout << setprecision(2) << setiosflags(ios::fixed) << 120.00 << endl;
  22. return 0;
  23. }

2.2.2 按进制输出

输出十进制,十六进制,八进制。默认输出十进制的数据。

  1. int main()
  2. {
  3. int i = 123;
  4. cout << i << endl; // 123
  5. cout << dec << i << endl; // 123
  6. cout << hex << i << endl; // 7b
  7. cout << oct << i << endl; // 173
  8. cout << setbase(16) << i << endl; //7b
  9. return 0;
  10. }

2.2.3 设置填充符

还可以设置域宽的同时,设置左右对齐及填充字符。

  1. int main()
  2. {
  3. cout << setw(10) << 1234 << endl;
  4. cout << setw(10) << setfill('0') << 1234 << endl;
  5. cout << setw(10) << setfill('0') << setiosflags(ios::left) << 1234 << endl;
  6. cout << setw(10) << setfill('-') << setiosflags(ios::right) << 1234 << endl;
  7. /*
  8. 1234
  9. 0000001234
  10. 1234000000
  11. ------1234
  12. */
  13. }

3 函数重载(function overload)

3.1.引例

如下函数分别求出整理数据和浮点型数据的绝对值:

  1. int iabs(int a)
  2. {
  3. return a > 0 ? a : -a;
  4. }
  5. double fabs(double a)
  6. {
  7. return a > 0 ? a : -a;
  8. }

C++ 致力于简化编程,能过函数重载来达到简化编程的目的。

  1. int abs(int a)
  2. {
  3. return a > 0 ? a : -a;
  4. }
  5. double abs(double a)
  6. {
  7. return a > 0 ? a : -a;
  8. }

3.2 重载规则与调用匹配(overload&match)

重载规则:

  • 函数名相同。
  • 参数个数不同,参数的类型不同,参数顺序不同,均可构成重载。
  • 返回值类型不同则不可以构成重载。

如下:

  1. void func(int a); // ok
  2. void func(char a); // ok
  3. void func(char a,int b); // ok
  4. void func(int a, char b); // ok
  5. char func(int a); // 与第一个函数有冲突

有的函数虽然有返回值类型,但不与参数表达式运算,而作一条单独的语句。

匹配原则:

  • 严格匹配,找到则调用。
  • 通过隐式转换寻求一个匹配,找到则调用。 ```cpp

    include

    using namespace std;

void print(double a) { cout << a << endl; } void print(int a) { cout << a << endl; }

int main() { print(1); // print(int) print(1.1); // print(double) print(‘a’); // print(int) print(1.11f); // print(double) return 0; }

  1. 注:<br />C++ 允许,int long doubledouble int float 隐式类型转换。遇到这种情型,则会引起二义性。例:将上题上的print(int a)中的类型 int 改为double
  2. ```cpp
  3. error: call of overloaded 'print(int)' is ambiguous
  4. print(1); // print(int)
  5. error: call of overloaded 'print(char)' is ambiguous
  6. print('a'); // print(int)

解决方法,在调用时强转。

3.3 重载底层实现(name mangling)

C++利用name mangling(倾轧)技术,来改名函数名,区分参数不同的同名函数。
实现原理:用 v-c-i-f-l-d 表示void char int float long double 及其引用。

void func(char a); // func_c(char a)
void func(char a, int b, double c);
// func_cid(char a, int b, double c)

3.4 extern “C”

name mangling 发生在两个阶段,.cpp 编译阶段,和.h 的声明阶段。只有两个阶段同时进行,才能匹配调用。
mystring.h

extern "C" {
    int myStrlen(char *str);
}

mystring.cpp

// #include "mystring.h"
int myStrlen(char* str)
{
    int len = 0;
    while (*str++)
        len++;
    return len;
}

main.cpp

#include "mystring.h"
#include <iostream>

using namespace std;
int main()
{
    char* p = "china";
    int len;
    len = myStrlen(p);
    return 0;
}

c++ 完全兼容c 语言,那就面临着,完全兼容c 的类库。由.c 文件的类库文件中函数名,并没有发生name mangling 行为,而我们在包含.c 文件所对应的.h 文件时,.h 文件要发生name manling 行为,因而会发生在链接的时候的错误。

C++为了避免上述错误的发生,重载了关键字extern。只需要在避免name manling的函数前,加extern “C” 如有多个,则 extern “C”{} 我们看一个系统是怎么处理的:
sting.h

extern "C" {
    char* __cdecl _strset(char* _Str, int _Val_MINGW_ATTRIB_DEPRECATED_SEC_WARN);
    char* __cdecl _strset_l(char* _Str, int _Val, _locale_t _Locale__MINGW_ATTRIB_DEPRECATED_SEC_WARN);
    char* __cdecl strcpy(char* __restrict__Dest, const char* __restrict__Source);
    char* __cdecl strcat(char* __restrict__Dest, const char* __restrict__Source);
    int __cdecl strcmp(const char* _Str1, const char* _Str2);
    size_t __cdecl strlen(const char* _Str);
    size_t __cdecl strnlen(const char* _Str, size_t _MaxCount);
    void* __cdecl memmove(void* _Dst, const void* _Src, size_t _Size__MINGW_ATTRIB_DEPRECATED_SEC_WARN);
}

4 操作符重载(operator overload)

前面用到的 << 本身在c语言中是位操作中的左移运算符。现在又用用流插入运算符,这种一个字符多种用处的现像叫作重载。在c语言中本身就用重载的现象,比如& 既表示取地址,又表示位操作中的与。*既表示解引用,又表示乘法运算符。只不过c 语言并没有开放重载机制。

C++提供了运算符重载机制。可以为自定义数据类型重载运算符。实现构造数据类型也可以像基本数据类型一样的运算特性。

#include <iostream>

using namespace std;

struct COMP {
    float real;
    float image;
};

COMP operator+(COMP one, COMP another)
{
    one.real += another.real;
    one.image += another.image;
    return one;
}

int main()
{
    COMP c1 = { 1, 2 };
    COMP c2 = { 3, 4 };
    COMP sum = operator+(c1, c2); // c1+c2;
    cout << sum.real << " " << sum.image << endl; // 4 6
    return 0;
}

示例中重载了一个全局的操作符+号用于实现将两个自定义结构体类型相加。

本质是函数的调用。当然这个COMP operator+(COMP one, COMP another),也可以定义为COMPadd(COMP one, COMP another),但这样的话,就只能COMP sum = add(c1,c2),而不能实现COMP sum = c1 +c2 了。

5 默认参数(default parameters)

通常情况下,函数在调用时,形参从实参那里取得值。对于多次调用用一函数同一实参时,C++给出了更简单的处理办法。给形参以默认值,这样就不用从实参那里取值了。

5.1 示例

单个参数

#include <ctime>
#include <iostream>

using namespace std;

void weatherForcast(const char* w = "sunny")
{
    time_t t = time(0);
    char tmp[64]; // 2020/05/31 20:21:56
    strftime(tmp, sizeof(tmp), "%Y/%m/%d %X %A", localtime(&t));
    cout << tmp << ",today is weahter " << w << endl;
}
int main()
{
    // sunny windy cloudy foggy rainy
    weatherForcast(); // 2020/05/31 20:21:56 Sunday,today is weahter sunny
    weatherForcast("rainny"); // 2020/05/31 20:21:56 Sunday,today is weahter rainny
    weatherForcast(); // 2020/05/31 20:21:56 Sunday,today is weahter sunny
    return 0;
}

多个参数

#include <iostream>

using namespace std;

float volume(float length, float weight = 4, float high = 5)
{
    return length * weight * high;
}

int main()
{
    float v = volume(10);
    float v1 = volume(10, 20);
    float v2 = volume(10, 20, 30);
    cout << v << endl; // 200
    cout << v1 << endl; // 1000
    cout << v2 << endl; // 6000
    return 0;
}

5.2 规则

  • 默认的顺序,是从右向左,不能跳跃。
  • 函数声明和定义一体时,默认认参数在定义(声明)处。声明在前,定义在后,默认参数在声明处。
  • 一个函数,不能既作重载,又作默认参数的函数。当你少写一个参数时,系统无法确认是重载还是默认参数。 ```cpp void print(int a) { } void print(int a, int b = 10) { }

int main() { print(10); return 0; }

// main.cpp : 16 : error : call of overloaded ‘print(int)’ is ambiguous print(10);

<a name="scFwn"></a>
# 6 引用(Reference)
<a name="mfAj9"></a>
## 6.1 引用的概念
变量名,本身是一段内存的引用,即别名(alias)。此处引入的引用,是为己有变量起一个别名。声明如下:
```cpp
int main()
{
    int a;
    int& b = a;
}

6.2 规则

  • 引用没有定义,是一种关系型声明。声明它和原有某一变量(实体)的关系。故而类型与原类型保持一致,且不分配内存。与被引用的变量有相同的地址。
  • 声明的时候必须初始化,一经声明,不可变更。
  • 可对引用,再次引用。多次引用的结果,是某一变量具有多个别名。
  • &符号前有数据类型时,是引用。其它皆为取地址。 ```cpp

    include

using namespace std;

int main() { int a = 10, b = 20; int& r = a; // &r = b; // 错误,不可更改原有的引用关系,表达式必须是可修改的左值 // float& rr = b; // 错误,引用类型不匹配 cout << &a << “ “ << &r << endl; // 变量与引用具有相同的地址。 int& ra = r; // 可对引用更次引用,表示a 变量有两个别名,分别是r 和ra }

<a name="eRaHE"></a>
## 6.3 应用
C++很少使用独立变量的引用,如果使用某一个变量,就直接使用它的原名,没有必要使用他的别名。<br />值作函数参数(call by value)
```cpp
void swap(int a, int b);   // 无法实现两数据的交换
void swap(int *p, int *q); // 开辟了两个指针空间实现交换

引用作函数参数(call by reference)

#include <iostream>

using namespace std;

void swap(int& a, int& b)
{
    int tmp;
    tmp = a;
    a = b;
    b = tmp;
}

int main()
{
    int a = 3, b = 5;
    cout << "a=" << a << " b=" << b << endl; // a=3 b=5
    swap(a, b);
    cout << "a=" << a << " b=" << b << endl; // a=5 b=3
    return 0;
}

c++中引入引用后,可以用引用解决的问题。避免用指针来解决。

6.4 引用提高

引用的本质是指针,C++对裸露的内存地址(指针)作了一次包装,又取得的指针的优良特性。所以再对引用取地址,建立引用的指针没有意义。
1,可以定义指针的引用,但不能定义引用的引用

int a;
int* p = &a;
int*& rp = p; // ok
int& r = a;
int&& rr = r; // error

案例:

#include <iostream>
using namespace std;
void swap(const char* pa, const char* pb)
{
    const char* t;
    t = pa;
    pa = pb;
    pb = t;
}
void swap2(const char** pa, const char** pb)
{
    const char* t;
    t = *pa;
    *pa = *pb;
    *pb = t;
}
void swap3(const char*& pa, const char*& pb)
{
    const char* t;
    t = pa;
    pa = pb;
    pb = t;
}
int main()
{
    const char* pa = "china";
    const char* pb = "america";
    cout << "pa=" << pa << ";pb=" << pb << endl; // pa=china;pb=america
    // swap(pa, pb);    // 不交换
    // swap2(&pa, &pb); // 交换
    // swap3(pa, pb);   // 交换
    cout << "pa=" << pa << ";pb=" << pb << endl; // pa = america; pb = china
    return 0;
}

2,可以定义指针的指针(二级指针),但不能定义引用的指针。

int a;
int* p = &a;
int** pp = &p; // ok
int& r = a;
int&* pr = &r; // error

3,可以定义指针数组,但不能定义引用数组,可以定义数组引用。

int main()
{
    int a, b, c;
    int* parr[] = { &a, &b, &c }; // ok
    // int& rarr[] = { a, b, c }; // error 不允许使用引用的数组
    int arr[] = { 1, 2, 3 };
    int(&rarr)[3] = arr; // ok 的
    cout << rarr << "," << *(rarr + 1) << endl; // 00E5F738,2
    return 0;
}

4,常引用
const 引用有较多使用。它可以防止对象的值被随意修改。因而具有一些特性。
(1) const 对象的引用必须是const 的,将普通引用绑定到const 对象是不合法的。

这个原因比较简单。既然对象是const 的,表示不能被修改,引用当然也不能修改,必须使用const 引用。实际上,const int a=1; int &b=a;这种写法是不合法的,编译不过。

(2) const 引用可使用相关类型的对象(常量,非同类型的变量或表达式)初始化。

这个是const 引用与普通引用最大的区别。const int &a=2;是合法的。double x=3.14; const int &b=a;也是合法的。

常引用原理:

const 引用的目的是,禁止通过修改引用值来改变被引用的对象。const 引用的初始化特性较为微妙,可通过如下代码说明:

int main()
{
    double val = 3.14;
    const int& ref = val; // int const & int & const ??
    double& ref2 = val;
    // val=3.14; ref=3; ref2=3.14
    cout << "val=" << val << "; ref=" << ref << "; ref2=" << ref2 << endl;
    val = 4.14;
    // val=4.14; ref=3; ref2=4.14
    cout << "val=" << val << "; ref=" << ref << "; ref2=" << ref2 << endl;
    return 0;
}

上述输出结果为val=3.14; ref=3; ref2=3.14和val=4.14; ref=3; ref2=4.14。因为ref 是const 的,在初始化的过程中已经给定值,不允许修改。而被引用的对象是val,是非const 的,所以val 的修改并未影响ref的值,而ref2 的值发生了相应的改变。

那么,为什么非const 的引用不能使用相关类型初始化呢?实际上,const 引用使用相关类型对象初始化时发生了如下过程:

int temp = val;
const int &ref = temp;

如果ref 不是const 的,那么改变ref 值,修改的是temp,而不是val。期望对ref 的赋值会修改val 的程序员会发现val 实际并未修改。

int i=5;
const int & ref = i + 5;
//  此时产生了与表达式等值的无名的临时变量,
// 此时的引用是对无名的临时变量的引用。故不能更改。
cout<< ref <<endl;

5,尽可能使用const
use const whatever possible 原因如下:

  • 使用const 可以避免无意修改数据的编程错误。
  • 使用const 可以处理const 和非const 实参。否则将只能接受非const 数据。
  • 使用const 引用,可使函数能够正确的生成并使用临时变量(如果实参与引用参数不匹配,就会生成临时变量)

    6.5 引用的本质浅析

    6.5.1 大小与不可再引用

    引用的本质是指针,是个什么样指针呢?可以通过两方面来探究,初始化方式和大小。 ```cpp

    include

struct TypeP { char* p; }; struct TypeC { char c; }; struct TypeR { char& r; // 把引用单列出来,不与具体的对像发生关系 }; int main() { // int a; // int &ra = &a; // const int rb; // const 类型必须要初始化。 printf(“%d %d %d\n”, sizeof(TypeP), sizeof(TypeC), sizeof(TypeR)); // 4 1 4 return 0; }

结论:<br />引用的本质是,是对常指针type * const p 的再次包装。
```cpp
char &rc == *pc
double &rd == *pd

6.5.2 反汇编对比指针和引用

原程序

#include <iostream>

using namespace std;

void Swap(int* p, int* q)
{
    int t = *p;
    *p = *q;
    *q = t;
}

void Swap(int& p, int& q)
{
    int t = p;
    p = q;
    q = t;
}

int main()
{
    int a = 3;
    int b = 5;
    Swap(a, b);
    Swap(&a, &b);
    return 0;
}

汇编程序

     3: void Swap(int* p, int* q) {
     4:     int t = *p;
00B917A8  mov         eax,dword ptr [p]  
00B917AB  mov         ecx,dword ptr [eax]  
00B917AD  mov         dword ptr [t],ecx  
     5:     *p = *q;
00B917B0  mov         eax,dword ptr [p]  
00B917B3  mov         ecx,dword ptr [q]  
00B917B6  mov         edx,dword ptr [ecx]  
00B917B8  mov         dword ptr [eax],edx  
     6:     *q = t;
00B917BA  mov         eax,dword ptr [q]  
00B917BD  mov         ecx,dword ptr [t]  
00B917C0  mov         dword ptr [eax],ecx  
     7: }

     8:     void Swap(int& p, int& q) { 
     9:     int t = p;
00B91738  mov         eax,dword ptr [p]  
00B9173B  mov         ecx,dword ptr [eax]  
00B9173D  mov         dword ptr [t],ecx  
    10:     p = q;
00B91740  mov         eax,dword ptr [p]  
00B91743  mov         ecx,dword ptr [q]  
00B91746  mov         edx,dword ptr [ecx]  
00B91748  mov         dword ptr [eax],edx  
    11:     q = t;
00B9174A  mov         eax,dword ptr [q]  
00B9174D  mov         ecx,dword ptr [t]  
00B91750  mov         dword ptr [eax],ecx  
    12: }

    13: int main() { 
    14:     int a = 3; int b = 5;
00B918D2  mov         dword ptr [a],3  
00B918D9  mov         dword ptr [b],5  
    15:     Swap(a, b);
00B918E0  lea         eax,[b]  
00B918E3  push        eax  
00B918E4  lea         ecx,[a]  
00B918E7  push        ecx  
00B918E8  call        Swap (0B91320h)  
00B918ED  add         esp,8  
    16:     Swap(&a, &b);
00B918F0  lea         eax,[b]  
00B918F3  push        eax  
00B918F4  lea         ecx,[a]  
00B918F7  push        ecx  
00B918F8  call        Swap (0B9137Ah)  
00B918FD  add         esp,8  
    17:     return 0;
00B91900  xor         eax,eax  
    18: }

7.new/delete Operator

C 语言中提供了malloc 和 free 两个系统函数,完成对堆内存的申请和释放。而c++则提供了两关键字new 和delete。

7.1 new/new[]用法:

1.开辟单变量地址空间

int *p = new int;    // 开辟大小为sizeof(int)空间
int *a = new int(5); // 开辟大小为sizeof(int)空间,并初始化为5

2.开辟数组空间

一维: int *a = new int[100]{0};   开辟一个大小为 100 的整型数组空间
      int **p = new int*[5]{NULL}
二维: int (*a)[6] = new int[5][6]
三维: int (*a)[5][6] = new int[3][5][6]

四维及其以上:依此类推.

7.2 delete /delete[]用法:

1.int *a = new int;

delete a; // 释放单个int 的空间

2.int *a = new int[5];

delete []a; // 释放int 数组空间

7.3 综合用法

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <typeinfo>

using namespace std;

int main()
{
    // 申请空间并初始化
    int* p = new int(5);
    cout << *p << endl; // 5
    delete p;

    // 申请空间并初始化
    char* pp = new char[10];
    strcpy(pp, "china");
    cout << pp << endl; // china
    delete[] pp;

    string* ps = new string("china"); // china
    cout << *ps << endl; //cout<<ps<<endl;
    delete ps;

    /*const char** pa = new char* [5];
    memset(pa, 0, sizeof(char* [5]));
    pa[0] = "china";
    pa[1] = "america";
    const char** pt = pa;
    while (*pt) {
        cout << *pt++ << endl;
    }
    delete[]pt;*/

    int(*q)[3] = new int[2][3];
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            q[i][j] = i + j;
        }
    }

    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            cout << q[i][j];
        }
        cout << endl; // 012 345
    }
    delete[] q;

    int(*qq)[3][4] = new int[2][3][4];
    delete[] qq;
}

7.4 关于返回值

#include <iostream>

using namespace std;
int main()
{
    // C 语言版本
    char* ps = (char*)malloc(100);
    if (ps == NULL)
        return -1;

    // C++ 内存申请失败会抛出异常
    try {
        int* p = new int[10];
    } catch (const std::bad_alloc e) {
        return -2;
    }

    // C++ 内存申请失败不抛出异常版本
    int* q = new (std::nothrow) int[10];
    if (q == NULL)
        return -3;
    return 0;
}

7.5 注意事项

  • new/delete 是关键字,效率高于malloc 和free
  • 配对使用,避免内存泄漏和多重释放
  • 避免,交叉使用。比如malloc 申请的空间去delete,new 出的空间被free

    7.6 更进一步

    如果只是上两步的功能,c 中的malloc 和free 完全可以胜任,C++就没有必要更进一步,引入这两个关键字。

此两关键字,重点用在类对象的申请与释放。申请的时候会调用构造器完成初始化,释放的时候,会调用析构器完成内存的清理。以后我们会重点讲。

8 内联函数(inline function)

8.1 内联

c 语言中有宏函数的概念。宏函数的特点是内嵌到调用代码中去,避免了函数调用的开销。但是由于宏函数的处理发生在预处理阶段,缺失了语法检测和有可能带来的语意差错。

8.2 语法

C++提供了inline 关键字,实现了真正的内嵌。
宏函数 VS inline 函数

#include <iostream>
#include <string.h>
using namespace std;
#if 0
    优点: 内嵌代码, 辟免压栈与出栈的开销
    缺点: 代码替换, 易使生成代码体积变大, 易产生逻辑错误, 无类型检查
#endif
#define SQR(x) ((x) * (x))

#if 0
    优点: 高度抽象, 避免重复开发, 类型检查
    缺点: 压栈与出栈, 带来开销
#endif
inline int sqr(int x)
{
    return x * x;
}

int main()
{
    int i = 0;
    while (i < 5) {
        // printf("%d\n", SQR(i++));
        printf("%d\n", sqr(i++));
    }
    return 0;
}

8.3 评价

优点:避免调用时的额外开销(入栈与出栈操作)
代价:由于内联函数的函数体在代码段中会出现多个“副本”,因此会增加代码段的空间。
本质:以牺牲代码段空间为代价,提高程序的运行时间的效率。
适用场景:函数体很“小”,且被“频繁”调用。

9 类型强转(type cast)

类型转换有c 风格的,当然还有c++风格的。c 风格的转换的格式很简单(TYPE EXPRESSION),但是c 风格的类型转换有不少的缺点,有的时候用c 风格的转换是不合适的,因为它可以在任意类型之间转换,比如你可以把一个指向const 对象的指针转换成指向非const 对象的指针,把一个指向基类对象的指针转换成指向一个派生类对象的指针,这两种转换之间的差别是巨大的,但是传统的c 语言风格的类型转换没有区分这些。还有一个缺点就是,c 风格的转换不容易查找,他由一个括号加上一个标识符组成,而这样的东西在c++程序里一大堆。所以c++为了克服这些缺点,引进了4 种新的类型转换操作符。

9.1 静态类型转换

语法格式:

static_cast<目标类型> (标识符)

转化规则:
在一个方向上可以作隐式转换,在另外一个方向上就可以作静态转换。

int main()
{
    int a = 10;
    int b = 3;
    cout << static_cast<float>(a) / b << endl; // float = int int = float
    return 0;

    int* p;
    void* q;
    p = static_cast<int*>(q);

    char* p2 = static_cast<char*>(malloc(100));
    return 0;
}

9.2 重解释类型转换(有错误)

语法格式:

reinterpret_cast<目标类型> (标识符)

转化规则:
“通常为操作数的位模式提供较低层的重新解释”也就是说将数据以二进制存在形式的重新解释,在双方向上都不可以隐式类型转换的,则需要重解释类型转换。

9.3 (脱)常类型转换

语法格式:

const_cast<目标类型> (标识符) // 目标类型只能是指针或引用。

语法规则:
用来移除对象的常量性(cast away the constness),使用 const_cast 去除const 限定的目的不是为了修改它的内容,通常是为了函数能够接受这个实际参数。
应用场景1:

#include <iostream>

using namespace std;

void func(int& ref)
{ //别人己经写好的程序或类库
    cout << ref << endl;
}

int main()
{
    const int m = 4444;
    // func(m); // func参数也要是const修饰才行
    func(const_cast<int&>(m));
    return 0;
}

脱掉 const 后的引用或指针可以改吗?

#include <iostream>

using namespace std;

int main()
{
    const int x = 200;
    int& a = const_cast<int&>(x); // int &a = x;
    a = 300;
    cout << "x_id=" << &x << ";x_val=" << x << endl; // x_id=00EFF864;x_val=200
    cout << "a_id=" << &a << ";a_val=" << a << endl; // a_id=00EFF864;a_val=300
    cout << endl;

    int* p = const_cast<int*>(&x); // int *p = &x;
    *p = 400;
    cout << "p_id=" << &p << ";p_val=" << *p << endl; // p_id=00EFF84C;p_val=400
    cout << "a_id=" << &a << ";a_val=" << a << endl; // a_id=00EFF864;a_val=400
    cout << endl;

    struct A {
        int data;
    };

    const A xx = { 1111 };
    A& a1 = const_cast<A&>(xx);
    a1.data = 222;
    cout << a1.data << " " << xx.data << endl; // 222 222

    A* p1 = const_cast<A*>(&xx);
    p1->data = 333;
    cout << p1->data << " " << xx.data << endl; // 333 333

    return 0;
}

结论:
可以改变const 自定义类的成员变量,但是对于内置数据类型,却表现未定义行为。

Depending on the type of the referenced object, a write operation through the resulting pointer, reference, or pointer to data member might produce undefined behavior.

const 常变量(补充):
C++中const 定义的变量称为常变量。变量的形式,常量的作用,用作常量,常用于取代 #define 宏常量。

#include <iostream>

using namespace std;

#define N 200

int main() {
    const int a = 200;
    int b = 300;
    int c = a + b; // int c = N + b;
    return 0;
}

9.4 动态类型转换:

语法格式:

dynamic_cast<目标类型> (标识符)

用于多态中的父子类之间的强制转化,以后再讲。

10 命名空间(namespace scope)

10.1 为什么要引入namespace

命名空间为了大型项目开发,而引入的一种避免命名冲突的一种机制。比如说,在一个大型项目中,要用到多家软件开发商提供的类库。在事先没有约定的情况下,两套类库可能在存在同名的函数或是全局变量而产生冲突。项目越大,用到的类库越多,开发人员越多,这种冲突就会越明显。

10.2 默认NameSpace(Global &Function)

Global scope 是一个程序中最大的scope。也是引起命名冲突的根源。C 语言没有从语言层面提供这种机制来解决。也算是 C 语言的硬伤了。Global scope 是无名的命名空间。

#include <stdio.h>

//c 语言中如何访问被局部变量覆盖的全局变量
int val = 200;

int main()
{
    int* p = &val;
    int val = 100;
    printf("func val = %d\n", val);
    printf("global val = %d\n", *p);
    return 0;
}
#include <iostream>
#include <string.h>

using namespace std;

int val = 200;

void func()
{
    return;
}

int main()
{
    int val = 100;
    cout << "func val = " << val << endl;
    cout << "global val = " << ::val << endl;
    ::func(); //因为不能在函数内定义函数。所以前面的::没有意义。
    return 0;
}

10.3 语法规则

NameSpace 是对全局(Global scope)区域的再次划分。

10.3.1.声明

命令空间的声明及 namespace 中可以包含的内容

namespace NAMESPACE {
    全局变量      int a;
    数据类型      struct Stu{};
    函数          void func();
    其它命名空间  namespace;
}

10.3.2.使用方法

1.直接指定命名空间: Space::a = 5;
2.使用using+命名空间+空间元素: using Space::a; a = 2000;
3.使用using+namespace+命名空间: using namespace Space;

#include <iostream>
using namespace std;
namespace MySpace {
int val = 5;
int x, y, z;
}

int main()
{
    // MySpace::val = 200;
    // cout<<MySpace::val;
    // using MySpace::x;
    // using MySpace::y;
    // x = 100;
    // y = 200;
    // cout<<x<<y<<endl;

    using namespace MySpace;
    val = 1;
    x = 2;
    y = 3;
    z = 4;
    cout << val << x << y << z << endl;
    return 0;
}

类比 std::cout /using std::cout using/namespact std;
无可辟免的冲突

#include <iostream>

using namespace std;

namespace Space {
int x;
}

namespace Other {
int x;
}

int main()
{
    // Space::x = 4;
    // cout<< Space::x <<endl;
    // Other::x = 5;
    // cout<< Other::x <<endl;
    {
        using Space::x;
        x = 5;
        cout << x << endl;
    }
    {
        using Other::x;
        x = 7;
        cout << x << endl;
    }
    {
        using namespace Space;
        x = 5;
    }
    {
        using namespace Other;
        x = 8;
    }
    return 0;
}

10.3.3 支持嵌套

#include <iostream>
using namespace std;

namespace MySpace {
int x = 1;
int y = 2;

namespace Other {
    int m = 3;
    int n = 4;
}
}

int main()
{
    using namespace MySpace::Other;
    cout << m << n << endl;
    return 0;
}

10.3.4 协作开发

同名命名空间自动合并,对于一个命名空间中的类,要包含声明和实现。
a.h

#ifndef A_H
#define A_H
namespace XX {
class A {
public:
    A();
    ~A();
};
}
#endif // A_H

a.cpp

#include "a.h"
using namespace XXX
{
    A::A() { }
    A::~A() { }
}

b.h

#ifndef B_H
#define B_H
namespace XX {
class B {
public:
    B();
    ~B();
};
}
#endif // B_H

b.cpp

#include "b.h"
namespace XX {
B::B() { }
B::~B() { }
}

main.cpp

#include "a.h"
#include "b.h"
#include <iostream>

using namespace std;
using namespace XX;

int main()
{
    A a;
    B b;
    return 0;
}

11 系统string 类

除了使用字符数组来处理字符串以外,c++引入了字符串类型。可以定义字符串变量。

11.1 定义及初始化

#include <iostream>

using namespace std;

int main()
{
    string str;
    str = "china";
    string str2 = " is great ";
    string str3 = str2;
    cout << str << str2 << endl
         << str3 << endl;
    return 0;
}

11.2 类型大小

cout << "sizeof(string) = " << sizeof(string) << endl;  // sizeof(string) = 28
cout << "sizeof(str) = " << sizeof(str) << endl;        // sizeof(str) = 28

11.3 常用运算

11.3.1 赋值

string str3 = str2;

11.3.2 加法

string combine = str + str2;
cout << combine << endl;

11.3.3 关系

#include <iostream>
using namespace std;
int main()
{
    string s1 = "abcdeg";
    string s2 = "12345";
    if (s1 > s2)
        cout << "s1>s2" << endl;
    else
        cout << "s1<s2" << endl;
    string s3 = s1 + s2;
    cout << s3 << endl;
    return 0;
}

11.4 常见的成员函数

11.4.1 下标操作

char & operator[](int n) ;

11.4.2 求串大小

int size();

11.4.3 返回c 串

char *c_str();

11.4.4 查找

int find(char c, int pos = 0);
int find(char *s, int pos = 0);
// 返回下标值,没有找到返回-1,默认从0 下标开找

11.4.5 删除

string &erase(int idx=0,int n = npos);
// 作用是删除从idx开始,往后数n位的字符串。

11.4.6 交换swap

void swap(stirng &s2);

11.5 string 类型数组

string sArray[10] = {
    "0",
    "1",
    "22",
    "333",
    "4444",
    "55555",
    "666666",
    "7777777",
    "88888888",
    "999999999",
};
for (int i = 0; i < 10; i++) {
    cout << sArray[i] << endl;
}

string 数组是高效的,如果用二维数组来存入字符串数组的话,则容易浪费空间,此时列数是由最长的字符串决定。如果用二级指针申请堆空间,依据大小申请相应的空间,虽然解决了内存浪费的问题,但是操作麻烦。用string 数组存储,字符串数组的话,效率即高又灵活。

12 C++之父给 C 程序员的建议

  • 在C++中几乎不需要用宏,用 const 或 enum 定义显式的常量,用 inline 避免函数调用的额外开销,用模板去刻画一族函数或类型,用 namespace 去避免命名冲突。
  • 不要在你需要变量之前去声明,以保证你能立即对它进行初始化。
  • 不要用malloc,new 运算会做的更好。
  • 避免使用void*、指针算术、联合和强制,大多数情况下,强制都是设计错误的指示器。
  • 尽量少用数组和C 风格的字符串,标准库中的 string 和 vector 可以简化程序。
  • 更加重要的是,试着将程序考虑为一组由类和对象表示的相互作用的概念,而不是一堆数据结构和一些可以拨弄的二进制。