• 【C++笔记】C++基础入门


    人生中第一个C++程序

    1. #include<iostream>
    2. int main()
    3. {
    4. std::cout << "hello world!" << std::endl;
    5. return 0;
    6. }

    1.C++关键字

    根据(C++98)标准,C++总计63个关键字,C语言32个关键字。

    Mirosoft visual stdio 2022标识:

    2.命名空间

    在C/C++中,变量,函数和类都是大量存在的,变量,函数,类的名称都将存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的:对表示的名称进行本地化。以避免命名冲突或名字污染

    关键字:namespace

    关于命名冲突举例:

    了解C语言都清楚malloc用于向计算机申请空间的函数。

    1. #include<stdio.h>
    2. //#include<stdlib.h>
    3. int malloc = 0;
    4. int main()
    5. {
    6. printf("%d\n", malloc);
    7. return 0;
    8. }

    1. #include<stdio.h>
    2. #include<stdlib.h>
    3. int malloc = 0;
    4. int main()
    5. {
    6. printf("%d\n", malloc);
    7. return 0;
    8. }

     

     当包含malloc所在的头文件时,原先的代码直接报错,如果是因为变量命名冲突导致报错,那就非常的搞人心态了。

    C++贴心的使用命名空间搞掉了这个问题。

    2.1命名空间定义

    定义命名空间,需要使用关键字namespace,后面跟命名空间的名字,然后跟上一对{},{}中即为命名空间的成员。

    举个栗子:

    1. #include<iostream>
    2. //普通的命名空间
    3. namespace T1//以T1作为命名空间的名字
    4. {
    5. //内容中可以定义 变量,函数,结构体
    6. char a;
    7. void test()
    8. {
    9. printf("hello world\n");
    10. }
    11. struct ListNode
    12. {
    13. int val;
    14. struct ListNode* next;
    15. };
    16. }
    1. //进阶
    2. //命名空间就像是一个“大函数”,函数能够嵌套,那么命名空间也能嵌套
    3. namespace T2
    4. {
    5. char q;
    6. namespace T3
    7. {
    8. int val;
    9. }
    10. }
    1. //同一个工程中可存在多个名称相同的命名空间,编译器在链接时会将其自动合并
    2. namespace T1
    3. {
    4. int num;
    5. double e;
    6. }

     

    在namespace中定义的变量,函数,结构体等内容的作用域都将受限在干该命名空间中。

    注意:在同一个域中,不能有同名变量。

    2.2命名空间的使用

    创建了命名空间,解决了可能存在的命名冲突问题,那么该怎么使用这些定义的变量。上文提及过,定义的所用内容的作用域都将受限于其命名空间中,所以直接在其他函数内是无法被使用的。

    C++提供了操作符:作用域限定操作符::

    1.加命名空间名称及作用域操作符

    这就是老实人手动访问

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

     

    这里是只把T1中的test函数放出来。

    3.使用using namespace 命名空间名称引入

    这里是将T1中定义的东西全部释放出来。

    所以可以解决之前的疑问:为啥要在文件中加using namespace std;

    如果不加呢?

    可知,cout 和 endl 是包含在C++标准库中的,而且这个库还是一个单独

    的命名空间。

    在某些情况下,将库完全放出来会出现命名污染的情况,需要注意。

    不将标准库中的所有放出来,可以考虑使 1 用和 2.

     

     

     

    3.C++输入和输出

    针对人生第一个C++程序,hello world的说明:

    • 使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含<iostream>头文件以及std标准命名空间。
    • 使用C++输入输出更方便,不需要加数据格式化控制。

    4.缺省参数

    有个备胎,走在路上都安心了好多(doge)

    C++中的函数参数也是可以有备胎的。

    4.1缺省函数的概念

    缺省函数是生命或者定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则

    采用默认值,否则使用指定的实参。

    举例子:

    1. void test(int i = 1)
    2. {
    3. cout << i << endl;
    4. }
    5. int main()
    6. {
    7. test();
    8. test(20);
    9. return 0;
    10. }

     

     可见:没有传参时,使用参数为默认值,传参时,使用指定的实参。

    4.2缺省参数分类

    • 全缺省参数
      1. void fun(int a = 10, int b = 20, int c = 30)
      2. {
      3. cout << "a = " << a << endl;
      4. cout << "b = " << b << endl;
      5. cout << "c = " << c << endl;
      6. }
      7. int main()
      8. {
      9. fun();
      10. return 0;
      11. }

    • 半缺省参数

      1. void fun(int a = 10, int b = 20, int c = 30)
      2. {
      3. cout << "a = " << a << endl;
      4. cout << "b = " << b << endl;
      5. cout << "c = " << c << endl;
      6. }
      7. int main()
      8. {
      9. fun(1,2);
      10. return 0;
      11. }

       

      可知:这是一种部位,且这种部位是顺序的,即无法规定传递的实参2由c来接受。

      注意:

      1.版缺省参数必须从右往左依次来给出,不能间隔着。

      2.缺省参数不能再函数声明和定义中同时出现。

      3.缺省值必须是常量或者全局变量。

      4.C语言不支持

    5.函数重载

    5.1概念

    函数重载:函数的一个特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,

    这些同名函数的形参列表(参数个数/类型/顺序)必须不同,常用来处理实现功能类似数据类型不同的问题。

    举个栗子:

    1. int Add(int a, int b)
    2. {
    3. return a + b;
    4. }
    5. double Add(double a, int b)
    6. {
    7. return a + b;
    8. }
    9. int main()
    10. {
    11. Add(1, 2);
    12. Add(1.6, 2);
    13. cout << "Add(1,2) = " << Add(1, 2) << endl;
    14. cout << "Add(1.6,2) = " << Add(1.6, 2) << endl;
    15. return 0;
    16. }

     

    这里可以看到:虽然调用了“相同的函数接口”,但是实际上调用的函数接口是不同的。

    构成函数重载的关键:

    参数个数,类型,顺序中的至少一个即可。

    6.引用

    6.1引用概念

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

    它和它引用的变量共用一块内存空间

    比如:《西游记》里的猴子,敬称“齐天大圣”,法号“悟空”。指的都是同一人。

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

    举个例子:

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

     

    由此可以判定:引用不是一种赋值操作,而是一种绑定操作。

    6.2引用特性

    1.引用在定义时必须初始化。

    2.一个变量可以有多个引用。

    3.引用一旦引用一个实体,就不能再引用其它实体。

    1. int main()
    2. {
    3. int a = 10;
    4. //int& b;//会报错
    5. int& c = a;
    6. int& d = c;
    7. printf("%p\n%p\n%p\n",&a,&c,&d);
    8. return 0;
    9. }

     

    6.3常引用

    引用针对变量有一套语法,那对于常量语法还能使用吗?

    答案是不能。

    1. int main()
    2. {
    3. int a = 10;
    4. int& b = a;
    5. //那要直接给常量10取别名,而不是对变量a取别名。
    6. //int& c = 10;//报错
    7. //为什么?
    8. //常量10只能读不能写,那直接引用会将其权限扩大,是不被允许的。
    9. const int& c = 10;
    10. const int& d = a;
    11. return 0;
    12. }

    加上const 可以解决问题,那对于变量引用能否使用const修饰?

    答案是可以的。

    可以得知:

    取别名原则:对原引用变量,权限(读写权限)只能缩小,不能放大。

    再看一组代码:

    1. int main()
    2. {
    3. double a = 1.6;
    4. //int& b = a; //类型不一样,会报错。
    5. const int& b = a;//不会报错
    6. cout << "a = " << a << endl;
    7. cout << "b = " << b << endl;
    8. }

     

     

    但是其中的机制又是什么?

    a为浮点型,b为整形,之间的转换涉及类型转换,会生成一个临时变量存放在寄存器中。

    我们引用的b实际上是那个临时变量的。

    验证:

    可见虽然b是a的引用,但是地址是不同的。

    这里也可以得知:临时变量是具有常性的

    6.4使用场景

    1.做参数

    使用引用可以规避指针问题

    初始C语言时:写过交换函数

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

    这个写法是错误的

    交换了临时变量但实参却没有解决。但引用就可以完美解决。

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

     

    2.做返回值

    函数返回值在出了作用域时,会销毁栈帧,原先的返回值会放在寄存器中(拷贝)

    最后才会将结果赋值给你所创建在调用函数接口的变量中(拷贝)

    1. int& f()
    2. {
    3. static int a = 0;
    4. a++;
    5. return a;
    6. }
    7. int main()
    8. {
    9. int b = f();
    10. return 0;
    11. }

    传值返回:会出现一个拷贝。

    传引用返回:没有这个拷贝了,函数返回的直接就是返回变量的别名。

    这似乎能提高计算机的速度,但是有一个致命的问题,引用的原对象必须

    ”活着“,否则这个引用就会越界访问,是违法的。

    这里使用了static将a放进了静态区,生命周期为整个程序的生命周期,当然不会出现越界访问。

    体验临时变量生命周期结束的情况:

    1. int& Add(int a, int b)
    2. {
    3. int c = 0;
    4. c = a + b;
    5. return c;
    6. }
    7. int main()
    8. {
    9. int& ret = Add(1, 2);//ret为c的别名
    10. Add(3, 4);//再次调用函数,返回是被修改的c
    11. cout << "Add(1,2) : " << ret << endl;//第一次打印
    12. cout << "Add(1,2) : " << ret << endl;//第二次打印时,原先的c的栈帧已被销毁。
    13. cout << "Add(1,2) : " << ret << endl;//同第二次
    14. return 0;
    15. }

     

    所以:如果函数返回时,出了函数作用域,如果返回对象还未交还给系统,则可以使用引用返回,

    如果已经返回给系统里,那就必须使用传值返回。

    6.5针对值,指针和引用最返回值类型在性能上的比较

    1. #include<time.h>
    2. typedef struct Test
    3. {
    4. int Arr[100000];
    5. }ST;
    6. ST st;
    7. ST test1()
    8. {
    9. return st;
    10. }
    11. ST& test2()
    12. {
    13. return st;
    14. }
    15. ST* test3()
    16. {
    17. return &st;
    18. }
    19. int main()
    20. {
    21. //值返回
    22. size_t begin1 = clock();
    23. for (int i = 0; i < 10000; i++)
    24. {
    25. test1();
    26. }
    27. size_t end1 = clock();
    28. //引用返回
    29. size_t begin2 = clock();
    30. for (int i = 0; i < 10000; i++)
    31. {
    32. test2();
    33. }
    34. size_t end2 = clock();
    35. //指针返回
    36. size_t begin3 = clock();
    37. for (int i = 0; i < 10000; i++)
    38. {
    39. test3();
    40. }
    41. size_t end3 = clock();
    42. cout << "值返回:" << end1 - begin1 << endl;
    43. cout << "引用返回:" << end2 - begin2 << endl;
    44. cout << "指针返回:" << end3 - begin3 << endl;
    45. return 0;
    46. }

     

    通过上述比较,可知指针返回和引用返回的效率差不多,值返回效率最差。

    6.6引用和指针的区别

    引用:在概念上引用只是原变量的一个别名,没有独立的空间,和引用实体公用同一块空间。

    指针:要开辟空间,为4/8字节。

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

    引用

    指针

    初始化

    必须初始化

    无要求

    指向性

    不能引用多个实体

    可指向任一实体

    NULL

    无NULL引用

    NULL指针

    sizeof含义

    引用类型的大小

    指针地址空间所占字节个数

    自加

    实体增加1

    向后偏移一个类型的大小

    多级

    无多级引用

    有多级指针

    访问实体方式

    编译器自行处理

    需要解引用

    安全性

    引用比指针使用起来更加安全

    查看引用和指针的汇编代码

    7.内联函数

    7.1概念

    以inline修饰的函数叫做内联函数,编译时C++编译器会在掉哦用内联函数的地方展开。

    没有函数压栈的一系列消耗,内联函数可极大的提高效率。

    根据内联函数的基本概念,可知其与C语言中的宏有相似之处。

    对比C语言和C++内联函数

    1. 宏定义Add函数
    2. #define ADD(X,Y) ((X) + (Y))
    3. int main()
    4. {
    5. ADD(1, 2);
    6. cout << ADD(1, 2) << endl;
    7. return 0;
    8. }
    9. int main()
    10. {
    11. int ret = ADD(1, 2);
    12. cout << ret << endl;
    13. return 0;
    14. }
    1. //内联函数
    2. inline int ADD(int x, int y)
    3. {
    4. return x + y;
    5. }
    6. int main()
    7. {
    8. int ret = ADD(1, 2);
    9. cout << ret << endl;
    10. return 0;
    11. }

     

    7.2特性

    1.inline是一种以空间换时间的做法,省去函数调用栈帧的开销。所以代码很长或者有循环/递归的函数不适合作为内联函数。

    2.inline对于编译器是一种建议,所以如果定义为inline的函数体内有循环/递归等,编译器优化时会忽略内联。

    3.inline不推荐声明和定义分离,分离会导致连接错误。因为inline被展开就没有函数地址了,链接时就会报错。

    C++推出内联函数是为了解决C语言宏的晦涩难懂,容易出错,不方便调试的缺点。

    优点:满足C语言宏的所有优点,

    内联的写法与普通函数完全相同,极大的减少了编写的工程量。

    【面试题】

    宏的优缺点?

    优点

    缺点

    增强代码的可读性

    不方便调试宏(预编译阶段宏会被替换)

    提高性能

    代码可读性变差,维护性变差,易被误用

    没有类型安全的检查

    C++针对C语言宏的优化替代?

    1.常量定义 换用const

    2.函数定义 换用内联函数

    8.auto关键字

    8.1auto简介

    在C/C++赋予auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但随着编译器的优化

    auto似乎没再出现过。

    C++11中,auto被赋予了全新的含义:auto不再是一个储存类型的指示符,而是作为一个新的

    类型的指示符来指示编译器,auto生命的变量必须由编译器在编译时期推导而得。

    1. int main()
    2. {
    3. int a = 10;
    4. auto b = a;//int
    5. auto c = 'a';//char
    6. auto d = &a;//int*
    7. auto* e = &a;//int*
    8. auto& f = a;//int
    9. //typeid(a).name();用于查看变量类型
    10. cout << typeid(a).name() << endl;
    11. cout << typeid(b).name() << endl;
    12. cout << typeid(c).name() << endl;
    13. cout << typeid(d).name() << endl;
    14. cout << typeid(e).name() << endl;
    15. cout << typeid(f).name() << endl;
    16. //d = 20;
    17. return 0;
    18. }

     

    注意: auto g;非法,g的类型无法被识别。

    【注意】使用auto定义变量时必须对其进行初始化,在编译阶段需要根据初始化表达式来

    推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的占位符,

    编译器在预编译期将auto替换为变量实际的类型。

    那auto是否可以作为缺省参数进行传参?

    1. void AutoTest(auto a = 10)
    2. {}
    3. int main()
    4. {
    5. AutoTest();
    6. return 0;
    7. }

    答案是不允许的。

    8.2auto的使用细则

    1.auto与指针和引用结合起来使用

    用auto声明指针类型时,用auto或auto*没有任何区别,但用auto声明引用变量时必须加&

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

     

    2.在同一行定义多个变量

    在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器会报错。

    原因:编译器实际只会对第一个类型进行推导,然后用推导出来的类型定义其他变量。

    测试:

    1. void AutoTest()
    2. {
    3. auto a = 10, b = 20;
    4. auto c = 10.0, d = 20;
    5. }
    6. int main()
    7. {
    8. AutoTest();
    9. return 0;
    10. }

     

    8.3auto不能推导的场景

    • auto不能作为函数参数
    • auto不能用来声明数组
    • C++11只保留了auto作为类型指示符的用法
    • auto最常用的是配合范围for循环,和lambda表达式配合使用。

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

    9.1范围for的语法

    以遍历数组为例:

    C++98语法规则遍历

    1. int main()
    2. {
    3. int a[] = { 1,2,3,4,5,6,7,8,9 };
    4. for (int i = 0; i < sizeof(a)/sizeof(int); i++)
    5. {
    6. cout << a[i] << " ";
    7. }
    8. cout << endl;
    9. return 0;
    10. }

    C++11语法规则范围for遍历

    1. int main()
    2. {
    3. int a[] = { 1,2,3,4,5,6,7,8,9 };
    4. for (auto e : a)//范围for (auto 变量 : 数组名}
    5. {
    6. cout << e << " ";
    7. }
    8. cout << endl;
    9. return 0;
    10. }

     对于一个有范围的集合而言,使用范围for时比较优的选择。

    for循环后的括号由冒号”:“分为两部分:一部分时范围内用于迭代的变量,

    第二部分则表示被迭代的范围。

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

    9.2范围for的使用条件

    1.for循环的迭代的范围必须是确定的。

    对数组而言,就是数组中第一个元素和最后一个元素的范围;

    对类而言,提供beginend的范围,beginend就是for循环迭代的范围。

    错误示范:

    1. void ForTest(int a[])
    2. {
    3. for (auto e : a)
    4. {
    5. cout << e << " " << endl;
    6. }
    7. cout << endl;
    8. }
    9. int main()
    10. {
    11. int a[] = { 1,2,3,4,5,6,7,8,9 };
    12. ForTest(a);
    13. return 0;
    14. }

     

    原因:数组传参传递的是首元素地址,没有直接可确定的范围,自然范围for就是错误的。

    2.迭代器的对象实现++和==的操作。

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

    10.1C++98中的指针空值

    在C++98中可以这样定义空指针

    NULL实际上是一个宏,包含在C头文件(stddef.h)中,转到定义可知:

    NULL可能被定义为字面常量0或者被定义为(void*)常量,但这可能会出现指代不明的情况。

    1. void NULLTest(int)//字面常量
    2. {
    3. cout << "int" << endl;
    4. }
    5. void NULLTest(int*)//(void*)常量
    6. {
    7. cout << "int*" << endl;
    8. }
    9. int main()
    10. {
    11. NULLTest(0);//~int
    12. NULLTest(NULL);//希望调用int*
    13. NULLTest((int*)NULL);//~~int*
    14. NULLTest(nullptr);
    15. return 0;
    16. }

     

    可以看到,事与愿违,NULL调用的是int类型的字面常量。

    在C++98中,字面常量既可以是一个整型数字,也可以是个无类型的指针(void*)常量,

    编译器默认情况下将其看成一个整型常量,如果要按照指针的方式来使用,必须对其使用

    强转。

    C++11推出了新的nullptr来替代原有的NULL/0.

    注意:

    1.在使用nullptr表示指针为空时,不与要包含头文件,因为nullptrC++11版本下是关键字。

    2.C++中,sizeofnullptr)与sizeof((void*))所占字节相同。

    3.为了提高代码的健壮性,nullptrc++中可以完全替代NULL/0.

    如有错误,还请大佬指出!!

  • 相关阅读:
    Spring 事务失效的场景
    加 3 行代码减少 80% 构建时间
    SIFT和SURF的差异已经使用场景分析
    LeetCode 每日一题 2023/10/23-2023/10/29
    [C++] STL_stack && queue接口的模拟实现
    2.2 机器学习流程
    部门新来了个腾讯拿30k跳槽出来的,让我见识到了跳槽天花板
    Hutool工具包导入excel文件数据到数据库
    线性dp,优化记录,273. 分级
    基于神经网络算法LSTM模型对股票指数进行预测
  • 原文地址:https://blog.csdn.net/weixin_61932507/article/details/124950907