> 文档中心 > 【初阶与进阶C++详解】C++入门知识必备

【初阶与进阶C++详解】C++入门知识必备


⭐️本篇博客我要给大家分享一下C++入门知识。希望对大家有所帮助。
⭐️ 博主码云gitee链接:码云主页

目录

前言

💎一、C++关键字

💎二、命名空间

        🏆1.命名空间定义

      🍍作用

       🍍介绍::

       🍍1. 普通命名空间

        🍍2.命名空间的嵌套定义

        🍍3.同一个工程中可以有多个相同名称命名空间,编译器最后会合成到同一个命名空间中去

        🏆2.命名空间的使用

💎三、C++输入和输出

💎四、缺省函数

        🏆1.缺省参数概念

        🏆2.缺省参数分类 

💎五、函数重载

        🏆1.函数重载概念和介绍

        🏆2.为什么C语言不支持函数重载,C++支持函数重载,C++是如何支持函数重载

        🏆extern"C" 

💎六、引用

        🏆1.引用的概念和特性

        🏆2.常引用 

                🍍做参数

               🍍做返回值 

                🍍两者比较 

                🍍引用和指针的区别 

💎七、内联函数

        🏆1.概念

        🏆2.特性 

💎八、auto关键字

        🏆1.介绍

        🏆2.使用方法 

        🏆3.不能使用场景

💎九、基于范围的for循环

💎十、指针空值--nullptr

💎总结


前言


💎一、C++关键字

🧡C++又新增了31个关键字,一共63个关键字

image-20211009155405225

💎二、命名空间

🧡在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的,一个命名空间就相当于定义了一个新的作用域,命名空间中的所有内容都局限于命名空间中。

        🏆1.命名空间定义

🧡定义命名空间,需要使用到namespace关键字,namespace定义的是一个域,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。

        🍍作用

🧡解决C语言命名冲突,下面就是,输出scanf时候,会优先到局部变量去寻找,不回去头文件中寻找,前面定义scanf不会报错,但后面使用的时候就会报错

int scanf =10;int strlen = 20;scanf ("%d", &scanf);printf("%d\n", scanf);printf("%d\n", strlen);

         🍍介绍::

🧡:: 域作用限定符号,指定访问域

定义一个域

namespace bit{    int scanf = 10;}

使用:

int main (){    printf("%d",bit::scanf);}

优先使用bit这一个域中的参数,并且不会与stdio.头文件中的函数发生冲突

       🍍1. 普通命名空间

namespace linmanman{int a = 10;int b = 20;int Add(int x, int y){return x + y;}}

        🍍2.命名空间的嵌套定义

namespace linmanman{int a = 10;int b = 20;    namespace lin { int x = 10; int y = 20; int Add(int x, int y) { return x + y; } }}

        🍍3.同一个工程中可以有多个相同名称命名空间,编译器最后会合成到同一个命名空间中去

namespace linmanman{int a = 10;int b = 20;int Add(int x, int y){return x + y;}}namespace linmanman{int x = 10;int y = 20;}

        🏆2.命名空间的使用

        1.指定命名空间,加命名空间名称及作用域限定符,每个地方都要指定命名空间   

int main(){std::cout<<"hello!"<<std::endl;    //这里我们可以理解为作用域限定符"::"的使用,让我们在lin这个命名空间中找到了a这个变量return 0;}

        2.使用using namespace 命名空间名称,相当于库里面东西都到全局域里面了,但是如果我们库里面的东西和全局域冲突的话就没办法解决了

using namespace std;//关键字using的使用将命名空间展开到全局int main(){cout<<"hello!"<<endl;return 0;}

        3.对库里面的东西部分展开,引入使用using将命名空间中成员引入

using namespace std::cout;using namespace std::endl;int main(){cout<<"hello!"<<endl;return 0;}

💎三、C++输入和输出

🧡使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含头文件以及std标准命名空间

🧡使用C++输入输出更方便,不需增加数据格式控制,自动识别类型

🧡cout和cin是函数重载和运算符重载

💎四、缺省函数

        🏆1.缺省参数概念

🧡缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参

🧡缺省参数在调用的时候更加灵活

void StackInit (struct Stack* ps,int InitCapacity = 4){    ps->a = (int*) malloc(sizeof(int)* InitCapacity) ;    ps->size = 0;    ps->capacity = InitCapacity;}int main(){    struct Stack stl;    //假设我知道栈里面至少要存100个数据StackInit(&st1,100);    struct Stack st2;    //假设我知道栈里面最多要存10个数据StackInit(&st2,10);    struct Stack st3;    //假设我丕知旗x果炽服众i以StackInit(&st2);    return 0;}

🧡半缺省参数必须从右往左依次来给出,不能间隔着给
🧡缺省参数不能在函数声明和定义中同时出现,所以建议声明的时候给参数

//a.hvoid TestFunc(int a = 10);// a.cvoid TestFunc(int a = 20){}

void TestFunc(int a = 0){    cout<<a<<endl;}int main(){    TestFunc(); // 没有传参时,使用参数的默认值    TestFunc(10); // 传参时,使用指定的实参}

        🏆2.缺省参数分类 

🧡全缺省,全部参数都有默认值

void TestFunc(int a = 10, int b = 20, int c = 30){    cout<<"a = "<<a<<endl;    cout<<"b = "<<b<<endl;    cout<<"c = "<<c<<endl;}

🧡半缺省,缺省部分参数 ,但是缺省的参数必须从有往左,并且是连续的,半缺省传值必须至少传一个

void TestFunc(int a, int b = 10, int c = 20){    cout<<"a = "<<a<<endl;    cout<<"b = "<<b<<endl;    cout<<"c = "<<c<<endl;}

💎五、函数重载

        🏆1.函数重载概念和介绍

🧡C语言不可以定义相同的名字的函数,C++可以定义相同名字的函数,但是要求参数类型不同,或者参数个数不同,但参数个数不一样的时候调用会出错,编译器不知道调用哪个

 🧡虽然都是调用Add但是,会分别调用对应的类型

int Add(int left, int right){    return left+right;}double Add(double left, double right){    return left+right;}long Add(long left, long right){    return left+right;}int main(){    Add(10, 20);    Add(10.0, 20.0);    Add(10L, 20L);    return 0;}

         🏆2.为什么C语言不支持函数重载,C++支持函数重载,C++是如何支持函数重载

编译连接的过程:
f.h f.cpp main. cpp
🧡1、预处理--头文件展开+宏替换+去掉注释+条件编译

        f.i main.i
🧡2、编译--检查语法,生成汇编代码

        f.s main.s
🧡3、汇编―--把汇编代码转成二进制机器码

        f.o  main.o
🧡4、链接--链接到一起生成可执行程序

        a.out

 🧡编译后链接前,a.out的目标文件中没有Add的函数地址,因为Add是在头文件中声明的,而声明是没有创建函数栈帧,所以声明是没有地址的,但是编译器还是让程序过了。

#include using namespace std;int add(int a, int b);double add(double a, double b);

🧡之后链接的时候将上述文件链接到一起,同时到其他文件中取找到Add函数的地址,同时每一个目标文件都会生成一个符号表其中f.o文件的符号表中有Add函数的地址,找到函数地址的时候将函数地址填入之前编译部分跳过的部分(call),如果没有找到就会报链接错误

🧡C++为了区分第二个相同的函数名,会用函数名修饰规则支持函数重载,那么第一个函数会变成_Z3addii(地址),第二个函数会变成_Z3adddd(地址),两个函数地址不一样。函数修饰【_Z+函数长度+函数名+类型首字母】

🧡但是C语言的函数地址是直接拿函数名作为函数,没有前缀后缀 ,没有函数名修饰规则

🧡C++函数名修饰规则会把参数首字母带入,所以参数不同函数名就不同

🧡不同编译器下的修饰规则是不一样的

        🏆extern"C" 

🧡有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern "C",意思是告诉编译器,将该函数按照C语言规则来编译。

extern "C" int Add(int left, int right);int main(){    Add(1,2);    return 0;}

🧡但是这个函数不能重载

🧡C++和C可以互相调用,但是java不行

💎六、引用

        🏆1.引用的概念和特性

🧡引用不是新定义一个变量,而是给已存在变量取了一个别名,它和它引用的变量共用同一块内存空间引用类型必须和引用实体是同种类型

🧡引用在定义时必须初始化
🧡一个变量可以有多个引用
🧡引用一旦引用一个实体,再不能引用其他实体

类型& 引用变量名(对象名) = 引用实体;

void TestRef(){    int a = 10;    int& ra = a;//<====定义引用类型    printf("%p\n", &a);    printf("%p\n", &ra);}

        🏆2.常引用 

void TestConstRef(){const int a = 10;//int& ra = a; // a为常量,加了const不能修改,那自己和别名都不可以修改,此行为属于权限放大//要变成cosnt int& ra = a;int b = 10;int& rb = b;const int& crb = b;//此行为可以使用,由可以改变变成不可以改变,属于权限缩小int c = 10;double b = 1.1;b = c;//隐式类型转换,中间会产生临时变量,类型为couble类型const int& b = 10;double d = 12.34;//int& rd = d; // 权限放大,是可读可写的const int& rd = d;//中间产生临时变量,rd为临时变量的·别名,临时变量具有常性,不能改变,所以加const就不会报错了}

                🍍做参数

void Swap(int& left, int& right){    int temp = left;    left = right;    right = temp;}

               🍍做返回值 

 🧡传值返回,ret引用的不是C,是一个临时变量,临时变量具有常性,所以要加const

int Add(int a,int b){    int c = a + b;    return c;}int main(){    const int& ret = Add(1,2) ;    return 0;}

🧡传引用返回,返回的是对象C的引用

🧡下面打印输出的是7,函数返回的是C的别名,而C的别名是ret所以就返回

int& Add(int a, int b){    int c = a + b;    return c;}int main(){    int& ret = Add(1, 2);    Add(3, 4);    cout << "Add(1, 2) is :"<< ret <<endl;    return 0;}

 🧡下面打印的是随机值,首先我们直到栈是从高地址向低地址开辟空间,堆是从低地址向高地址开辟空间的,那么栈开辟的空间使用完后就会销毁,那么printf又重新使用了之前Add的空间,所以改变了这里面的值

int& Add(int a, int b){    int c = a + b;    return c;}int main(){    int& ret = Add(1, 2);    Add(3, 4);    printf("hello!");    cout << "Add(1, 2) is :"<< ret <<endl;    return 0;}

 🧡此时函数执行完后,输出ret的值是3,因为static 存放的变量是在静态区中,就算函数销毁了也不会改变C的值,ret是静态区当中的别名,加const不会改变C的生命名周期和存储周期,只是不能修改而已。

char* p = "hello"

上面p还是一个指针变量,只有hello是存在常量区,也就是 *p 是存在常量区间

int& Add(int a, int b){    static int c = a + b;    return c;}int main(){    int& ret = Add(1, 2);    Add(3, 4);    printf("hello!");    cout << "Add(1, 2) is :"<< ret <<endl;    return 0;}

 🧡传值返回是返回拷贝,传引用返回返回的是别名(临时变量),如果是以下代码,是引用,返回别名,但是函数销毁了,返回就是随机值

int& Add(int a, int b){    int c = a + b;    return c;}int main(){    int ret = Add(1, 2);    Add(3, 4);    cout << "Add(1, 2) is :"<< ret <<endl;    return 0;}

                🍍两者比较 

🧡传值返回会产生拷贝,传引用传参和传引用作返回值不会拷贝,避免深拷贝,可以提高效率

                🍍引用和指针的区别 

int main(){    int a = 10;    //在语法上,这里给a这块空间取了一个别名,没有新开空间    int& ra = a;    ra = 20;    //在语法上,这里定义个pa指针变量,开了4个字节,存储a的地址    int* pa = &a;    *pa = 20;    return 0;}

🧡引用和指针的不同点:

        1.从语法上来说,引用定义一个变量的别名,指针存储一个变量的地址

        2.引用在定义时必须初始化,指针没有要求,int& ra;(×) int* p;(√)

        3.引用在初始化时引用一个实体后,就不能在引用其他实体,而指针可以在任何事之后指向任何一个同类型的实体

        4.没有NULL引用,但有NULL指针

        5.在sizeof中含义不同,引用结果为引用类型大小,但指针始终是地址空间所占字节个数(有占4个字节和8个字节)

        6.引用自加及引用实体增加1,指针自加及指针向后便宜一个类型的大小

        7.有多级指针,没有多级引用

        8.访问实体方式不同,指针需要显示解引用,引用编译器自己处理

        9.引用比指针使用更加安全

int* p = NULL;int*& rp = p;

 🧡rp类型是int*, 是p的引用

💎七、内联函数

        🏆1.概念

🧡C语言为了避免小函数建立栈帧,提供宏函数,宏函数在预处理阶段展开。

        优点:提高性能,增强代码复用性

        缺点:不支持调试,宏函数语法复杂,容易出错,没有类安全检查

🧡以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。

🧡release版本里面,没有call来开辟函数栈帧

🧡在debug版本,按照以下配置,也可以将debug消除call

inline int Add(int left,int right){    return left + right;}int main(){    int ret = 0;    ret = Add(1,2);    return 0;}

🧡假设上面的函数有100条指令(一条汇编代码对应一条指令),调用上面的函数10次,函数栈帧总共会有110条指令(函数内有100条指令,十次调用用了10次call),inline调用总共就是1000条指令(每次调用都要展开)

🧡指令变多意味,编译出来可执行程序变大,内存占用越多

        🏆2.特性 

🧡结论:平调用小函数建议定义成inline 

        1.内联是空间换时间的做法,省去函数调用的开销,所以代码很长或者有循环和递归的函数不适合使用内联函数

        2.如果函数太大或者有递归,编译器优化时会忽略内联

        3.inline不建议声明和定义分离,分离会导致链接错误,因为inline被展开了,就没有函数地址了,链接就会找不到

// F.h#include using namespace std;inline void f(int i);// F.cpp#include "F.h"void f(int i){    cout << i << endl;}// main.cpp#include "F.h"int main(){    f(10);    return 0;}

🧡一个在头文件用inline,一个在主函数不用inline,会导致链接错误。

 🧡C++替换宏的方法:

        1.函数定义用内联函数

        2.常量定义用const

💎八、auto关键字

        🏆1.介绍

🧡auto修饰的变量,是具有自动存储器的局部变量,用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&,auto看的是“ = ”之后变量的类型。

int main(){    int a = 10;    auto b = a;//类型声明成auto,可根据a的类型自动推导b}

 🧡当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量

void TestAuto(){    auto a = 1, b = 2;    auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同}

        🏆2.使用方法 

🧡与指针结合使用

int main(){    int x = 10;    auto a = &x;//int*    auto* b = &x;//int*    auto& c = x;//int}

        🏆3.不能使用场景

🧡auto不能作为函数的参数

// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导void TestAuto(auto a){}

 🧡auto不能直接用来声明数组

void TestAuto(){    int a[] = {1,2,3};    auto b[] = {4,5,6};}

💎九、基于范围的for循环

🧡for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。

void TestFor(){    int array[] = { 1, 2, 3, 4, 5 };    for(auto& e : array)   //auto&e是引用array数组中的元素,效果是将元素乘2 e *= 2;    for(auto e : array)    //自动遍历依次取出array中的元素赋值给e,直到结束 cout << e << " ";    return 0;}

🧡注意:可以用continue来结束本次循环,也可以用break来跳出整个循环

 🧡条件:

        1.for循环迭代的范围必须是确定的,对于数组而言,就是数组中第一个元素和最后一个元素的范围
        2.迭代的对象要实现++和==的操作

💎十、指针空值--nullptr

🧡C++中最好nullptr,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同,在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的,定义为

#define NULL ((void *)0)

🧡NULL,宏定义为0,所以后时候在使用时,就默认八NULL当作数字0,如果要使用作为指针的话,必须对其进行强转(void *)0

#define NULL 0

💎总结

🧡断断续续的看了一个星期左右才搞懂了这些知识,知识很多很杂,不仅现在学了,以后还要再次体会。