学习一年Java的程序员的C++学习记录(指针引用绕晕记)

2023-10-31

一 C++入门

  1. 标准输出流中 cout 是一个ostream对象,<<>>是C++中经过重载的运算符,配合cout和cin使用时表示流运算符。C++中是如何重载运算符的?

  2. cin 从键盘读取输入的时候,首先会忽略掉开头的任意多个空格输入,只会从第一个非空格符、制表符和回车符开始读取。开始读取后,cin会以一个回车符作为输入的结束标志,但不会读取这个回车符!因此这个回车符还会保存在输入缓存中。

    在这里插入图片描述

  3. 运算符优先级:比较运算符 大于 赋值运算符

  4. 为什么需要.h头文件?因为头文件中包含了一些全局属性的声明。在C++中,函数可以直接定义在类之外,因此有函数和方法的概念。函数是哪些直接定义在类外的,而方法是定义在类内部的。函数和方法的结构和功能都一致,仅仅是位置不同。定义在类之外的函数,是全局可用的,这点和类一致。因此,需要在使用某个函数的时候进行声明。当函数的声明变多的时候,头文件就起到了包含这些所需要声明方法的作用。一般的,某个源文件中通过同名的头文件来进行声明,需要使用该源文件中功能的时候,包含其头文件即可。

二 变量和数据类型

  1. C++是有一门强类型的语言(与Java相似,而Python是一门弱类型的语言)
  2. 作用域:每个{}内部定义的变量只在此{}内部有效,出了{}后变量无效,这样的变量被称为全局变量。如果是定义在所有{}之外的变量,则被称为全局变量,是全局可访问的。这点和函数、类一致。
  3. static关键字:在不同的地方的作用有所不同
    • 如果是在全局范围(所有{}之外的范围就是全局范围)或者命名空间范围内,static关键字指定变量或函数为内部链接,即该变量或函数仅仅在文件内部是可见的,外部无法进行访问。在声明变量时,变量具有静态持续时间,即生命周期从程序启动时新生到到程序结束时死亡。并且除非您指定另一个值,否则编译器会将变量初始化为0。
    • 如果是在函数范围内,static关键字指定变量只初始化一次,并在调用该函数后保留其状态,但是变量的作用域还是在该函数范围内。
    • 如果是在类内部声明成员变量,static关键字指定该类所有实例共享该变量的副本。必须在文件范围内定义静态数据成员。(与Java一致)
    • 如果是在类内部声明成员方法。static关键字表示可以通过类名对该方法进行调用。因此静态成员方法无法调用非静态成员变量,因为此时没有生成对象。(与Java一致)
  4. C++中,整型默认是可正可负的。对于每种类型的整型,C++都提供了一个无符号的类型。无符号数据类型在底层是如何存储的呢?应该还需要额外的字段来存储数据类型吧。
  5. char类型也是一种整数类型,可以直接定义char为0~127之间的整数,在显示时,计算机会将char类型显示为字符。如果char类型参与数值计算,显示时将作为整型显示。
  6. bool类型也是一种整数类型。bool类型有两种取值,true和false。可以直接定义bool类型变量为整型。但bool类型只会保存0和1。当定义bool为0时,bool会保存0;当定义bool为任何非0的值时(包括负数),bool都将保存1。
    • python、C++都可以讲bool类型和int类型隐式类型转换,在if表达式中可以直接使用1 和 0来表示true和false。
    • Java无法将boolean和int类型进行转化
  7. 在赋值时,字面值常量有自己的数据类型,变量被定义为某种数据类型,编译器会将字面值常亮转化成变量的数据类型,从而赋值给变量。这也解释了为什么所有非0的整数赋值给bool的时候都会转化成1。
  8. C++在赋值时的隐式类型转换非常广泛,可以是:
    • bool -> 整数类型,true是1,false是0
    • 整数类型 -> bool,非零为1
    • float -> int,只保留整数部分,发生精度丢失
    • int -> float:小数部分为0
    • 给无符号类型赋值,如果超出它表示范围,则结果是初始值对无符号类型能表示的总数取模后的余数
    • 给有符号类型赋值,如果超出了它的表示范围,则结果是未定义的(undefined)。此时,程序可能继续工作,也可能崩溃。
  9. C++中,问号也需要使用转义字符。

三 运算符

  1. 在基本数据类型中,除了void之外,其他数据类型都可以进行算数运算。

  2. 在进行算数运算中,精度不同的数据类型进行运算时,运算结果的数据类型取精度大的那个数据类型。

  3. 取余运算符%的两个操作数必须是整数类型

  4. 赋值运算的规则:

    • 赋值运算符=左侧必须是可修改的左值
    • 如果赋值运算符左右两侧的数据类型不同,就把右侧的对象转换成左侧对象的数据类型
    • C++ 11新标准提供了一种新的语法:用花括号{}括起来的数值列表,可以作为右值。这样就可以非常方便的给数组赋值了。
    • 赋值操作可以连续,从右到左依次执行。
    • 赋值运算符优先级很低,一般会执行其他运算符,最后进行赋值
  5. 短路求值:||的左侧为真,则会跳过右侧判断; &&左侧的结果为假,则会跳过右侧判断。可以用于特定左侧条件下执行右侧语句,能否代替if条件语句?

  6. 在C++中,如果要进行位移运算,那么长度小于int的数据类型会默认提升到int类型,运算的结果也是int类型。

  7. 在左移操作时,默认左移的位数是小于32的,如果大于等于32,则左移的位数会对32取模。例如左移34位相当于左移2位。

  8. 在自动类型转换的时候,一般会将长度较小的类型转换到长度较大的类型,从而避免精度丢失。但是在将浮点型转换为整数类型是,会丢弃小数部分,只保留整数部分,造成精度丢失。

  9. C++中强制类型转换的语法有三种:

    int total = 20, num = 6;
    // 方式一:c语言风格
    double avg = (double)total / num;
    // 方式二:c++风格
    double avg = double(total) / num;
    // 方式三:c++强制类型转换运算符
    double avg = static_cast<double>(total) / num;
    

四 流程控制

  1. switch-case中,每个case一般会配合break使用,表示跳出当前switch。如果case中没有加break,则会继续执行其他case中的语句,即使case没有匹配,直到遇见break。

    switch(变量){
        case 值1:
            ...;
            break;
        case 值2:
            ...;
            break;
        ...
        default:
            ...;
    }
    
  2. C++中的for循环和Java一样,都有两种方式:普通for循环和增强for循环。

  3. C++中有goto语句,只需要在某个代码上面做个标记,就可以使用goto进行跳转。goto语句非常灵活,但是也非常危险,很容易造成死循环。

五 复合数据类型

  1. 数组的定义

    int a1[10];
    const int n = 4;
    double a2[n];
    int a3[4] = {1, 2, 3, 4};
    double a4[] = {1.1, 2.2, 3.3}; // 自动推断长度
    short a5[10] = {3, 6, 9};
    short a6[2] = {1, 2, 3}; // 报错,初始值太多
    int a6[4] = a3; // 报错,不能用另一个数组对数组进行赋值
    
  2. 在定义数组时,数组的长度必须为常量,如果是变量会报错。(Java不会)

  3. 在方法中定义的数组的长度必须大于0

    int a[0]; //会报错,数组元素必须大于0
    
  4. 空数组可以定义,但仅限于类或者结构体中

  5. 在Visual studio中,对为定义的局部变量赋值为0xcc。如果直接打印,则会打印出"烫烫烫烫…"。

  6. C++中数组的长度是如何获取的?

    数组所占空间 = 数据类型所占空间大小 * 元素个数
    元素个数 = 数组所占空间 / 数据类型所占空间大小
    
  7. C++中,如果对数组进行越界访问,并不会报错,而是接着数组的最后一个元素的内存位置继续向后访问。因此可能访问到其他程序正在占用的内存。所以在C++中使用数组,要严格限制访问的下标在数组范围之内。

  8. 多维数组初始化

    int ia[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    int ia2[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
    int ia3[][4] = {1, 2, 3, 4, 5, 6, 7}; // 自动推断
    
  9. vector详解(相当于Java中的List)

    • 初始化方式:

      // 默认初始化
      vector<int> v1;
      // 拷贝初始化
      vector<char> v2 = {'a', 'b', 'c'};
      // 等号可以省略
      vector<char> v3{'a', 'b', 'c'};
      // 直接初始化,定义一个初始长度为5的容器,默认初始化值为0
      vector<short> v4(5);
      // 直接初始化,定义一个长度为5的容器,默认初始化值为100
      vector<short> v5(5, 100);
      
    • 访问元素:可以通过下标访问。越界访问会报错退出。

    • 添加元素:

      v5.push_bask(69);
      
  10. 除了vector之外,C++ 11还提供了一个array模板类,它跟数组更加类似,长度是固定的,但更加方便,更加安全。所以在实际应用中,一般推荐对于固定长度的数组使用array不固定长度的数组使用vector

  11. 字符串详解

    • 初始化方式

      // 默认初始化
      string s1;
      // 拷贝初始化
      string s2 = s1;
      string s3 = "Hello world";
      // 直接初始化
      string s4("Hello world");
      string s5(5, 'a');
      
    • 访问字符:可以通过下标进行访问

      cout << s4[2] << endl; // 输出l
      s4[0] = 'h'; // 字符串变为 hello world
      cout << s4[s4.size() - 1] << endl;
      
    • 字符串拼接

      string str1("hello"), str2 = "world";
      string str3 = str1 + str2;
      
      • 两个字符串对象可以相加,一个字符串对象和一个字符串常量可以相加,两个字符串常量不可以相加。
      • 原因是C++中的标准模板类string为了和c语言中的字符串进行兼容。因此在c++/c中,字符串常量在底层的存储形式是char数组。因此+运算符重载的条件就是必须基于一个string对象。在多个string对象和多个字符串常量同时做相加时,需要满足左结合律,即+左边必须是一个string对象。
    • 字符串比较

      • 在C++中,在不使用指针的情况下,即使是像string str2 = str1;这样的语句,str1 与 str2都是不同的对象,在内存中的地址也是不一样的。
      • 在C++中,== 、 <= 、>=号都可以进行字符串比较,但比较的字符串的内容,而不是字符串地址。
    • 字符数组(c语言风格字符串)

      • 在c语言中,并没有字符串类型。字符串都是以char[]的形式保存的。

      • 在c语言中,可以通过如下形式定义字符串

        char str1[] = {'a', 'b', 'c', 'd', 'e', 'f', '\n'};
        char str2[] = "abcdef";
        
      • 一个字符串的结束标识是\0,在进行打印的时候,printf和cout总是会在遇到\0时结束打印。使用char str2[] = "abcdef";方式定义字符串是,会默认在末尾增加一个隐藏的\0。

      • C++为了兼容c语言,字符串常量都是以char[]形式进行存储的。

      • 一般推荐直接使用string,尽量少使用字符数组来表示字符串。

  12. 命令行输入输出

    • cin >> str:忽略开头的所有的空白符,从第一个非空白符开始读取,直到遇到下一个空白符。即cin每次读取的是一个单词。下一个空白符之后的内容还会被保存在输入缓存中。
    • getline(istring input,string str):从输入流input中读取一行输入保存到str中,直到遇到换行符,并丢弃该换行符。
    • cin.get():读取输入缓存中的一个字符。还有一个重载函数,cin.get(char[] str, int length):读取length长度的输入,保存在字符数组str中,用的比较少。
  13. 文件输入输出

    • ifstream:用法基本和cin一致,只是操作对象从命令行变更为文件
      • >>:从文件中读取一个单词
      • getline(ifstream, str):读取一行
      • get():读取一个字符
  14. 结构体的 定义 必须在 使用 之前。

  15. 枚举:

    • 枚举类型内部只有有限个名字,他们各自代表了一个常量,被称为枚举量

    • 默认情况下,会将整数值赋值给枚举量,默认从0开始,每个枚举量依次加1

      enum week{
          // 分别对应着 0 ~ 6的常量
          Mon, Tue, Wed, Thu, Fri, Sat, Sun
      }
      
    • 可以通过对枚举量赋值,显式的设置每个枚举量的值。

    • 如果直接用一个整型值对枚举类型赋值,将会报错,因为类型不匹配;

    • 可以通过强制类型转换,讲一个整型值赋给枚举对象;

    • 最初的枚举类型只有列出的值时有效的;而C++通过强制类型转换,允许扩大枚举类型合法值的范围。不过一般使用枚举类要避免直接强转赋值。

  16. 指针

    • 概念:指针顾名思义,就是"指向"另外一种数据类型的复合类型。指针是C/C++中一种特殊的类型,他所保存的信息,其实是另外一个数据对象在内存中的’'地址"。通过指针可以访问到指向的那个数据对象,所以这是一种间接访问对象的方法。指针类型的长度是固定的,与操作系统寻址空间有关。

    • 所谓64位系统和32位系统,说的是内存寻址空间的长度是64位bit和32位bit。在64位系统中,内存寻址空间从0 ~ (2^64 - 1)。因此指针中可以保存的最大的地址是2^64 - 1,共需要8个字节来存储地址信息。而在32位系统中,内存寻址空间从0 ~ (2^32 - 1),因此只需要4个字节来存储地址信息。

    • 指针指向的地址,指的是指向对象的首地址。因为数据类型的长度是不同的,因此知道是何种类型的指针和首地址后,解引用操作就可以通过首地址加偏移量获取指向对象的值。

    • 指针类型定义如下:

      int a = 10;
      int* p1 = &a;
      
      long l = 20;
      long* p2 = &l;
      
      // 不可以将p2指针指向 &a,因为类型不匹配
      // 指针类型指的就是int* 、 long* 这些类型,而p1 、p2 是指针类型的变量
      
    • 解引用

      // 可以使用* 对指针类型的变量进行解引用
      cout << p1 << endl; 	// 输出00000059362FF7B4,是变量a的地址
      cout << *p1 << endl; 	// 输出10
      *p1 = 20;
      cout << *p1 << endl; 	// 输出20
      
    • 一些特殊指针

      • 无效指针:没有初始化的指针,这些指针指向的地址是不确定的,因此使用这些指针是非常危险的!一般来说编译器是不会让无效指针通过编译的。无效指针也叫做野指针,不能使用野指针。

      • 空指针:先定义了一个指针,但还不知道它要指向哪个对象,这是可以把它初始化为"空指针"。有三种方法定义空指针:

        // 三种方式本质上都一样
        int* np = nullptr;
        np = NULL;
        np = 0;
        
      • void* 指针:表示该类型的指针可以指向任意类型的数据对象。但无法解引用,因为不知道指向的数据类型的长度。一般void* 类型的指针只用来存放指针的地址,不做解引用操作。

      • 二级指针:指向指针的指针,即保存的是一级指针的地址。对二级指针进行解引用将会得到一级指针的值,一级指针的值本质上也是一个地址,需要在用一次解引用后获得所指向的基本数据类型的值。也就是说,解引用符*操作地址 可以获得对应地址的值。

    • 指向常量的指针和指针常量

      • 指向常量的指针:顾名思义,这个指针是个变量,只是指向的数据类型是个常量。指针本身是可以变的,即可以指向其他的常量。但指向的常量是不可以变的。即解引用后无法更改内容。
      • 指针常量:指针是常量,指向的数据类型是变量。指针本身存放的地址是固定的,这个地址中存放的值是变量,是可变的。
      • 指向常量的指针常量:意思就是说指针本身是常量,指向的数据类型也是常量。究极之不可变。一旦定义,指针本身的保存的地址不能变,改地址中的对象也不能变。
      int a = 100;
      int b = 200
      const int num1 = 10;
      const int num2 = 20;
      // 指向常量的指针
      const int* pc = &num1;
      cout << *pc << endl;	// 输出10
      pc = &num2;
      cout << *pc << endl;	// 输出20
      // *pc = 25;			// 错误,无法改变常量
      // 指针常量
      int* const cp = &a;
      cout << *cp << endl;	// 输出100
      *cp = 150;
      cout << *cp << endl;	// 输出150
      // cp = &b;				// 错误,无法修改常量
      // 指向常量的指针常量
      const int* const cpp = &num1;
      // cpp = &num2;			// 错误,无法修改常量
      // *cpp = 15;			// 错误,无法修改常量
      
    • 指针和数组

      • 数组名本质上就是一个指针,指向数组中第一个元素的首地址。不能对数组进行拷贝赋值,本质上就是因为不能将一个地址赋值值给数组。
      • 可以对数组名进行加运算。会根据数组的类型进行寻址。本质上就是跳过一些数组元素指向后面的数组元素。
    • 指针数组和数组指针

      • 指针数组:一个数组,里面保存的都是指针
      • 数组指针:一个指针,指向一个数组,该数组指针变量的内容是指向的数组的首地址。在解引用的时候会根据这个数组的长度来确定内存地址取值范围,从而生成一个数组对象。数组对象名指向的是数组中第一个元素,因此保存的值是数组中第一个元素的首地址;事实上数组的首地址和数组中第一个元素的首地址是相同的。编译器会根据类型和地址来确定解引用的结果
      int arr[5] = {1, 2, 3, 4, 5};
      // 指针数组
      int* pa[5] = {nullptr, nullptr, nullptr, nullptr, nullptr};
      // 数组指针
      int (*ap) [5];
      
      ap = &arr; // arr是指向arr数组的第一个元素的指针,arr的值是数组中第一个元素的首地址
      cout << ap << endl;
      cout << arr << endl;
      
    • 关于数组名和指针的一些思考关于C++数组名和指针的一些思考

  17. 引用:定义变量的别名,类似于快捷方式。

    • 引用的定义

      int a = 10;
      int b = 25;
      // 一旦定义引用的对象,之后就不可以更改
      int& ref = a;
      
      // int& ref; 报错!引用必须被初始化。
      // int& ref = 10; 报错!变量的引用必须指向对象,而不是字面值。
      
      // ref = b;并不代表引用指向b,而是ref的值被修改为25,即a被修改为25。
      ref = b;
      
    • 引用时内存中变量的别名,本身并不占据内存空间,一旦定义引用,就与指向的变量是共同的内存地址。

    • 引用的引用:套娃,就是别名的别名,本身还是指向同一个对象。

    • 常量引用:顾名思义,就是对常量的引用。但是在初始化时,有时可以不必指向常量。对常量引用的初始化要求非常宽松。

      const int ci = 20;
      int i = 30;
      const int& cref = ci;
      const int& cref2 = i;		// 正确,常量引用可以直接引用变量
      const int& cref3 = 10;		// 正确,常量引用可以直接引用字面量
      
      double d = 3.14;
      const int& cref4 = d;		// 正确,此时d被隐式转化为int类型的值 3,cref4引用了字面量3
      
      // cref2 = 10;				错误!虽然cref2引用变量,但常量引用不能修改值
      
    • 引用可以看做是指针常量解引用的语法糖,但本质上并不是指针常量的解引用,因为指针常量需要占据内存,引用不占据内存。

    • 可以有对指针的引用

      int a = 10;
      int* ptr = &a;
      int*& pref = ptr;
      // 操作pref就是操作ptr
      cout << *pref << endl; 		// 输出10;
      
    • 没有指向引用的指针,因为指针保存的是内存地址,而引用只是别名,并不进行存储,因此没有地址。

  18. C++中的箭头运算符 ->:是解引用可访问成员两个操作的结合;这样就可以很方便的表示"取指针所指向内容的成员"

    Student s1 = Student("阿秋", 25);
    Student* p = &s1;
    
    // 下面两个语句是等价的
    cout << (*p).getName() << endl;
    cout << p -> getName() << endl;
    
  19. C++中的对象赋值:就是把对象的内容复制过去,对象的地址不发生改变。浅谈C++和Java中对象的等号赋值

六 函数

  1. 全局变量和局部变量:每个变量都有其作用域,一个作用域就是一对{},在{}内定义的变量,作用域只在该{}内。定义在{}之内的变量由于就称为局部变量。而定义在所有{}之外的变量,它的作用域是全局可见的,被称为全局变量

  2. 自动对象和静态对象

    • 自动对象:在平常代码中定义的普通局部变量,生命周期为:在程序执行到变量定义语句时创建,在程序运行到当前代码块末尾时销毁。这样的对象被称为自动对象。方法中的形参也是一种自动对象。对于自动对象来说,它的生命周期和作用域是一致的。
    • 静态对象:如果希望延长一个局部变量的生命周期,让他在其作用域之外依然存活,可以在定义局部变量时加上static关键字。这样的对象叫做局部静态对象。注意,局部静态对象依然只有局部的作用域,在作用域之外依然是不可见的。但它的生命周期贯穿了整个程序运行过程,只有在程序结束时才被销毁,这一点与全局变量类似。静态对象如果不在代码中做初始化,基本数据类型会被默认初始化为0值。
  3. 函数的声明

    • 在C++代码的执行过程中,对于变量、对象和函数的使用,是严格按照县声明再使用的方式的。也就是说,你不可以使用一个在这个语句之后才声明的变量、对象或函数。注意,对于全局对象和全局变量而言,如果在声明时没有初始化,则会进行默认初始化;对于函数而言,是先声明再使用,而不是先定义在使用,因此可以先声明后定义
    • 可以在main()函数之前声明需要用到的函数,然后在任意其他位置进行定义。但一般而言,会使用头文件的来声明函数。
    • #pragma once:在.h头文件中,可以进行结构体、类和函数的定义。头文件是可以在多个文件中被引用的。因此为了避免盖头文件中的内容被多次定义,可以在.h文件中声明#pragma once,表示里面定义的内容在全局只会定义一次。同时#pragma once还可以表示在嵌套包含时,.h文件只会被包含一次。
  4. 参数传递:参数传递的本质,其实就是使用实参对形参进行初始化赋值。

    • 传值:将实参的值赋值给形参,实参和形参在内存中是两份实体,并不相互影响。

    • 传引用:将形参定义为引用,实参也是引用。将引用传递给引用,其实就是引用的引用,所指向的对象是相同的。

      • 传引用的好处是:可以对函数外部数据对象进行更改可以避免数据复制

      • trick:在定义引用类型的参数的时候,如果不会更改引用对象本身的内容,那么可以把参数类型定义为常亮引用。这样做的好处是能够扩大传参的范围。因为引用变量是无法用字面值初始化的,常亮引用是可以用字面值初始化的。

      void fun(const string& str1, const string& str2) {
      	cout << str1 << endl;
      	cout << str2 << endl;
      }
      
      void fun2(string& str1, string& str2) {
      	cout << str1 << endl;
      	cout << str2 << endl;
      }
      
      int main() {
      
          // 正确
      	fun("hello", "world");
      
          // 报错,无法用常亮初始化引用变量
      	fun2("hello", "world");
      
      	return 0;
      }
      
    • 传数组:有三种方式传数组,前两种会把传入的数组退化成一个指针。一般建议使用传数组引用的方式。

      void fun1(int* arr) {
      	cout << (sizeof arr) << endl;
      }
      
      void fun2(int arr[5]) {
      	cout << (sizeof arr) << endl;
      }
      
      void fun3(int (& arr) [5]) {
      	cout << (sizeof arr) << endl;
      }
      
      int main() {
      
      	int arr[] = { 1, 2, 3, 4, 5 };
      	cout << sizeof arr << endl;
      
      	fun1(arr); 	// 输出8
          fun1(arr);	// 输出8
          fun1(arr);	// 输出20
      
      	return 0;
      }
      
  5. 返回类型

    • typedef:用于自定义一个类型的别名。在函数返回值比较复杂的时候,可以使用自定义类型别名来简化。
    • 尾置返回类型:在函数声明的时候,可以在函数前使用auto关键字,在函数尾部使用 -> 数据类型 的方式来声明函数。此时函数的返回类型就是尾部声明的类型。

七 函数高阶

  1. 内联函数:函数声明前 使用 inline关键字,表示该函数在调用时,直接拷贝函数体内的代码到调用处,而不做函数调用。省去了保存上下文和函数入栈等操作。在某种程度上可以优化运行效率。但最近研究表明,inline函数的功能已经发生了改变,编译器会自动进行内联优化,如果仅仅是为了优化效率,则不建议使用inline。现代C++里,inline被理解成:它通知编译器,这个符号可能会在多个翻译单元中重复出现。大坑inline函数

  2. 函数参数默认值

    • 在C++中,可以定义函数参数的默认值,直接在函数定义的时候在参数列表后面使用等号为参数设置默认值即可。在调用函数进行传参时,python是可以显式的指定参数名从而为指定形参赋值的。与python不同,C++无法跳过某些默认值进行之后的参数设置,必须严格按照参数顺序进行传参。

    • 在Java中无法设置函数参数默认值,但是可以通过函数重载来达到相同的目的。

  3. 函数重载

    • 和Java一样,C++支持函数重载。而C语言和python并不支持函数重载。在python中,后定义的函数会覆盖先定义的函数。

    • const类型的函数重载

      • 顶层const:const修饰参数本身,与不带const修饰的参数在接收参数的功能上是一致的。无法进行函数重载。
      • 底层const:一般用于指针和引用的修饰,修饰的是参数所指向/引用的数据类型。可以进行重载。
    • 函数匹配

      void fun(int x){
          cout << 1 << endl;
      }
      void fun(int x, int y){
          cout << 2 << endl;
      }
      void fun(double x, double y = 1.5){
          cout << 3 << endl;
      }
      
      int main(){
          // 输出3
          fun(3.14);
          // 发生二义性调用。报错!有多个重载函数fun实例与参数列表匹配
          fun(3.14, 10);
      }
      
      • 参数匹配时,参数数量要相等,参数类型也需要相同或者可以通过隐式类型转换的。
      • 最佳匹配:不需要进行类型转换的优先匹配,或者需要最少次数的隐式类型转换。
      • 多参数匹配:按照参数类型精确匹配优先,或者有某一个可行的函数都不必其他函数差,并且最少有一个参数匹配更优,那么它就是最佳匹配。
      • 二义性调用:在检查所有重载函数之后,发现有多个可行函数不分优劣,无法找到一个最佳匹配,编译器就会直接报错,这中情况被称为二义性调用
    • 在Java中,函数参数匹配时,只可以发生向下的隐式类型转换,例如int类型传递给double类型。无法在丢失精度的情况下发生类型转换。并且int型优先匹配为long,而不是double。

    • 函数重载必须在同一作用域中。如果在不同作用域中声明同名函数,内层的函数会覆盖外层的声明或定义的其他同名不同参数的函数

      void fun(int i){
          cout << i << endl;
      }
      void fun(double i){
          cout << i << endl;
      }
      void fun(string i){
          cout << i << endl;
      }
      
      int main(){
          void fun(int i);
          // 可行,调用fun(int i)
          fun(10);
          // 可行,调用fun(int i),发生参数类型隐式转换
          fun(3.14);
          // 报错!无法调用fun(string i)
          fun("hello");
      }
      
  4. 函数指针和函数引用

    函数其实也是有类型的,函数的类型由其返回值和参数类型共同决定,与函数名和参数名无关。

    void testDefault(double i) {
        cout << 1 << endl;
    }
    
    void testDefault(long i) {
        cout << 2 << endl;
    }
    
    int main() {
        // 定义函数指针
        void (*fp) (long) = nullptr;
        // 以下两种赋值语句等价
        fp = testDefault;
        fp = &testDefault;
        
        // 以下两种调用方式等价,输出3
        (*fp)(3);
        fp(3);
        
        // 定义函数引用  
        void (&fr) (long) = testDefault;
        // 也可以调用函数,输出3
        fr(3);				
    }
    
  5. 函数指针和函数引用作为函数的参数。感觉比较离谱

    • 作用基本相同,唯一不同的是函数指针的引用作为参数时,无法使用函数名作为参数传入,此时需要定义为const

    • python中也有函数作为参数的概念,在Java中可以使用接口和匿名内部类的方式来实现。不过C++中的语法还是太离谱了,很多种方式都可以。

    • typedef decltype(funName) newName:提取funName函数的类型,定义函数类型为新的名称newName

    • 疯了!下面的代码除了注释的部分,其他都没问题,请自行理解

    void testDefault(long i) {
        cout << i << endl;
    }
    // 以下几种定义方式相同,可以省略指针符号*
    void fun1(long i, void (*fp) (long)) {
        // 以下两种调用方式相同,可以省略解引用符号*
        (*fp)(i);
        fp(i);
    }
    
    void fun2(long i, void fp(long)) {
        // 以下两种调用方式相同,可以省略解引用符号*
        (*fp)(i);
        fp(i);
    }
    
    // 使用类型别名,以下方式相同,函数名本身就被编译器解析为指针
    
    typedef void (*Funcp)(long);
    typedef void Func(long);
    typedef decltype(testDefault) Func;
    
    
    void fun3(long i, Funcp fp) {
        // 以下两种调用方式相同,可以省略解引用符号*
        (*fp)(i);
        fp(i);
    }
    
    void fun4(long i, Funcp& fp) {
        // 以下两种调用方式相同,可以省略解引用符号*
        (*fp)(i);
        fp(i);
    }
    
    void fun5(long i, const Funcp& fp) {
        // 以下两种调用方式相同,可以省略解引用符号*
        (*fp)(i);
        fp(i);
    }
    
    void fun6(long i, Func fp) {
        // 以下两种调用方式相同,可以省略解引用符号*
        (*fp)(i);
        fp(i);
    }
    
    void fun7(long i, Func& fp) {
        // 以下两种调用方式相同,可以省略解引用符号*
        (*fp)(i);
        fp(i);
    }
    
    void fun8(long i, Func* fp) {
        // 以下两种调用方式相同,可以省略解引用符号*
        (*fp)(i);
        fp(i);
    }
    
    
    Func& fun9() {
        return testDefault;
        // return &testDefault;     报错
    }
    
    Func* fun10() {
    
        return testDefault;
        return &testDefault;
    }
    
    Funcp fun11() {
    
        return testDefault;
        return &testDefault;
    }
    
    Funcp& fun12() {
    
        Funcp f = testDefault;
        return f;
    }
    
    int main() {
        // 以下两种调用方式相同,可以省略去地址符号&
        fun1(10, testDefault);
        fun1(10, &testDefault);
    
        fun2(10, testDefault);
        fun2(10, &testDefault);
    
        fun3(10, testDefault);
        fun3(10, &testDefault);
    
        Funcp f = testDefault;
        Funcp& ref = f;
    
        fun4(10, f);         
        fun4(10, ref);
        //fun4(10, testDefault);    报错,无法使用字面量初始化一个引用
        //fun4(10, &testDefault);    报错,无法使用字面量初始化一个引用
    
        fun5(10, &testDefault);
        fun5(10, testDefault);
    
        fun6(10, &testDefault);
        fun6(10, testDefault);
    
        fun7(10, testDefault);
    
        fun8(10, &testDefault);
        fun8(10, testDefault);
    
        fun1(10, fun9());
        fun1(10, fun10());
        fun1(10, fun11());
        fun1(10, fun12());
    
        fun2(10, fun9());
        fun3(10, fun10());
        // fun4(10, fun11());   报错,字面量无法赋值引用变量
        fun5(10, fun12());
    
        fun4(10, fun12());
    }
    
    • 函数指针作为返回类型:直接使用函数名是不可以的,必须使用函数指针或函数引用才可以。
    • 综上所示,函数名作和数组一样。在直接使用的时候可以当做一个指针,但本身并不是指针。并且在作为函数参数传入时,会退化为一个普通指针。
    • 疯了疯了…

八 面向对象

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

学习一年Java的程序员的C++学习记录(指针引用绕晕记) 的相关文章

  • 编译时运算符

    有人可以列出 C 中可用的所有编译时运算符吗 C 中有两个运算符 无论操作数如何 它们的结果始终可以在编译时确定 它们是sizeof 1 and 2 当然 其他运算符的许多特殊用途可以在编译时解决 例如标准中列出的那些整数常量表达式 1 与
  • 通过 CMIS (dotCMIS) 连接到 SP2010:异常未经授权

    我正在使用 dotCMIS 并且想要简单连接到我的 SP2010 服务器 我尝试用 C 来做到这一点 如下所示http chemistry apache org dotnet getting started with dotcmis htm
  • 动态加载程序集的应用程序配置

    我正在尝试将模块动态加载到我的应用程序中 但我想为每个模块指定单独的 app config 文件 假设我的主应用程序有以下 app config 设置
  • 按成员序列化

    我已经实现了template
  • 在结构中使用 typedef 枚举并避免类型混合警告

    我正在使用 C99 我的编译器是 IAR Embedded workbench 但我认为这个问题对于其他一些编译器也有效 我有一个 typedef 枚举 其中包含一些项目 并且我向该新类型的结构添加了一个元素 typedef enum fo
  • 秒表有最长运行时间吗?

    多久可以Stopwatch在 NET 中运行 如果达到该限制 它会回绕到负数还是从 0 重新开始 Stopwatch Elapsed返回一个TimeSpan From MSDN https learn microsoft com en us
  • 用于检查类是否具有运算符/成员的 C++ 类型特征[重复]

    这个问题在这里已经有答案了 可能的重复 是否可以编写一个 C 模板来检查函数是否存在 https stackoverflow com questions 257288 is it possible to write a c template
  • 查找c中结构元素的偏移量

    struct a struct b int i float j x struct c int k float l y z 谁能解释一下如何找到偏移量int k这样我们就可以找到地址int i Use offsetof 找到从开始处的偏移量z
  • 从Web API同步调用外部api

    我需要从我的 Web API 2 控制器调用外部 api 类似于此处的要求 使用 HttpClient 从 Web API 操作调用外部 HTTP 服务 https stackoverflow com questions 13222998
  • Clang 3.1 + libc++ 编译错误

    我已经构建并安装了 在前缀下 alt LLVM Clang trunk 2012 年 4 月 23 日 在 Ubuntu 12 04 上成功使用 GCC 4 6 然后使用此 Clang 构建的 libc 当我想使用它时我必须同时提供 lc
  • 如何从 appsettings.json 文件中的对象数组读取值

    我的 appsettings json 文件 StudentBirthdays Anne 01 11 2000 Peter 29 07 2001 Jane 15 10 2001 John Not Mentioned 我有一个单独的配置类 p
  • 将 VSIX 功能添加到 C# 类库

    我有一个现有的单文件生成器 位于 C 类库中 如何将 VSIX 项目级功能添加到此项目 最终目标是编译我的类库项目并获得 VSIX 我实际上是在回答我自己的问题 这与Visual Studio 2017 中的单文件生成器更改 https s
  • C# 中通过 Process.Kill() 终止的进程的退出代码

    如果在我的 C 应用程序中 我正在创建一个可以正常终止或开始行为异常的子进程 在这种情况下 我通过调用 Process Kill 来终止它 但是 我想知道该进程是否已退出通常情况下 我知道我可以获得终止进程的错误代码 但是正常的退出代码是什
  • 使用 WebClient 时出现 System.Net.WebException:无法创建 SSL/TLS 安全通道

    当我执行以下代码时 System Net ServicePointManager ServerCertificateValidationCallback sender certificate chain errors gt return t
  • WCF 中 SOAP 消息的数字签名

    我在 4 0 中有一个 WCF 服务 我需要向 SOAP 响应添加数字签名 我不太确定实际上应该如何完成 我相信响应应该类似于下面的链接中显示的内容 https spaces internet2 edu display ISWG Signe
  • 如何设计以 char* 指针作为类成员变量的类?

    首先我想介绍一下我的情况 我写了一些类 将 char 指针作为私有类成员 而且这个项目有 GUI 所以当单击按钮时 某些函数可能会执行多次 这些类是设计的单班在项目中 但是其中的某些函数可以执行多次 然后我发现我的项目存在内存泄漏 所以我想
  • 如何查看网络连接状态是否发生变化?

    我正在编写一个应用程序 用于检查计算机是否连接到某个特定网络 并为我们的用户带来一些魔力 该应用程序将在后台运行并执行检查是否用户请求 托盘中的菜单 我还希望应用程序能够自动检查用户是否从有线更改为无线 或者断开连接并连接到新网络 并执行魔
  • 测试用例执行完成后,无论是否通过,如何将测试用例结果保存在变量中?

    我正在使用 NUNIT 在 Visual Studio 中使用 Selenium WebDriver 测试用例的代码是 我想在执行测试用例后立即在变量中记录测试用例通过或失败的情况 我怎样才能实现这一点 NUnit 假设您使用 NUnit
  • C++ 中类级 new 删除运算符的线程安全

    我在我的一门课程中重新实现了新 删除运算符 现在我正在使我的代码成为多线程 并想了解这些运算符是否也需要线程安全 我在某处读到 Visual Studio 中默认的 new delete 运算符是线程安全的 但这对于我的类的自定义 new
  • 如何防止用户控件表单在 C# 中处理键盘输入(箭头键)

    我的用户控件包含其他可以选择的控件 我想实现使用箭头键导航子控件的方法 问题是家长控制拦截箭头键并使用它来滚动其视图什么是我想避免的事情 我想自己解决控制内容的导航问题 我如何控制由箭头键引起的标准行为 提前致谢 MTH 这通常是通过重写

随机推荐

  • 华为30道Python面试题总结

    Python是目前编程领域最受欢迎的语言 在本文中 我将总结华为 阿里巴巴等互联网公司Python面试中最常见的30个问题 每道题都提供参考答案 希望能够帮助你在求职面试中脱颖而出 找到一份高薪工作 这些面试题涉及Python基础知识 Py
  • hutool json转map_记一个Jackson与Hutool混用的坑

    技术公众号 Java In Mind Java In Mind 欢迎关注 问题出现 最近遇到一个问题 Hutool从4 1 7升级到4 6 8之后 使用feign调用出现错误 Caused by feign codec EncodeExce
  • CXF java.lang.RuntimeException: Cannot create a secure XMLInputFactory

    刚开始接触cxf 照着网上的例子写了一个demo 在测试 编写客户端访问服务运行的时候后台报了 CXF java lang RuntimeException Cannot create a secure XMLInputFactory 的错
  • Android gradle配置抽取合并

    一 为什么要合并 当项目中model或library变多过后 比如用到组件化或者引入第三方库需要配置多个build gradle文件 一旦需要统一其SDK或者其他组件版本就需要同时修改多个文件 这确实很麻烦 所以抽取gradle配置非常有必
  • JAVA单元测试框架-9-testng.xml管理依赖

    在testng xml里配置依赖管理 先写个测试用例 Test description 测试分组 groups operation public void TestGroupAdd System out print String value
  • 对七牛云的简单了解

    一 初识七牛云 1 概述 七牛云是国内领先的企业级公有云服务商 致力于打造以数据为核心的场景化PaaS服务 围绕富媒体场景 七牛先后推出了对象存储 融合CDN加速 数据通用处理 内容反垃圾服务 以及直播云服务等 通俗来讲七牛云就是一个服务器
  • UE4基础学习笔记——— 材质编辑器04

    材质实例 原理 不用在原父级材质编辑器中去调节材质 我们把重要的调节值设置为 转换为参数 将材质实例化 要修改只要修改参数即可 选择父级材质右键 创建材质实例 注意标识颜色是 深绿 在实例编辑界面中 出现了之前设置的可变参数 材质实例化方便
  • 《Java Web程序设计——开发环境的搭建》

    Java Web程序设计 开发环境的搭建 一 前言 这里主要分享一下我搭建开发环境的过程以及遇到的问题 其中涉及到的软件都可以从官网获取 若官方访问过慢也可到镜像网站或者下面分享的网盘链接中下载 软件安装路径尽量不要有中文 否则可能会报错
  • 试题 算法训练 拿金币-蓝桥杯

    这里的关键字仍然是动态规划 动态规划核心 拆分子 记住过往 减少重复计算 计算结果 1 不难发现 对于某个确定的路径上的特定位置上的金币总数 总是由该位置的上方的值或左边的值确定的 所以遍历数组位置的上方和左边的 再 比较 递加 就能计算出
  • K8S之资源管理

    文章目录 一 K8S中的资源 二 YAML语言 三 资源管理方式 一 命令式对象管理 二 命令式对象配置 三 声明式对象配置 一 K8S中的资源 在kuberbnetes中 所有的内容都抽象为资源 用户需要通过操作资源来管理kubernet
  • 可视化埋点方案和实践-PC-WEB端(一)

    目录 一 什么是可视化埋点 1 圈选 点选 即标记页面元素 的逻辑代码 2 捕获监听标记的元素的逻辑代码 二 遇到的坑 1 标记元素兼容性难 2 监听难 三 优点 1 方便了测试人员和运营人员 2 埋点的变更是即时的 不需要更新系统代码 3
  • 【Graph Neural Network】 GraphSAGE 基本原理与tensorflow2.0实现

    文章目录 GraphSAGE 前向传播算法 采样算法 聚合 aggragator 操作 参数学习 基于tensorflow2 0实现Graph SAGE GCN是一种利用图结构和邻居顶点属性信息学习顶点Embedding表示的方法 GCN是
  • 如何有效预防脱库

    本篇不从DBA 网络架构层面来讲述数据安全 这部分有很专业的架构和云上产品来解决 本篇重点从开发人员角度讲述如何避免数据安全的漏洞 相信大部分人都看到过这样的新闻 某某论坛泄漏了用户密码 某某物流公司泄漏了用户的手机号等等 我一直坚信大部分
  • 用Unity3D和VuforiaSDK简单做AR应用(入门)

    最近刚开始接触AR技术 结合u3d 算是对增强现实应用入个门 网上的例子不胜枚举 但有些浅尝辄止 根据自己几天来的摸索 毕竟新的技术源自国外 翻起晦涩的外文 一步一个脚印终于爬了出来 先上个史记效果图先 我取名之 鹿君下山 接下来说说步骤
  • Linux分区记录

    命令 cat proc mtd dev size erasesize name mtd0 00007000 00010000 vendor mtd1 00030000 00010000 IDBlock mtd2 00600000 00010
  • 3.5设计模式——————接口隔离原则——面向对象设计原则

    接口隔离原则的定义 接口隔离原则 Interface Segregation Principle ISP 要求程序员尽量将臃肿庞大的接口拆分成更小的和更具体的接口 让接口中只包含客户感兴趣的方法 2002 年罗伯特 C 马丁给 接口隔离原则
  • php新闻管理系统(简单)学习教程

    最近因为工作原因需要使用php开发网页 所以开始学习php 在学习的过程中也遇到了很多困难 经过不断的查询百度各种学习资料 逐步的客服了这些困难和疑惑 现在我将学习过程中编写的代码分享给有需要的朋友 仅供参考 此系统比较简单 共有20个ph
  • 某宝登录滑块拖动没反应解决,亲测有效

    这两天在抓取某宝数据的时候发现使用selenium登录时会有滑块 然后尝试使用xpath定位到滑块位置然后使用Actionchains拖动 但是发现滑块拖动没有反应 但是在抓取过程中的滑块拖动时没有问题的 如图所示 随后对代码进行调试 终于
  • 微信小程序开店怎么做?

    在日活量如此之高的微信里 很多商家都希望能再微信开一个小程序商店 来提高自己的一个卖货收益 那么微信小程序开店怎么做呢 下面跟大家分享一下微信小程序怎么开店 一 开通小程序账号 首先我们需要开通一个小程序账号 小程序账号的主体类型要企业或者
  • 学习一年Java的程序员的C++学习记录(指针引用绕晕记)

    文章目录 一 C 入门 二 变量和数据类型 三 运算符 四 流程控制 五 复合数据类型 六 函数 七 函数高阶 八 面向对象 一 C 入门 标准输出流中 cout 是一个ostream对象 lt lt 和 gt gt 是C 中经过重载的运算