• lesson0-C++入门(6000余字详细配图讲解)


    个人主页:Lei宝啊 

    愿所有美好如期而遇



    目录

    #1. C++关键字

    #2. 命名空间

    ​编辑

    #3. C++输入&输出

    #4. 缺省参数

    #5. 函数重载

    #6. 引用

    #7. 内联函数

    #8. auto关键字(C++11)

    #9. 基于范围的for循环(C++11)

    #10. 指针空值---nullptr(C++11)


    #1. C++关键字

    C++总计63个关键字,C语言32个关键字,C++兼容%99的C语言,只有极少部分在C++中不可使用,后面我们会提到,关键字没有必要现在全部知道,后面可以慢慢学。

    #2. 命名空间

     首先我们通过几个C语言代码引入。

    假如说我们今天引入一个头文件,里面有一个函数叫做rand()

    1. #include
    2. #include
    3. int rand = 0;
    4. int main()
    5. {
    6. printf("%d", rand);
    7. return 0;
    8. }

    首先我们知道stdlib头文件展开后,rand函数就也在全局出来了,于是和我们定义的rand就命名冲突了,如果说我们定义在主函数中,那么就是局部优先,不会冲突。

    1. #include
    2. #include
    3. int main()
    4. {
    5. int rand = 0;
    6. printf("%d", rand);
    7. return 0;
    8. }

    那么我们如何去避免命名冲突这种情况发生?很遗憾,C语言无法避免,于是C++里解决了这个问题,他引入了命名空间,命名空间里变量和函数的生命周期还是全局,但是如果不展开或者使用域作用限定符,那么命名空间里的一系列变量和函数都无法使用,看例子:

    域作用限定符 ::

     一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中 

    1. #include <iostream>
    2. using namespace std;
    3. namespace jj_jntm
    4. {
    5. int a = 0;
    6. int Add(int x = 0, int y = 0)
    7. {
    8. return x + y;
    9. }
    10. void COUT()
    11. {
    12. int x = a;
    13. cout << x << endl;
    14. }
    15. }
    16. int main()
    17. {
    18. int a = 1;
    19. cout << a << endl;
    20. cout << jj_jntm::a << endl;
    21. cout << jj_jntm::Add(1, 2) << endl;
    22. a = 2;
    23. //外面的a改变不会影响命名空间里的a,两者是独立的。
    24. jj_jntm::COUT();
    25. //只有命名空间里的a改动时,命名空间里调用的该a才会改变
    26. jj_jntm::a = 3;
    27. jj_jntm::COUT();
    28. return 0;
    29. }

    命名空间还可以嵌套。

    1. #include <iostream>
    2. using namespace std;
    3. namespace jj_jntm
    4. {
    5. int a = 0;
    6. int Add(int x = 0, int y = 0)
    7. {
    8. return x + y;
    9. }
    10. void COUT()
    11. {
    12. int x = a;
    13. cout << x << endl;
    14. }
    15. namespace basketball
    16. {
    17. int kk = 0;
    18. }
    19. }
    20. int main()
    21. {
    22. cout << jj_jntm::basketball::kk << endl;
    23. return 0;
    24. }

    同一个工程允许命名空间的名字相同,因为编译器最后会将他们合成一个命名空间。

    使用 using 将命名空间中某个成员引入
    1. #include <iostream>
    2. using namespace std;
    3. namespace jj_jntm
    4. {
    5. int a = 0;
    6. int Add(int x = 0, int y = 0)
    7. {
    8. return x + y;
    9. }
    10. void COUT()
    11. {
    12. int x = a;
    13. cout << x << endl;
    14. }
    15. namespace basketball
    16. {
    17. int kk = 0;
    18. }
    19. }
    20. using jj_jntm::a;
    21. using jj_jntm::COUT;
    22. int main()
    23. {
    24. int a = 9;
    25. COUT();
    26. //cout << jj_jntm::basketball::kk << endl;
    27. return 0;
    28. }

    为什么呢?我们使用using展开部分命名空间,但是这与头文件展开完全是不同的,using仅仅是允许编译器进入namspace里对其进行搜索,我们不展开的时候,或者不指定的时候,编译器默认不会进去搜索的。

    所以我们展开a和COUT,意思就是允许编译器在命名空间里去寻找了,而后定义的a赋值9不会影响到命名空间里的a,因为他是重新定义了一个局部变量。

    这个就是在主函数中找不到a,我们就去全局找,又因为我们展开了a,所以可以在命名空间里找到a,于是命名空间里的a被修改为了6。

     使用using namespace 命名空间名称 引入(展开到全局)

    1. #include <iostream>
    2. using namespace std;
    3. namespace jj_jntm
    4. {
    5. int a = 0;
    6. int Add(int x = 0, int y = 0)
    7. {
    8. return x + y;
    9. }
    10. void COUT()
    11. {
    12. int x = a;
    13. cout << x << endl;
    14. }
    15. namespace basketball
    16. {
    17. int kk = 0;
    18. }
    19. }
    20. using namespace jj_jntm;
    21. int main()
    22. {
    23. int a = 3;
    24. cout << Add(a, 2) << endl;
    25. cout << Add(jj_jntm::a, 2) << endl;
    26. COUT();
    27. //cout << jj_jntm::basketball::kk << endl;
    28. return 0;
    29. }

     

    #3. C++输入&输出

    使用cout标准输出对象(控制台)cin标准输入对象(键盘)时,必须包含< iostream >头文件

    输入:std::cin 

    输出:std::cout

    换行:std:endl

    >> << 可以看做流向。

    1. #include <iostream>
    2. using std::cout;
    3. using std::cin;
    4. int main()
    5. {
    6. int a = 0;
    7. //从键盘输入数据流入a中
    8. std::cin >> a;
    9. //a先流出到屏幕上,而后是换行符
    10. std::cout << a << std::endl;
    11. int b = 0;
    12. cin >> b;
    13. cout << b << std::endl;
    14. return 0;
    15. }

     

    当然,我们最好是能在输入前有个提示。

    #4. 缺省参数

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

    我们来看代码:

    1. #include <iostream>
    2. using std::cout;
    3. using std::cin;
    4. using std::endl;
    5. int Add(int a = 0, int b = 0)
    6. {
    7. return a + b;
    8. }
    9. int main()
    10. {
    11. cout << Add(1, 1) << endl;
    12. cout << Add() << endl;
    13. cout << Add(1) << endl;
    14. return 0;
    15. }

     值得注意的是,实参必须从左向右给,缺省值必须从右向左缺。

    1. int Add(int a = 1, int b = 2, int c = 3)
    2. {
    3. return a + b + c;
    4. }
    5. int main()
    6. {
    7. cout << Add() << endl;
    8. cout << Add(2) << endl;
    9. cout << Add(2,3) << endl;
    10. cout << Add(2, 3, 4) << endl;
    11. return 0;
    12. }

    你也许会有疑问,我能不能跳着给实参,或者跳着缺省?

    那么如果是这样,我怎么能知道这个2是传给b的还是传给c的,也就是说,1和2有一个是传给b的,那剩下的那个是给哪个缺省值呢?无法区分。 

    另外,如果函数的声明和定义不在同一个文件,而我们又想给函数缺省值,那么就给在声明,不可同时给。

    这显然不可以,所以不能同时给,就是为了避免这样的情况。

    至于说我难道不能给定义吗?一定要给声明?好好好,我写了一个函数,但是不给你源码,只给你用,给你个接口,你说我缺省在定义里,你怎么知道这个函数该不该缺省,所以说写在声明里。

    #5. 函数重载

    C语言中,假设我们有一个函数,叫做

    int Add(int a,int b),这是一个加法函数,返回值为int类型,当我们想做double类型的两个数相加时,那么就要再写一个函数,并且重新起一个名字,不管是叫Add_double也好,还是叫什么,如果我们有多个加法函数,那么要起多少个名字?总之是给我们带来了不便,我们就想,能不能不改名字,还能找到这样的函数,C语言不可以,于是C++有了函数重载

    1 、参数类型不同
    1. #include <iostream>
    2. using namespace std;
    3. int Add(int a, int b)
    4. {
    5. return a + b;
    6. }
    7. double Add(double a, double b)
    8. {
    9. return a + b;
    10. }
    11. int main()
    12. {
    13. cout << Add(1314.0, 0.0);
    14. cout << Add(500, 20) << endl;
    15. return 0;
    16. }

    2、参数个数不同 

    1. #include <iostream>
    2. using namespace std;
    3. void f()
    4. {
    5. cout << "我没有参数" << endl;
    6. }
    7. void f(int a)
    8. {
    9. cout << "哈哈,我有参数:" << a << endl;
    10. }
    11. int main()
    12. {
    13. f();
    14. f(6);
    15. return 0;
    16. }

     3、参数类型顺序不同

    1. #include <iostream>
    2. using namespace std;
    3. int Add(int a, int b)
    4. {
    5. return a + b;
    6. }
    7. int Add(int a, int b, int c)
    8. {
    9. return a + b + c;
    10. }
    11. int main()
    12. {
    13. cout << "两个参数:>" << Add(1, 2) << endl;
    14. cout << "三个参数:>" << Add(1, 2, 3) << endl;
    15. return 0;
    16. }

    #6. 引用

    引用概念
    引用 不是新定义一个变量,而 是给已存在变量取了一个别名 ,编译器不会为引用变量开辟内存空 间,它和它引用的变量共用同一块内存空间

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

    1. int main()
    2. {
    3. int a = 6;
    4. int& b = a;
    5. cout << "a地址> " << &a << " b地址> " << &b << endl;
    6. return 0;
    7. }

    所以b就是a的别名,好比你的大名和小名,不管叫那个都是你。

    1. void Swap(int* a, int* b)
    2. {
    3. int temp = *a;
    4. *a = *b;
    5. *b = temp;
    6. }
    7. int main()
    8. {
    9. int a = 1;
    10. int b = 2;
    11. Swap(&a, &b);
    12. cout << "a = " << a << " b = " << b << endl;
    13. return 0;
    14. }

    1. void Swap(int& a, int& b)
    2. {
    3. int temp = a;
    4. a = b;
    5. b = temp;
    6. }
    7. int main()
    8. {
    9. int a = 1;
    10. int b = 2;
    11. Swap(a, b);
    12. cout << "a = " << a << " b = " << b << endl;
    13. return 0;
    14. }

    引用特性

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

    1. int main()
    2. {
    3. int a = 0;
    4. int& b = a;
    5. int& c = a;
    6. int& d = c;
    7. cout << a << b << c << d << endl;
    8. return 0;
    9. }

    1. int main()
    2. {
    3. int a = 0;
    4. int b = 1;
    5. int& c = a;
    6. c = b;
    7. cout << c << endl;
    8. return 0;
    9. }

    1. int main()
    2. {
    3. int a = 0;
    4. int b = 1;
    5. int& c = a;
    6. c = b;
    7. cout << c << endl;
    8. cout << "&a = " << &a << "\n&c = " << &c << "\n&b = " << &b << endl;
    9. return 0;
    10. }

    常引用

     

    1. const int a = 0;
    2. const int& c = a;
    3. const int& e = 10;
    4. //这是OK的
    5. const double& d = a;

    至于上面的const限定的为什么可以,我们先来看代码。

    1. int main()
    2. {
    3. int a = 9;
    4. double c = a;
    5. return 0;
    6. }

    这里为什么可以呢?

    在强制类型转换时,会生成一个临时变量,将临时变量的值强制转换后在赋值。

     

    使用场景

    1. 做参数

    我们上面的交换函数就是。

    2.做返回值
    1. int& Add(int a, int b)
    2. {
    3. static int c = a + b;
    4. return c;
    5. }
    6. int main()
    7. {
    8. int& a = Add(3, 5);
    9. cout << a << endl;
    10. a = 98;
    11. a = Add(a, 2);
    12. cout << a << endl;
    13. return 0;
    14. }

    怎么会是98呢?不应该是100吗?
    注意:static 修饰的变量只会初始化一次,再次调用时跳过初始化语句。
    1. int& Add(int a, int b)
    2. {
    3. static int c;
    4. c = a + b;
    5. return c;
    6. }
    7. int main()
    8. {
    9. int& a = Add(3, 5);
    10. cout << a << endl;
    11. a = 98;
    12. a = Add(a, 2);
    13. cout << a << endl;
    14. return 0;
    15. }

     那么问题是,我直接返回值不好吗,为什么要引用返回呢?

    1.以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直 接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效 率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低.

    引用和指针的区别

     语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。

    1. int main()
    2. {
    3. int a = 10;
    4. int& ra = a;
    5. cout << "&a = " << &a << endl;
    6. cout << "&ra = " << &ra << endl;
    7. return 0;
    8. }

    底层实现上 实际是有空间的,因为 引用是按照指针方式来实现 的。

    引用和指针的不同点:
    1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
    2. 引用 在定义时 必须初始化 ,指针没有要求
    3. 引用 在初始化时引用一个实体后,就 不能再引用其他实体 ,而指针可以在任何时候指向任何 一个同类型实体
    4. 没有 NULL 引用 ,但有 NULL 指针
    5. sizeof 中含义不同 引用 结果为 引用类型的大小 ,但 指针 始终是 地址空间所占字节个数 (32 位平台下占4 个字节 )
    6. 引用自加即引用的实体增加 1 ,指针自加即指针向后偏移一个类型的大小
    7. 有多级指针,但是没有多级引用
    8. 访问实体方式不同, 指针需要显式解引用,引用编译器自己处理
    9. 引用比指针使用起来 相对 更安全

    对5的解释:

    1. int main()
    2. {
    3. char a = 4;
    4. char* pa = &a;
    5. char& b = a;
    6. cout << sizeof(pa) << endl;
    7. cout << sizeof(b) << endl;
    8. return 0;
    9. }

    对9的解释:

    不会有野指针这样的风险,而且频繁使用“*指针变量名”的形式进行运算容易产生错误而且可阅读性较差。

    #7. 内联函数

    inline放在我们写的函数的前面,如:inline int Add(int a,int b);

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

    内联函数有什么作用? 

    简单来说就是减少栈帧的开销,我们来看调用一个函数的过程。

    我们要给这个函数建立栈帧,然后执行下面一系列操作。

    但是inline函数就相当于这样。

    不需要call 函数的地址,进入后再去执行。

    当我们将源文件编译结束,如果编译器响应的话,未忽略我们的inline建议,那么此时我们已经找不到这个函数的地址,他已经完成了替换,也就少了call 函数地址这个过程。

    我用的编译器是 ,在debug下,需要调一些东西inline才会替换,看图示:

    调成程序数据库。

    改为只适用于_inline,release版本下会优化。inline在合适的条件下会替换。

    1. inline int Add(int a, int b)
    2. {
    3. int c = a + b;
    4. return c;
    5. }
    6. int ADD(int a,int b)
    7. {
    8. return a + b;
    9. }
    10. int main()
    11. {
    12. int ret = ADD(1, 2);
    13. int a = Add(1, 2);
    14. return 0;
    15. }

    上面的ADD还会call函数地址,而Add直接替换展开了。 

     特性

    1. inline 是一种 以空间换时间 的做法,如果编译器将函数当成内联函数处理,在 编译阶段,会
    用函数体替换函数调用 ,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运
    行效率。
    2. inline 对于编译器而言只是一个建议,不同编译器关于 inline 实现机制可能不同 ,一般建
    议:将 函数规模较小 ( 即函数不是很长,具体没有准确的说法,取决于编译器内部实现 )
    是递归、且频繁调用 的函数采用 inline 修饰,否则编译器会忽略 inline 特性。
    3. inline 不建议声明和定义分离,分离会导致链接错误。因为 inline 被展开,就没有函数地址
    了,链接就会找不到。

    #8. auto关键字(C++11)

    auto声明的变量必须由编译器在编译时期推导而得

    1. #include <iostream>
    2. using namespace std;
    3. int main()
    4. {
    5. int a = 0;
    6. auto b = a;
    7. cout << typeid(b).name() << endl;
    8. return 0;
    9. }

     我们看到b是auto类型。

    当然,我们也可以这样测试一下,

    1. int main()
    2. {
    3. int a = 0;
    4. int* pa = &a;
    5. auto* p = &a;
    6. cout << typeid(pa).name() << endl;
    7. cout << typeid(p).name() << endl;
    8. return 0;
    9. }

     我们也可以发现,p的类型仍然匹配,auto的推导是正确的。

    那么auto可不可以做函数的参数和返回值呢?

    编译器明确告诉我们此处不可以使用auto,因为如果这样的话,我们的参数传什么类型,他就推导什么类型,返回值就可能不符合我们的预期,以及函数内部的实现也会出问题,至于返回值auto就更不可行了,让编译器去思考你想返回什么值?真是子非鱼,安知鱼之乐。

    同时,auto不能声明数组。

    #9. 基于范围的for循环(C++11)

    1. int main()
    2. {
    3. //第一种遍历数组的方式
    4. int arr[10] = { 1,2,3,4,5,6,7,8,9 };
    5. for (int i = 0; i < 10; i++)
    6. {
    7. cout << arr[i] << " ";
    8. }
    9. cout << endl;
    10. //第二种就是范围for
    11. for (auto a : arr)
    12. {
    13. cout << a << " ";
    14. }
    15. cout << endl;
    16. return 0;
    17. }

    注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环

    另外要说到的一点是以下方式是不可以的 ,for循环迭代的范围必须是确定的,我们传过去一个指针,没有办法确定他的范围在哪里,对于数组而言,就是数组中第一个元素和最后一个元素的范围

    #10. 指针空值---nullptr(C++11)

     我们在C语言中使用NULL,但是NULL有一个缺陷。

    NULL实际是一个宏,在传统的C头文件(stddef.h)中,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量,不论采取何 种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦。

    1. void A(int)
    2. {
    3. cout << "int" << endl;
    4. }
    5. void A(int*)
    6. {
    7. cout << "int*" << endl;
    8. }
    9. int main()
    10. {
    11. A(0);
    12. A(NULL);
    13. A((int*)NULL);
    14. return 0;
    15. }

    应该输出什么呢?

    基于这样的情况,所以C++引进了nullptr

    1. void A(int)
    2. {
    3. cout << "int" << endl;
    4. }
    5. void A(int*)
    6. {
    7. cout << "int*" << endl;
    8. }
    9. int main()
    10. {
    11. //A(0);
    12. //A(NULL);
    13. A(0);
    14. A(nullptr);
    15. return 0;
    16. }

    注意:
    1. 在使用 nullptr 表示指针空值时,不需要包含头文件,因为 nullptr C++11 作为新关键字引入
    2. C++11 中, sizeof(nullptr) sizeof((void*)0) 所占的字节数相同。
    3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用 nullptr

  • 相关阅读:
    我的Primavera Unifier学习环境介绍(软件+硬件)
    裸辞,我去面试Python岗位了
    WPS的excel表格设置了编辑权限,要怎么取消?
    Unity3d框架搭建 使用 类对象池技术 优化 C#语言 GC
    毛玻璃跟随鼠标移动
    在Remix中编写你的第一份智能合约
    MATLAB | 如何绘制二维散点主方向直方图
    一文详解手眼标定公式推导
    【COSTAS环】基于FPGA的costas环载波同步的Verilog实现
    CTC 技术介绍概述——啃论文系列
  • 原文地址:https://blog.csdn.net/m0_74824254/article/details/133846094