> 文档中心 > C++从入门到精通(十万字详细内容总结)细节满满

C++从入门到精通(十万字详细内容总结)细节满满


为了帮助所有学习C++的朋友能够系统的了解C++,我总结了这篇《C++从入门到精通》,希望能够帮助大家共同学习,如有错误还请及时指正!!关注我后续持续更新C++内容总结!(注意:本文适合有C语言基础的同学学习!)

文章分为三个部分:

  1. C++基础入门阶段

  2. C++面向对象

  3. C++继承与派生  


                                                

    文章目录:                                  ​

    C++基础入门阶段 :

    1.C++统一初始化

    2.C++中的输入输出

    3.const与指针

    3.1 const在C和C++中的区别

    3.2 const和指针的关系

    4.引用(取别名)

    4.1引用的特征:

    细节tips:

    左值:可以取地址

    右值:不可以取地址

    将亡值

    4.2const引用(常引用)

    4.3引用作为形参代替指针

    4.4 其他引用形式:

    4.5 引用和指针的区别(重点):

    5.inline函数

    6.缺省函数

    7.函数重载

    7.1 判断函数重载的规则:

    7.2 名字修饰

    7.3 关键字extern:

    8.函数模板

    9.命名空间:namespace

    9.1 定义

    9.11 加命名空间及作用域限定符

    9.12使用using 将命名空间中成员引入

    9.13使用using namespace 命名空间名称引入

    10.new/delete

    10.1 new运算符的使用(关键字调动)

     10.2 new的函数调动

    10.3 定位new的使用

    11.C11中的auto(类型指示符)

    11.1 auto的推导规则

    11.2 auto作为函数形参

    11.3 auto的限制

    *注意:auto可以推导函数的返回值

    12.C11中的decltype关键字

    13.基于范围的for循环

    13.1 使用auto自动推导val数据类型

     14.typedef 和using

    C++面向对象:

    1.面向过程

    2.面向对象的概念

    3.类型设计与实例化对象

    C++继承与派生:


                                                                         8e5d1e1e5bb9b9f0753bad68a1d95a8a.png

C++基础入门阶段 :


1.C++统一初始化

初始化列表:{ }      在C++中我们可以使用{ }初始化一切数据类型

#includeusing namespace std;int main(){    //初始化int a = 10; //C int b(10);  //面向对象的初始化方案int c{ 10 }; //初始化列表方案int ar[10] = { 1,2,3,4,5,6,7,8,9,10 };int br[10]{ 1,2,3,4,5,6,7,8,9,10 };int cr[]{ 1,2,3,4 };int dr[] = { 1,2,3,4 };int* ip = NULL;int* is{ NULL };cout << a << endl << b << endl << c;cout << endl;for (int i = 0; i < 10; i++){cout << ar[i]<<" ";}cout << endl;for (int i = 0; i < 10; i++){cout << br[i] << " ";}return 0;}

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_12,color_FFFFFF,t_70,g_se,x_16

这里a,b,c,均初始化为10,数组,指针初始化同理

{ }初始化对于数据类型的检查更为严格

int main(){int a = 12.23;  //a=12int b{ 12.23 }; //错误 无法强转类型double d{ 12.23 };return 0;}

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_20,color_FFFFFF,t_70,g_se,x_16

 2.C++中的输入输出

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_20,color_FFFFFF,t_70,g_se,x_16

在C语言中有标准输入输出函数scanf和printf,而在C++中有cin标准输入和cout标准输出。在C语言中使用scanf和printf函数,需要包含头文件stdio.h。在C++中使用cin和cout,需要包含头文件iostream以及std标准命名空间。

C++的输入输出方式与C语言更加方便,因为C++的输入输出不需要控制格式,例如:整型为%d,字符型为%c。  

注意: 在理解cin功能时,不得不提标准输入缓冲区。当我们从键盘输入字符串的时候需要敲一下回车键才能够将这个字符串送入到缓冲区中,那么敲入的这个回车键(\r)会被转换为一个换行符\n,这个换行符\n也会被存储在cin的缓冲区中并且被当成一个字符来计算!比如我们在键盘上敲下了123456这个字符串,然后敲一下回车键(\r)将这个字符串送入了缓冲区中,那么此时缓冲区中的字节个数是7 ,而不是6。cin读取数据也是从缓冲区中获取数据,缓冲区为空时,cin的成员函数会阻塞等待数据的到来,一旦缓冲区中有数据,就触发cin的成员函数去读取数据。

对于cin提取输入流遇到空格会不会结束的问题

  • <> 从流中提取 非字符数组或者字符串数据时通常跳过流中的空格 tab键换行符等空白字符。
    int main(){int a, b;cin >> a>>b;cout << a <<"    "<<b << endl;return 0;}

 中间输入多个空格后,提取数据时都进行了跳过

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_20,color_FFFFFF,t_70,g_se,x_16

当cin>>从缓冲区中读取数据时,若缓冲区中第一个字符是空格、tab或换行这些分隔符时,cin>>会将其忽略并清除,继续读取下一个字符,若缓冲区为空,则继续等待。但是如果读取成功,字符后面的分隔符是残留在缓冲区的,cin>>不做处理。 

  • 用cin>> 读取 字符数组或者字符串 数据时遇到空白字符(包括空格 tab键和回车)作为终止字符。
    int main(){char a[100];cin >> a;cout<<a;return 0;}

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_20,color_FFFFFF,t_70,g_se,x_16

  • 对于读取字符或字符串在C++中我们可以采用cin.get函数或cin.getline函数

这里我们举例cin.getline

cin.getline读取一行
函数作用:从标准输入设备键盘读取一串字符串,并以指定的结束符结束。 
函数原型有两个:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_20,color_FFFFFF,t_70,g_se,x_16
 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_17,color_FFFFFF,t_70,g_se,x_16

 注意:cin.getline与cin.get的区别是,cin.getline不会将结束符或者换行符残留在输入缓冲区中。

3.const与指针

3.1 const在C和C++中的区别

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_20,color_FFFFFF,t_70,g_se,x_16

在C语言的编译环境下:

int main(){    const int n = 10;  //在c语言中以变量为主    int b = 0;    int* p = (int*)&n;    *p = 100;    b = n;    printf("n=%d *p=%d b=%d ", n, *p,b);    return 0;}

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_19,color_FFFFFF,t_70,g_se,x_16

 在C++下的编译环境:

int main(){    const int n = 10;  //在c语言中以变量为主    int b = 0;    int* p = (int*)&n;    *p = 100;    b = n;    printf("n=%d *p=%d b=%d ", n, *p,b);    return 0;}

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_20,color_FFFFFF,t_70,g_se,x_16

3.2 const和指针的关系

1.指向常量的指针: 一个指向常量的指针变量

int main(){ const char* pc = "abcd";     该方法不允许改变指针所指的变量,即     pc[3] = ‘x';   是错误的,     但是,由于pc是一个指向常量的普通指针变量,不是常指针,因此可以改变pc所指的地址     pc = "jklmn";     该语句付给了指针另一个字符串的地址,改变了pc的值     }

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_17,color_FFFFFF,t_70,g_se,x_16

2. 常指针(常变量和指针):将指针变量所指的地址声明为常量

int main(){char* const pc = "abcd";创建一个常指针,一个不能移动的固定指针,可更改内容,如    pc[3] = 'x';但不能改变地址,如    pc = 'dsff'; 不合法}

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_20,color_FFFFFF,t_70,g_se,x_16

3.指向常量的常指针:这个指针所指的地址不能改变,它所指向的地址中的内容也不能改变。

int main(){    const char* const pc = "abcd";    内容和地址均不能改变}

4.引用(取别名)

引用的定义

类型&引用变量名称 = 变量名称

int main(){    int a=10;    int b=a; //变量    int &c=a; //引用    return 0;}

4.1引用的特征:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_20,color_FFFFFF,t_70,g_se,x_16

  • 注意:引用一旦引用了一个实体就不能再引用别的实体
    int main(){    int a = 20;    int& b = a;//正确     int c = 30;    b = c;  其实是c的值赋给了b,b是a的别名,也相当于修改了a的值    而不是b又引用了c,成了c的别名    cout << "b = "<< b << endl;    cout << "a = " << a << endl;return 0;}

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_20,color_FFFFFF,t_70,g_se,x_16

细节tips:

左值:可以取地址

举例来说, 函数名和变量名(实际上是函数指针和具名变量,具名变量如std::cin、std::endl等)、返回左值引用的函数调用、前置自增/自减运算符连接的表达式++i/--i、由赋值运算符或复合赋值运算符连接的表达式(a=b、a+=b、a%=b)、解引用表达式*p、字符串字面值"abc"等。

右值:不可以取地址

  • 本身就是赤裸裸的、纯粹的字面值,如3、false;
  • 求值结果相当于字面值或是一个不具名的临时对象。

举例: 除字符串字面值以外的字面值、返回非引用类型的函数调用、后置自增/自减运算符连接的表达式i++/i--、算术表达式(a+b、a&b、a<=b、a<b)、取地址表达式(&a)等。

++i是左值,i++是右值。

前者,对i加1后再赋给i,最终的返回值就是i,所以,++i的结果是具名的,名字就是i;而对于i++而言,是先对i进行一次拷贝,将得到的副本作为返回结果,然后再对i加1,由于i++的结果是对i加1前i的一份拷贝,所以它是不具名的。假设自增前i的值是6,那么,++i得到的结果是7,这个7有个名字,就是i;而i++得到的结果是6,这个6是i加1前的一个副本,它没有名字,i不是它的名字,i的值此时也是7。可见,++i和i++都达到了使i加1的目的,但两个表达式的结果不同。

解引用表达式*p是左值,取地址表达式&a是纯右值。

&(*p)一定是正确的,因为*p得到的是p指向的实体,&(*p)得到的就是这一实体的地址,正是p的值。由于&(*p)的正确,所以*p是左值。而对&a而言,得到的是a的地址,相当于unsigned int型的字面值,所以是纯右值。

a+b、a&&b、a==b都是纯右值

a+b得到的是不具名的临时对象,而a&&b和a==b的结果非true即false,相当于字面值。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_20,color_FFFFFF,t_70,g_se,x_16

将亡值

在C++11之前的右值和C++11中的纯右值是等价的。C++11中的将亡值是随着右值引用④的引入而新引入的。换言之,“将亡值”概念的产生,是由右值引用的产生而引起的,将亡值与右值引用息息相关。所谓的将亡值表达式,就是下列表达式:

  • 返回右值引用的函数的调用表达式
  • 转换为右值引用的转换函数的调用表达式

在C++11中,我们用左值去初始化一个对象或为一个已有对象赋值时,会调用拷贝构造函数或拷贝赋值运算符来拷贝资源(所谓资源,就是指new出来的东西),而当我们用一个右值(包括纯右值和将亡值)来初始化或赋值时,会调用移动构造函数或移动赋值运算符⑤来移动资源,从而避免拷贝,提高效率(关于这些知识,在后续文章讲移动语义时,会详细介绍)。当该右值完成初始化或赋值的任务时,它的资源已经移动给了被初始化者或被赋值者,同时该右值也将会马上被销毁(析构)。也就是说,当一个右值准备完成初始化或赋值任务时,它已经“将亡”了。而上面1)和2)两种表达式的结果都是不具名的右值引用,它们属于右值

4.2const引用(常引用)

引用类型不仅要与引用实体是同种类型,还有考虑能否修改的问题

让我们观察下面这段代码:

int main(){    int a=10;    const int b=20;    int &m=b; //错误    const int &n=b //正确    const int &x=a //正确    congt int &y=10;//正确    return 0;}

在这里b是常量,不可修改。在int &m=b这句中m作为b的别名是可以被修改的,而b是不被允许修改,所以错误。而对于const int &n=b用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到了引用的安全性。

4.3引用作为形参代替指针

首先我们需要清除一个概念:由于不存在空引用,并且引用一旦被初始化指向一个对象,它就不能被改变为另一个对象的引用,因此引用很安全。指针可以指向别的对象,而且可以不被初始化或者初始化为空,因此指针不安全

这里我们以交换函数作比较:

使用指针交换两个整型void swap(int *a,int *b){    assert(a!=NULL&&b!=NULL);//需要判空    int tmp=*a;    *a=*b;    *b=tmp;}int main(){    int a=10,b=20;    swap(&a,&b);  //传入地址    cout<<"a= "<<a<<"b= "<<b<<<endl;    return 0;}使用引用交换两个整型void swa(int &a,int &b){  //不存在NULL引用,不需要判空,比指针安全    int tmp=a;    a=b;    b=tmp;}int main(){    int a=10,b=20;    swap(a,b);    cout<<"a= "<<a<<"b= "<<b<<<endl;    return 0;}

4.4 其他引用形式:

*记住:对于所有定义的识别都是从右向左!

4.41 指针的引用:

int m = 1;int *p = &m;'int *&rp = p;

&说明r是一个引用; *确定r引用的类型是一个指针。 

4.42 数组的引用:

int main(){    int arr[5]={1,2,3,4,5};    int &x=arr[0];    int (&brr)[5]=arr;  //引用数组    return 0;}

先看括号里面,&brr是一个引用,引用的对象是个数组[],是有五个int类型元素的数组[5],所以brr是arr的引用,称为数组的引用。

4.5 引用和指针的区别(重点):

  1. 从语法规则上讲,指针变量存储某个实例(变量或对象)的地址;引用是某个实例的别名。
  2. 程序为指针变量分配内存区域;而不为引用分配内存区域。
  3. 解引用是指针使用时要在前加" * " ;引用可以直接使用。
  4. 指针变量的值可以发生改变,存储不同实例的地址;引用在定义时就被初始化,之后无法改变(不能是其他实例的引用)。
  5. 指针变量的值可以为空NULL;没用空引用。
  6. 指针变量作为形参时需要测试它的合法性(判空NULL);引用不需要判空。
  7. 对指针变量使用"sizeof"得到的是指针变量的大小。对引用变量使用"sizeof"得到的是变量的大小。
  8. 理论上指针的级数没有限制;但引用只有一级;即不存在引用的引用。
  9. ++引用和++指针的效果不同,例++操作而言:
  •  对指针变量的操作,会使指针变量指向下一个实体(变量或对象)的地址;而不是改变所指实体(变量或对象的内容)。
  • 对引用的操作直接反应到所引用的实体(变量或对象)。

    10.不可以对函数中的局部变量或对象以引用或指针方式返回

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_20,color_FFFFFF,t_70,g_se,x_16

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_20,color_FFFFFF,t_70,g_se,x_16

只有当变量的生存期不受函数影响时可以返回指针或引用。换句话说函数死了(调用结束),变量还活着。

  • 如果定义成静态就可以,对于上述fun函数改为static int c=a+b;主函数打印结果就会是5,因为在第二次调用fun(5,5)时,直接跳过static int c=a+b这句话,直接返回 return c;
  • 全局变量
  • 举例:int &fun(int &a,int &b),引用进去再引用出来

从汇编层次理解引用和指针变量的区别在这里不介绍,可以在网上找找其他解析资料

5.inline函数

  当程序执行函数调用时,系统要建立栈空间,保护现场,传递参数以及控制程序执行的转移等等这些工作需要系统时间和空间的开销。

  当函数功能简单,使用频率很高,为了提高效率,直接将函数的代码嵌入到程序中。但这个办法优缺点,一是相同代码重读书写,二是程序可读性往往没有使用函数的好。

  为了协调好效率和可读性之间的矛盾,C++提供定义内联函数,方法是在定义函数时用修饰词inline。

inline bool Isnumber(char x){    return x>='0'&&x<='9'?1:0;}

编译时C++编译器会在调用点内联展开该函数

注意:

  • inline是一种以空间换时间的做法,省去调用函数额外开销。但当函数体的代码过长或者递归函数前加inline关键字,也不会在调用点以内联展开该函数。
  • inline对于编译器只是一个建议,编译器会自动优化。
  • inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
  • 建议如果函数的执行开销小于开栈清栈开销(函数体较小),使用inline处理效率较高。如果函数的执行开销大于开栈清栈开销,使用普通函数方式处理。

6.缺省函数

C++允许定义有缺省参数的函数,这种函数调用时,实参个数可以与形参不相同。

缺省函数在指在定义函数时为形参指定缺省值(默认值)。

这样的函数在调用时,杜宇缺省参数,可以给出实参值,也可以不给出实参值。如果给出实参,将实参传递给形参进行调用,如果不给出实参,则按缺省值进行调用。

void delay(int loops = 10){    延时函数,默认延时10个时间单位 for (; loop > 0; loops--);}int main(){    delay(5);    cout << "延时5个时间单位" << endl;    delay();  //等同于delay(5)    cout << "延时5个时间单位" << endl;    return 0;}

6.1 缺省参数的函数调用:缺省实参不一定是常量表达式,可以是任意表达式,甚至可以通过函数调用给出。如果缺省实参是任意表达式,则函数每次被调用时该表达式被重新求值。但表达式必须有意义。

int my_rand(){    srand(time(NULL));    int ran = rand() % 100;    return ran;}void fun(int a, int b = my_rand()){    cout << "a= " << a << " b= " << b << endl;}int main(){    fun(12);    return 0;}

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_13,color_FFFFFF,t_70,g_se,x_167cd9b7cc6c1348b18ab9201c73c4e74f.png

6.2 缺省参数有多个:但所有缺省参数必须放在参数表右侧,即先定义所有的非缺省参数,再定义缺省参数。这是因为在函数调用时,参数自左向右逐个匹配,当实参和形参个数不同时只有这样才不会产生二义性。

void fun(int a, int b = 10, int c = int(), int d = int{}){    printf("a=%d b=%d c=%d d=%d\n", a, b, c, d);}int main(){    fun(12);    fun(10, 20);    fun(10, 20, 30);    //实参从左向右依次给出    fun(10, , 30);//错误    return 0;}

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_19,color_FFFFFF,t_70,g_se,x_16

*注意:当函数的声明和定义分离时,默认值要给在声明上即(在多文件结构上,缺省函数在公共头文件包含的函数声明中指定,不要在函数的定义中指定)

如果在函数的定义中指定缺省参数值,在公共头文件包含的函数声明中不能再次指定缺省参数值。

(小tip)函数声明需要返回类型,函数名,参数类型,参数个数,形参不作为声明的一部分因此在声明时可以这样定义

void fun(int, int);void fun(int =0 , int=0);

7.函数重载

在C++中可以为两个或两个以上的函数提供相同的函数名称,只要函数参数类型不同,或函数参数类型相同而参数的个数不同,称为函数重载。

注意:只能用参数表来区分,不能用返回类型来区分

//每个同名的函数参数表是唯一的int Max(int a,int b){    return a>b?a:b;}char Max(char a,char b){    return a>b?a:b;}double Max(double a,double b){    return a>b?a:b;}

7.1 判断函数重载的规则:

  • 如果两个函数的参数表中的个数或顺序类型不同,则认为这两个函数是重载
void printf(int a.char b);void printf(char a,int b);
  • 如果两个函数的参数表相同,但是返回类型不同,会被标记为错误,函数名重复声明
int Max(int a,int b){    return a>b?a:b;}unsigned int Max(int a,int b) //error{    return a>b?a:b;}
  • 参数表的比较过程与参数名无关
int Max(int a,int b);int Max(int x,int y);
  •  如果在两个函数的参数表中,只有缺省实参不同,则第二个函数会被认为一个的重复声明
void A(int *b,int a);void A(int *b,int lens=10);
  • typedef名是为现有的数据类型提供了一个替换名,它并没有创建一个新类型,因此如果两个函数参数表的区别只在于一个使用了type的放,而另一个使用了与typedef相应的类型,则该参数表被视为相同的参数列表
typedef char my_char;char B(my_char a);char B(char b);
  • 当一个形参类型有const或volatile修饰时,如果形参是按值传递定义,在定义在函数声明是否相同时,并不考虑修饰;如果形参是指针或引用时,就需要考虑修饰符
void fun(int a);void fun(const int a);-------------------------void fun(int *a);void fun(const int *a);void fun(int &a);void fun(const int &a);
  • 注意函数调用二义性;在参数表中形参类型相同,形参个数不同,形参默认值将会影响函数重载
int func(int a, int b, int c = 0){    return a * b * c;}int func(int a, int b){    return a + b;}int func(int a, int b, int c){    return a * b * c;}int main(){    cout << "func(1,2): " << func(1, 2) << endl;    return 0;}

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_15,color_FFFFFF,t_70,g_se,x_16

注意:函数都可以执行,但不知道调用哪一个,编译不通过.如果使用“默认参数”就不要使用“函数重载”。二选一,不要同时使用,防止二义性。

7.2 名字修饰

在C语言中不支持函数重载:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_20,color_FFFFFF,t_70,g_se,x_16

从上图可知编译器在编译.c文件时只会给函数进行简单的重命名;具体的方法是给函数名之前加上”_”;所以加入两个函数名相同的函数在编译之后的函数名也照样相同;调用者会因为不知道到底调用那个而出错;

C++中函数重载的底层处理

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lmd5Lmd5Li4aW8=,size_20,color_FFFFFF,t_70,g_se,x_16

在.cpp文件中,虽然两个函数的函数名一样,但是他们在符号表中生成的名称不一样。由于两个函数生成的符号表中的名称不一样,所以是可以编译通过的。 

以"?"标识符开始,后跟函数名;"@@YA"标识参数表开始;参数表第一项为函数返回值类型,后面依次为各个数据类型;"@Z"标识整个名字结束,如果该函数无参数则以"Z"结束。对于上述"?Add@@YAHHH@Z" 函数应为 int Add(int ,int)。

7.3 关键字extern:

extern"C"  表示函数名以C的方式约定规则

extern"C++"  表示函数名以C++的方式约定规则

eg:在C++中将一个函数按照C的风格来编译

#includeusing namespace std;extern "C" int Add(int a,int b){    return a+b;}

8.函数模板

C++中,存在泛型编程的概念:即不考虑具体数据类型的编程方式。为了代码重用,代码就必须是通用的;通用的代码就必须不受数据类型的的约束,因此我们可以把数据类型改为一个设计参数。

函数模板可以用来创建一个通用功能的函数,以支持多种不同形参,简化重载函数的设计。

设计模板定义如下:

68f92695fac4479f9fc4545058f10da4.png

:尖括号中不能为空,参数可以是多个,用逗号隔开

模板类型参数代表一种类型,由class或typename(建议使用typename)后加一个标识符构成。

示例:

templateT Max(T a, T b){    return a > b ? a : b;}int main(){    Max(11, 55);    Max('j', 'l');    Max(50.21, 12.4);    cout << Max(11, 55)<< endl;    cout << Max('j', 'l')<< endl;    cout << Max(50.21, 12.4)<< endl;    return 0;}

e29c410d16174a0a84c8981b1233226e.png

函数模板根据一组实际类型(或)值构造出独立的函数过程通常是隐式发生的,称为模板参数推演。对于上述代码实则推演为:

f67477f9ce834027b0d5d38c2675d59f.png

可以看到,我们使用函数模板,根据具体类型的参数化,就能适用于不同类型的变量交换,达到了代码复用的效果。

注意:在对于模板进行推演的时候,如果对于数据类型来说,相当于用tpyedef进行重命名;如果是个非类型则用数值替换了。

892bd4db6fdb4a22881e55b2b2f5e110.png

dac091773f7a45369de3e27557ae0dda.png

*注意函数模板是不允许隐式类型转换的,调用时类型必须严格匹配

函数模板还可以定义任意多个不同的类型参数,但是对于多参数函数模板:

  • 编译器是无法自动推导返回值类型的
  • 可以从左向右部分指定类型参数

示例:

01d7b3423a0544558bb2411c337191a4.png

在上边的代码中,我们定义了多类型参数的函数模板,调用时需要注意的是函数返回值需要在第一个参数类型中显示指定,后边的类型可自动推导或显示指定。

*注意:函数模板跟普通函数一样,也可以被重载
        - C++编译器优先考虑普通函数
        - 如果函数模板可以产生一个更好的匹配,那么就选择函数模板
        - 也可以通过空模板实参列表限定编译器只匹配函数模板

9.命名空间:namespace

在C++中支持局部域,名字空间域,类域。

9.1 定义

名字空间域相当于一个灵活的全局域,他的引入是为了防止程序中的全局实体名与其他程序中的全局实体名,命名冲突。用花括号把文件的一部分括起来,以关键字namespace开头给其起一个名字;花括号括起来的部分称为声明块。声明块包括:类、变量(带有初始化)、函数(带有定义)。

0497cc897e96431daf6d9210740bb359.png

*注意:一个命名空间就定义了一个新的作用域,其内容都局限于该命名空间内。

 那么该如何调用上述代码?下面我来演示三种方法

9.11 加命名空间及作用域限定符

作用域限定符" :: ",我们通过 命名空间名称 :: 命名空间成员 直接访问其空间对应成员

a741ae7696134d2baf56a6b048103d5f.png

9.12使用using 将命名空间中成员引入

使用using声明可以只写一次限定修饰名

a86d64781dff4112891b9a396845c391.png

9.13使用using namespace 命名空间名称引入

使用using指示符可以一次性使用命名空间中的所有成员。格式:using namespace 命名空间名。和引入C++库函数 using namespace std 是一样的。

bed2d435a90a4d0b93ca6ee87edb7580.png

10.new/delete

对于C++来说,我们用new进行内存管理,用delete进行释放。new不同于malloc,new可以对于申请的空间进行初始化。

10.1 new运算符的使用(关键字调动)

 10.2 new的函数调动

10.3 定位new的使用

定位new的操作并不是新开辟一个空间,而是对指定的空间进行初始化操作。

*注意:对于内置类型 new/delete/malloc/free可以混用

 区别:

  1. new/delete 是C++中的运算符。malloc/free是函数。
  2. malloc申请内存空间时,手动计算所需大小,new只需类型名,自动计算大小。
  3. malloc不会初始化,new可以。
  4. malloc返回值为void*,接收时必须强转,new不需要。
  5. malloc申请失败时,返回的是NULL,使用时必须判空;new申请失败时抛出异常。

11.C11中的auto(类型指示符)

auto类型推导:auto定义的变量,可以根据初始化的值,在编译推导出变量名的类型。

11.1 auto的推导规则

通过上述例子我们可以看出auto的一些用法。他可以与指针,引用结合起来,还可以与限定符结合。

 通过上述例子我们可以发现:

  • 当不声明指针或引用时,auto的推导结果和初始化表达式抛弃引用和限定属性后的类型一致
  • 当声明指针或引用时,auto推导将保留初始化表达式的限定属性

11.2 auto作为函数形参

 在最新版本C++下可以编译通过

示例1:

void func(auto x){    cout << sizeof(x) << endl;    cout << typeid(x).name() << endl;}int main(){    int a = 10;    int ar[] = { 11,12,13,14,15 };    func(a);    func(ar);    return 0;}

 示例2:

void func(auto &x){    cout << sizeof(x) << endl;    cout << typeid(x).name() << endl;}int main(){    int a = 10;    int ar[] = { 11,12,13,14,15 };    func(a);    func(ar);    return 0;}

11.3 auto的限制

*注意:auto可以推导函数的返回值

 

12.C11中的decltype关键字

上述auto用于通过一个表达式在编译时确定待定义的变量类型,auto所修饰的变量必须被初始化;若仅希望得到类型,而不需要定义变量时应该怎么办呢?

C11新增了decltype关键字,用来在编译时推导出一个表达式的类型。

语法格式:decltype(exp) 其中exp表示一个表达式

*注意:decltype类似于sizeof,他的推导过程是在编译时完成的,并不会真正计算表达式的值。

 对于函数表达式:

int add(int a, int b){return a + b;}int main(){int c = add(12, 13);auto d = add(12, 13);decltype(add(12, 13)) z;z = add(12,13);cout << c << endl;cout << d << endl;cout << z << endl;return 0;}

上述三种形式都是等价的。

13.基于范围的for循环

C++11基于范围的for循环以统一、简洁的方式来遍历容器和数组

示例:

int main(){int arr[] = { 1,2,3,4,5 };for (int x : arr){cout << x << " ";}cout << endl;return 0;}

格式:

ElemType:是范围变量的数据类型

val:是范围变量的名称。该变量将在循环迭代期间依次接受数组中的元素,eg:在第一次迭代期间他接收第一个值,第二次迭代期间接收第二个值...因此val值的改变,并不会影响数组里面的元素。请观察如下代码:

int main(){int arr[] = { 1,2,3,4,5 };for (int x : arr){cout << x << " ";x += 10;cout << x << " ";cout << endl;}cout << endl;return 0;}

array:是让该循环进行处理的容器名称

13.1 使用auto自动推导val数据类型

示例如下:

void My_Printf(auto& x){for (auto& h : x){cout << h << " ";}cout << endl;}int main(){int arr[] = { 1,2,3,4,5 };char brr[] = "i love you";My_Printf(arr);My_Printf(brr);return 0;}

 14.typedef 和using

 第一部分基础阶段到此结束,感谢观看!


                                                                               050101f0a0b9898fd56bab0879b7528d.png

C++面向对象:


1.面向过程

2.面向对象的概念

3.类型设计与实例化对象

....

未完待续.....我会尽快抽出时间总结编辑文章,可以先收藏起来多看看~

如果觉得文章还不错,收藏点赞是对我最大的肯定~


                                                                             0c8c9028558af9e45e314f94fd7f2f34.png

C++继承与派生:


素描网