【C++】1、C++基础语法

一、什么是 C++

他是一种静态数据类型检查、支持多重编程规范的通用程序设计语言。它支持过程化程序设计、数据抽象、面向对象程序设计、泛型程序设计等风格。

C++是 C 语言的扩张,兼容 C 语言中所有的语法和接口的同时,进一步扩充和完善了 C 语言,其对 C 的增强,主要体现在 6 个方面:

  1. 类型检查更严格
  2. 增强面型对象的机制
  3. 增强了泛型编程的机制(template)
  4. 增加了异常处理 try catch
  5. 增强了函数重载和运算符重载
  6. 增加了标准模板库(STL)

二、C 语言和 C++的具体使用

1、文件的后缀

C 语言C++
源文件.c.cpp&#8195&#8195 .cxx &#8195&#8195.cc &#8195&#8195.tlh
头文件.h.h &#8195&#8195.hpp &#8195&#8195.hxx &#8195&#8195.hh &#8195&#8195.inl
编译方式gcc&#8195&#8195 arm-linux-gccg++&#8195&#8195 arm-linux-g++
输入输出printf/scanfcin/cout

2、头文件

C++是完全兼容 C 的,对于 C 中的头文件,自然可以拿来使用,只不过需要做出些修改。


方式一:使用 C 库中的文件

导入的时候,将 C 语言中头文件中的 .h 去掉,在头文件之前加上 c 即可。此时引用头文件的路径是:/usr/include/c++/5.4.0

eg:

C:#include <stdio.h>
C++:#inlcude <cstdio>

#include <cstdio>

int main()
{
    printf("hello\n");
    
    return 0;
}


方式二:自定义库的头文件或者 c 库

在需要导入 C 语言的头文件的时候,使用 extern "C" 声明头文件,具体形式如下:

此时,我们引用头文件的路径是:/usr/include。(但我不知道他用到的是 include 中的哪一个,这里的路径是看的吴修贤讲师的笔记)

extern "C"
{
    #include <stdio.h>
}

int main()
{
    printf("world!\n");
    
    return 0;
}

三、C++的输入输出方式

1、输入输出的头文件

/usr/include/c++/5/iostream

  extern istream cin;        // Linked to standard input  标准输入
  extern ostream cout;        // Linked to standard output  标准输出
  extern ostream cerr;        // Linked to standard error (unbuffered)  标准错误(无缓冲区)
  extern ostream clog;        // Linked to standard error (buffered)  标准错误(有缓冲区)

此处的输入输出不是函数,只是操作标识符。

输入输出的操作需借助重定向符号(>> /<<)使用,以表示数据的流向。需注意的是,此过程会自动检测当前操作数据的数据类型。

关于自动检测当前操作数据类型的这一点,下面可以看看我的这个例程中输出 a 跟 b 的方式,分别是用的 c 跟 c++的方式来的,前者需要我用格式控制符来表明打印出的格式,但是后者却是自己进行的判断。

#include <cstdio>
#include <iostream>

int main(int argc, char const *argv[])
{
    int a = 1, b = 2;

    printf("hello===%d---------", a);

    std :: cout << b << "===world!\n";

    return 0;
}

2、cin 方式

写法:cin >> 变量

cin 输入的时候,获取缓冲区中的内容存放到变量之中,不需要在变量之前加上取地址符号&

cin 支持连续输入操作:cin >> a >> b >> c;

3、cout 方式

写法:cout << 变量

和 cin 一样,cout 也支持连续输出操作:cin << a << b << c;

输出的时候还是需要考虑换行的,这里的换行方式有两种,一种是和 C 里面一样,输出字符串的时候加上 \n 至于另一种,则是在后面加上 :: endl

还需要注意的是,cin 和 cout 所属于 std 命名空间,在没有指明空间路径的时候,需要在此之前加上 std:: ,其中 :: 叫空间所属符号

#include <cstdio>
#include <iostream>

int main()
{
    // 打印字符串 "hello world!"
    printf("hello world!\n");
    
    // 两种换行
    std :: cout << "hello \n";
    std :: cout << "world!" << std :: endl;
    
    // 连续输出
    std :: cout << "陈" << "好" << "人\n" ;
    
    // 连续输入输出 int 型数字
    int a, b;
    std :: cin >> a >> b;
    std :: cout << a <<"---"<< b << std :: endl;
    
    // 5 的阶乘
    int i=5, num=1;
    for(i ; i>=1; i--)
    {
        num *= i;    
    }
    std :: cout << "5 的阶乘是: ";
    std :: cout << num << std :: endl;
    
    return 0;
    
}

4、关于 C++流状态的检测函数

检测函数对应标志位说明
good()goodbit操作成功,没有发现错误
eof()eofbit到达输入末尾或者未见末尾
fail()failbit发生某些意外。例如需要数字内容,但是读取到的是字符
bad()badbit发生眼中的意外。例如磁盘读写故障

在 C++中,类中的函数需要使用操作运算符 . 来调用,例如:

#include <cstdio>
#include <iostream>

int main()
{
    int i = 0;
    
    std :: cin >> i;
    
    // 判断输入成功
    if(std :: cin.good())
    {
        std :: cout << "输入成功" << std :: endl;
    }
    else
    {
        std :: cout << "输入失败" << std :: endl;
    }
    
    return 0;
    
}

#include <cstdio>
#include <iostream>

int main()
{
    int i = 0;
    
    std :: cin >> i;
    
    // 判断输入成功
    if(std :: cin.good())
    {
        std :: cout << "输入成功" << std :: endl;
    }
    
    // 判断输入失败
    if(std :: cin.fail())
    {
        std :: cout << "输入失败" << std :: endl;
    }
    
    return 0;
    
}

四、命名空间

1、什么是命名空间

命名空间是对作用域的一种特殊抽象,他其实包含了处于该作用域内的标识符,且它本身也用于一个标识符识别来表示,

不同的数据可以存储在不同的命名空间,不同的命名空间可以同时存在,但是彼此独立,互不干扰,所以,我们常常利用命名空间的这个特性,来避免标识符的冲突,以通过空间确认自己的操作数据

举例说明:
std :: cout << "陈" << "好" << "人\n" ;

  • std 代表所在命名空间的名称
  • :: 作用域所属符
  • cout 作用域中存在的标识

2、命名空间的操作

关键字:namespace

格式:

namespace 空间名
{
    标识(变量、函数、类);
}

在命名空间中,数据称为成员变量,函数称为成员函数。

如果需要获取命名空间中的某个标识,则需要使用作用域所属符 :: ,获取方式是:命名空间名 :: 标识,比如:Student :: i


首先看一下当两个工程进行整合后,项目中同时出现两个全局变量 i 的情况:

#include <cstdio>
#include <iostream>

int i = 10; // 全局变量

int i = 16; // 另一个工程中的全局变量i

int main()
{
    int i = 15; // 局部变量

    return 0;
    
}


可以预期,也可以看到,这样的写法是会报错的,但是整合后再去修改变量名也不太现实,所以我们用命名空间来解决:

#include <cstdio>
#include <iostream>

// 定义空间1
namespace Student
{
    int i = 10; // 全局变量
};

namespace Teacher
{
    int i = 16; // 另一个工程中的全局变量i
};

int main()
{
    int i = 15; // 局部变量
    
    std :: cout << i << std :: endl;
    std :: cout << Student::i << std :: endl;
    std :: cout << Teacher::i << std :: endl;

    return 0;
    
}

3、命名空间的特殊用法

(1)、结合 using

使用方法:using namespace 命名空间

当您使用 using namespace 声明一个命名空间后,后续在代码中使用该命名空间中的变量或函数时,不再需要使用::运算符来显式注明命名空间。

#include <cstdio>
#include <iostream>

using namespace std;

// 定义空间1
namespace Student
{
    int i = 10; // 全局变量
};

namespace Teacher
{
    int i = 16; // 另一个工程中的全局变量i
};

int i = 3;

int main()
{
    int i = 15; // 局部变量
    
    std :: cout << i << std :: endl;
    std :: cout << Student::i << std :: endl;
    std :: cout << Teacher::i << std :: endl;
    
    cout << i << endl;
    
    return 0;
    
}


通过个人实践,当出现重复使用 using 声明的时候,会出现一些意外情况,后来在 CSDN 上找到了解释,挺好。

首先,看看这个程序:

#include <cstdio>
#include <iostream>

using namespace std;

// 定义空间1
namespace Student
{
    int i = 10; // 全局变量i
};

using namespace Student;

namespace Teacher
{
    int i = 16; // 另 一个工程中的全局变量i
};

int i = 3;

int main()
{
    std :: cout << i << std :: endl;
    std :: cout << Student::i << std :: endl;
    std :: cout << Teacher::i << std :: endl;
    
    cout << i << endl;
    
    return 0;
    
}

按照我之前阅读过的一篇博客对此的解释:usings 声明可以将某一个命名空间或命名空间中的内容释放到这个作用域中,使这个作用域内调用这个命名空间的内容不需要“ :: ”来访问。

也就是说,多次声明不是不可以,只是不能对变量 i 进行重载,在这个基础上,我对我的程序进行一些修改:

#include <cstdio>
#include <iostream>

using namespace std;

// 定义空间1
namespace Student
{
    int j = 10; // 全局变量i
};

using namespace Student;

namespace Teacher
{
    int i = 16; // 另 一个工程中的全局变量i
};

int i = 3;

int main()
{    
    std :: cout << i << std :: endl;
    std :: cout << Student::j << std :: endl;
    std :: cout << Teacher::i << std :: endl;
    
    cout << i << endl;
    cout << j << endl;
    
    return 0;
    
}

可以看到上面的结论得到了证实。


但我还发现了一个好玩的地方,就还是针对我最开始报错的那个程序,只要我加入了一个局部变量 i,就不报错:

#include <cstdio>
#include <iostream>

using namespace std;

// 定义空间1
namespace Student
{
    int i = 10; // 全局变量i
};

using namespace Student;

namespace Teacher
{
    int i = 16; // 另 一个工程中的全局变量i
};

int i = 3;

int main()
{
    int i = 5;
    
    std :: cout << i << std :: endl;
    std :: cout << Student::i << std :: endl;
    std :: cout << Teacher::i << std :: endl;
    
    cout << i << endl;
    
    return 0;
    
}

这里我也是去看了下别人的博客(跟刚刚提到的一篇都被 404 了),看了后发现 using 是分编译指令和编译声明的,我们之前用的 using namespace std; 就是属于编译声明。

引用博文当中的一句话:

如果引入的变量与文件的局部变量有重复,using 声明指令会报错,而 using 编译指令会用局部变量覆盖掉名称空间的变量。因此 using 声明指令安全性更高于 using 编译指令

如此一来,就很好理解了,这里还稍微看看 using 编译指令长啥样:using std:: cout;

(2)、相互嵌套

在使用过程中,有时需要对命名空间进行相互嵌套,那么,在读取的时候就需要逐层读取。

#include <cstdio>
#include <iostream>

using namespace std;

namespace Data
{
    namespace Data
    {
        int data = 1;
    };
};

int main()
{
    cout << Data::Data:: data << std :: endl;
    
    return 0;
    
}

(3)、匿名空间

顾名思义,匿名空间没有命名空间的名字,所以也无法在其他的文件内通过 extern 声明该变量,于是该变量只能在本文件中可见,由于这个特性,匿名空间通常用于跨文件的编辑。

C 语言中的类似操作是在函数之前加上 static 修饰,表示只能在当前文件中使用。而在 C++中 static 不能直接修饰类,所以在 C++的新标准中,如果需要限制文件的使用访问,需要使用匿名空间声明数据。

注意,调用全局的其他的数据,直接在函数或者变量名之前加上 ::

#include <cstdio>
#include <iostream>

using namespace std;

int num = 10;

// 匿名空间
namespace
{
    int num = 12;
}

int main()
{
    cout << num << endl; // 全局变量
    
    return 0;
}

我们可以看到,匿名空间并不像之前用的命名空间那样,可以出现与全局变量的变量名相同的变量。

但是也跟之前使用 using 的情况有些类似,可以让局部变量安排上,如此还是优先打印这个局部变量的数值的情况。


匿名空间和 static 的异同:

相同:

  • 作用域限定:都是限定文件中的数据使用范围为当前文件;
  • 避免命名冲突:使用匿名空间或静态变量可以避免变量名称的冲突,确保变量的唯一性。

不同:

  • 可见性范围:匿名空间的可见性仅限于定义它的源文件,而静态变量的可见性可以是函数、类或文件级别;
  • 声明方式:匿名空间使用 namespace 关键字进行定义,而静态变量使用 static 关键字进行声明;
  • 存储方式:匿名空间中的变量在内存中的存储方式和普通变量相同,而静态变量具有静态存储期,他们在程序运行期间一直存在,不会在函数退出时销毁
  • 初始化:匿名空间中的变量可以具有动态初始化,而静态变量具有静态初始化,可以在声明时或在第一次使用时初始化
  • 共享性质:匿名空间中的变量不会被其他源文件所见,每个源文件都有自己独立的匿名空间;而静态变量可以在多个函数之间共享,他们具有文件作用域

总结起来,匿名空间和静态变量都用于限定变量的作用域和可见性,以避免命名冲突。匿名空间适用于在源文件级别定义变量,而静态变量可以在函数、类或文件级别定义。匿名空间的变量具有动态初始化,作用域仅限于定义它的源文件,不共享给其他源文件。而静态变量具有静态初始化,作用域可以跨越多个函数,类或文件,并具有文件作用域的共享性质。


这里解析一下匿名空间与 static 在可见性范围上的不同:

匿名空间的可见性仅限于定义它的源文件,意味着匿名空间中定义的变量和函数只在当前源文件中可见,不能被其他源文件所访问。

相比之下,静态变量的可见性可以更加灵活。静态变量的可见性可以是函数、类或文件级别。

  • 函数级别:在函数内部声明的静态变量仅在该函数内可见,其他函数无法访问。
  • 类级别:在类中声明的静态变量可以被类的所有对象共享,可以通过类名和作用域解析运算符来访问。
  • 文件级别:在函数外部、类外部但在文件内部声明的静态变量可以在整个文件中使用,包括其他函数或类。

这里在理解上可能还存在一些问题,就是我们之前可能会出于一些固有的错误印象,觉得被 static 修饰的局部变量就可以直接当成全局函数来用了,但这样理解显然不太准确。

被 static 修饰的局部变量并不是全局变量,它仍然是局部变量,只是具有静态存储期和块作用域。它的可见性仍然限于声明它的作用域(通常是函数内部)。

static 修饰局部变量的主要特点是:

  • 静态存储期:被 static 修饰的局部变量在程序执行期间一直存在,而不是在每次进入和退出函数时创建和销毁。它的值在函数调用之间保持持久,下一次进入函数时保留上一次的值。
  • 块作用域:被 static 修饰的局部变量仅在声明它的块(通常是函数内部)中可见。它的作用域仅限于声明它的块,不会在函数外部可见。其他函数无法直接访问该变量。

五、堆空间操作符

在 C++中申请和销毁堆空间的操作是 new,delete

new 用于分配堆内存空间,相当于 C 语言中的 malloc 函数+构造函数 (构造函数理解为初始化数据)
delete 用于销毁堆内存空间,相当于 C 语言中的 free 函数+析构函数 (析构函数理解为回收资源)

1、分配单独的内存空间

格式:

  • 分配堆空间:数据类型 *变量对象 = new 数据类型(数据值); // 申请一个对象空间,可以赋值也可以不赋值
  • 销毁堆空间:delete 变量对象; // 销毁对象空间

分配的时候那个数据值也可以不写,这样只是开辟了堆空间并定义了一个对应的指针,而并没有装东西进去,指针也没有指向,所以在下面的代码中要继续操作,来装东西进去

#include <cstdio>
#include <iostream>

using namespace std;

int main()
{
    // 定义
    char *ctr = new char('A');
    char *ccc = new char();
    int *ptr = new int(10);

    ccc = "ABC";
    
    // 打印数据
    cout << *ctr << endl;
    cout << *ccc << endl;
    cout << *ptr << endl << endl;

    // 打印地址
    cout << ctr << endl;
    cout << ccc << endl; // 从运行结果我暂时认为,对于单个字符,起码在此程序这种应用场景下,C++是把它当成字符串处理的
    cout << ptr << endl;

    delete ctr;
    delete ccc;
    delete ptr;

    return 0;
    
}

但是这里也同样发现了一个 bug,就还是上面的方法表示字符和字符串,但是却是进一步去探究二者的地址,我们通过上面的代码已经输出了字符 A 和字符串 ABC,用到了一级指针,所以进一步对其取地址就会得到该一级指针的地址,也就是说我们要实现一个二级指针,在 C 语言中我们常用的方法是直接用&来取地址,这种方法在 C++中也是允许的,但老师上课的时候说 C++中更常用的是用 (void *),两种方法得到的结果理论上说应该是相同的,但是我试验了一下,发现并不相同,目前不知道什么原因。

#include <cstdio>
#include <iostream>

using namespace std;

int main()
{
    // 定义
    char *ctr = new char('A');
    char *ccc = new char();
    int *ptr = new int(10);

    ccc = "ABC";
    
    // 打印数据
    cout << *ctr << endl;
    cout << *ccc << endl;
    cout << *ptr << endl << endl;

    // 打印地址
    cout << ctr << endl;
    cout << ccc << endl;
    cout << ptr << endl << endl;

    // 继续深究地址
    cout << &ctr << endl; // 写法1
    cout << &ccc << endl << endl;

    cout << (void *)ctr << endl;
    cout << (void *)ccc << endl;

    delete ctr;
    delete ccc;
    delete ptr;

    return 0;
    
}

回看之前记载的笔记,关于上一段话,我当时其实是有一个大概的猜测,只是没得到证实,现在有了 ChatGPT,就很好了:

在这个程序中,&ctr 和 (void*)ctr 得到的地址不一样的原因是由于它们分别获取了不同类型的地址。

  • &ctr 获取的是指向指针 ctr 本身的地址。因为 ctr 是一个指针变量,其存储的值是另一个对象的地址。所以 &ctr 表达式会返回指针 ctr 自身的地址。
  • (void*)ctr 是将指针 ctr 强制转换为 void* 类型,然后打印该指针的值。void* 是一种通用的指针类型,可以用来存储任何类型的指针。强制转换为 void* 类型后,指针 ctr 的值被打印出来,表示指向分配的内存块的起始地址。

所以,&ctr 和 (void*)ctr 得到的地址不一样,因为它们分别表示不同的地址。

2、分配连续的对象空间

当需要指向的数据有点多,我们就需要一个更大的空间去装,在 C 语言中我们用的是 colloc 函数来解决,在 C++中,我们依旧是用 new 和 delete

格式:

  • 数据类型 *变量对象 = new 数据类型[n]; // 申请 n 个对象空间
  • delete []变量对象; // 销毁对象空间
#include <cstdio>
#include <iostream>

using namespace std;

int main()
{
    // 定义
    char *str = new char[10];
    char *itr = new char[10];

    // 赋值
    cin >> str;
    cout << sizeof(str) << endl; // 大小指的是指针的大小
    cout << str << endl << endl;

    for (int i = 0; i < 10; i++)
    {
        cin >> itr[i];
    }
    for (int i = 0; i < 10; i++)
    {
        cout << itr[i] << endl;;
    }
    
    // 销毁
    delete []str;
    delete []itr;

    return 0;
    
}

3、new/delete 和 malloc/free

newdelete 是 C++ 中的操作符,用于动态分配和释放内存,而 mallocfree 是 C 语言中的函数,也用于动态分配和释放内存。下面是它们之间的区别:

  1. 类型安全性:
  • newdelete 是 C++ 的操作符,能够在内存分配和释放时考虑类型的构造函数和析构函数。它们能够确保分配的内存与对象类型相匹配,并自动调用对象的构造函数和析构函数。因此,使用 newdelete 分配和释放的是具有类型信息的对象。
  • mallocfree 是 C 语言的函数,它们只关注分配和释放内存的字节大小,不考虑对象的类型信息。使用 malloc 分配的内存需要手动进行类型转换,并且不会调用对象的构造函数和析构函数。
  1. 内存分配大小:
  • newdelete 在分配内存时,根据对象类型自动计算所需的内存大小,无需显式指定大小。同时,new 运算符还可以用于数组的动态内存分配。
  • malloc 需要显式指定所需分配的内存大小,并且返回的是 void* 类型指针。
  1. 异常处理:
  • new 操作符在分配内存时,如果内存不足,会抛出 std:: bad_alloc 异常,可以通过异常处理机制进行捕获和处理。
  • malloc 在分配内存时,如果内存不足,返回空指针 NULL,需要手动检查返回值来判断是否分配成功。
  1. 自定义内存管理:
  • newdelete 可以被重载,允许自定义内存管理方式,例如使用定制的内存池。
  • mallocfree 是 C 标准库函数,无法直接进行重载。

总的来说,newdelete 是 C++ 提供的更高级、更安全、更方便的内存分配和释放方式,它们与对象的构造和析构密切相关。而 mallocfree 则是 C 语言中的内存分配和释放函数,更加底层,需要手动处理类型转换和内存大小等问题。在 C++ 中,推荐使用 newdelete 进行内存管理,特别是对于对象的动态分配和释放。

六、引用

1、变量名

变量名实质上就是一段连续内存空间的名称,相当于是一个标记。我们可以用变量名来申请并命名这块内存,并借助该变量名来使用这个空间。

2、引用的概念

变量名本质上是一块内存的引用,C++中引用实际上就是给一个已存在的变量再取别名。

3、引用格式

引用符号:&

容易发现,引用符号跟取地址的符号一样,但是也容易区分,就看&前面有没有数据类型

格式:数据类型 &引用名 = 变量名

eg:

int a = 123; // a是变量名
int &b = a; // b是a的引用

4、引用的特点

  • 引用和被引用的对象必须保持数据类型一致
  • 引用定义的时候必须初始化数据,一个引用只能引用一个对象
  • 引用本身不占用内存空间,与引用的对象共用内存
  • 引用的对象是常量数据,那么需要使用 const 关键字进行修饰:const int &a = 100;
 #include <cstdio>
#include <iostream>

using namespace std;

int main()
{
    int a = 100;

    // 定义引用
    int &b = a; 

    cout << a << "---" << b << endl; // 引用定义的时候必须初始化数据,一个引用只能引用一个对象
    cout << sizeof(a) << "---" << sizeof(b) << endl;

    // 定义变量
    const int data = 456;
    const int &pdata = data; // 引用和被引用的对象需要保持数据类型一致,所以这里的pdata也要加上const
    cout << pdata << "---" << data << endl;

    // 定义引用指向常量数据
    const int &c = 123; // 常量具有只读属性,我们需要加上const以让引用量具有只读属性
    cout << c << "---" << 123 << endl;

    return 0;
    
}

5、引用作为参数或者返回值

若参数列表是引用类型,那么只能传入变量类型参数,若引用类型被 const 修饰,那么传入参数可为常量

(1)、引用作为函数传参

函数传参的方式:值传递、地址传递、引用传递

#include <cstdio>
#include <iostream>

using namespace std;

// 使用值传递交换两个数据的值
void swap1(int a, int b)
{
    a = a^b;
    b = a^b;
    a = a^b;
}

// 地址传递交换两个数据的值
void swap2(int *a, int *b)
{
    *a = *a^*b;
    *b = *a^*b;
    *a = *a^*b;
}

// 引用传递交换两个数据的值
void swap3(int &a, int &b)
{
    a = a^b;
    b = a^b;
    a = a^b;
}

int main()
{
    int a = 12, b = 13;
    cout << a << "---" << b << endl<<endl;

    // 值传递
    swap1(a, b); // int a = a; int b = b;
    cout << a << "---" << b << endl<<endl;

    // 地址传递
    a = 12, b = 13;
    swap2(&a, &b); // 理解为:int *a = &a; int *b = &b
    cout << a << "---" << b << endl<<endl;

    // 引用传递
    a = 12, b = 13;
    swap3(a, b); // int &a = a; int &b = b;
    cout << a << "---" << b << endl<<endl;

    return 0;
    
}

值传递没有改变我在 main 函数中设定的值,因为 a 和 b 传递给形参,形参仅仅是局部变量,作用域仅限于函数内部。这个过程可以理解为形参被实参赋值。

而地址传递则是从地址的维度上触发,即使是在函数中修改的,也是对函数外的数据会造成影响。本质上是形参指向实参的地址,所以在函数中操作形参,其实还是在操作实参,所以 a 和 b 交换数据在函数外也是真实的。

至于引用,在这里可以理解为一个链接,他本身不占用内存空间,而是与被引用的对象共享内存,而由于参数中的引用与实参共享了一片内存,所以操作形参也是对实参进行操作,形参的值也会交换。


再看一个比较难的例子:

#include <cstdio>
#include <iostream>

using namespace std;

// Apply memory space for the pointer
void getMemory1(int **p)
{
    *p = new int;
}

// Apply memory space for the pointer
void getMemory2(int *&p) // Reference to Pointer p, be interpreted as (int *)&p
{
    p = new int;
}

int main()
{
    int *ptr; // define pointer

    getMemory1(&ptr); // Apply memory space for the pointer through function
    *ptr = 100; // Apply for space assignment
    cout << *ptr << endl;

    getMemory2(ptr); // Apply memory space for the pointer through function
    *ptr = 200; // Apply for space assignment
    cout << *ptr << endl;

    return 0;
    
}

这个程序演示了在C++中通过函数为指针申请内存空间的方法,使用了两种不同的方式:通过指针的指针和通过引用。

在主函数中,首先定义了一个指针 ptr,该指针将用于存储动态分配的内存空间的地址。

然后,程序调用了 getMemory1 函数,该函数接受一个指向指针的指针作为参数 p。在函数内部,通过 new 运算符为 *p 分配了一个 int 类型的内存空间,并将该地址赋值给 *p,即将指针 ptr 指向这个内存空间。然后,通过 *ptr 给该内存空间赋值为 100。最后,输出 *ptr 的值为 100

接下来,程序调用了 getMemory2 函数,该函数接受一个指针的引用 p 作为参数。在函数内部,通过 new 运算符为 p 分配了一个 int 类型的内存空间,并将该地址赋值给 p,即将指针 ptr 指向这个内存空间。然后,通过 *ptr 给该内存空间赋值为 200。最后,输出 *ptr 的值为 200

总结:这个程序演示了通过函数为指针申请内存空间的过程。getMemory1 函数通过传递指向指针的指针来修改指针的值,而 getMemory2 函数通过引用传递来修改指针的值。两种方式都能够成功为指针分配内存空间,并在主函数中进行使用。

(2)、引用作为函数返回值,返回变量本身

引用作为函数返回值的时候,引用名和返回值数据类型一致的情况下,可以将这个函数作为赋值运算符的右边充当变量名,赋值给左边的引用名。

#include <cstdio>
#include <iostream>

using namespace std;

// 引用作为返回值
int &func()
{
    int a = 123;
    int &b = a;

    return b;
}

int main()
{
    int &c = func(); 
    // 这里备注两点:
    // 1.函数本身作为一个独立的单元,我们很多时候都根据其返回值而直接将函数写到某些计算或者运算中,这个就相当于我在计算中直接写一个10,也可以(1000/10/10)
    // 2.这里看似很简单直白,理所当然的样子,但是这里也是满足引用左右的数据类型相同的,即两边都是int的引用,假如将上面函数func的返回值改为a,则会报错,原因就是等号两边的数据类型不相同,一边是int的引用,另一边仅仅是int类型

    cout << c << endl;

    return 0;
    
}


除此之外,还可以将这个函数作为赋值运算符的左侧,作为被赋值的那一端。

但是需要在返回值之后,所引用的空间(即被引用的变量所具有的的空间)没有被释放(静态数据、全局变量、堆空间数据、传入的指针数据)。

当然,等号两边的数据类型,即函数返回值的数据类型和变量名的数据类型还是要保持一致的。

#include <cstdio>
#include <iostream>

using namespace std;

// 引用作为返回值
int &func()
{
    int a = 123;
    int &b = a;

    return b;
}

// 普通函数
int func1()
{
    int a = 10;
    a++;

    return a;
}

// 引用返回函数
int &func2()
{
    static int a = 10; // static data
    a++;

    return a;
}

int main()
{
    int x, y;

    x = func1(); // it's as same as x=11, but the memory in function func1 has been freed
    y = func2(); // it's as same as y=11, But the function as an rvalue is equivalent to y=a, and the memory in func2 still exists

    cout << "x=" << x << "---" << "y=" << y << endl;

    return 0;
    
}

PS:切换中英文也是挺麻烦的,干脆以后都用英文注释得了

可以看到 x 和 y 此时都能被打出来,这是没毛病的,此时我们就要针对引用作为左值的情况来进行试验。

我们知道,x 对应的是 func1 ,y 对应的是 func2 ,联系函数本身我们可以知道,func1 中返回值作为一个局部变量,在主函数这边用的话其实他的内存空间已经被释放了而 func2 的返回值加了 static 修饰,已经升级成了全局变量,虽说在外部不可见,但由于其内存是实实际际存在的,在主函数中用也依旧可以。

这里结果为 45 应该是在 func2 中加一了的缘故,至于再次执行 func2 的过程中,应该是忽略了 static int a = 10; 这行代码的


然后让我们进入正题,也就是引用作为函数返回值,返回变量本身,这句话如何理解呢,我们首先来看一个例子:

#include <cstdio>
#include <iostream>

using namespace std;

int f(int i){return ++i;}
int &g(int &i){return ++i;}
int h(int i){return ++i;}

int main()
{
    int a=0, b=0, c=0;

    a += f(g(a));
    b += g(g(b));
    c += f(h(c));

    cout << a << "---" << b << "---" << c << endl;

    return 0;
}

不难看出,之所以会输出 3---4---2 这个玩意,其实都是因为 g() 这个函数本身被引用。

以 3 为例,我们可以看到 g() 的参数和返回值都是 &i,也就是说,从参数上可以认为 int &i = a;,而 a 是在整个 main 函数中有效,所以执行完 g() 后返回的 i,已经等于 1,并且存到了 a 对应的地址上,如此一来,执行完最里面的 g() 后,等号左边的 a 其实已经为 1 了,这还是没有被赋值的情况下,然后又加上右边的 2,就等于 3 了。

这是属于函数被引用的特性,也就是说,这个程序中的计算 a 和 b 都是适用的,因为其中都有 g(),且 g() 中的值都是等号左边的那个变量,至于计算 c,这是纯纯的普通函数,直接算就 vans 了。

6、引用的意义

  1. 在大多数时候可以用引用代替指针
  2. 引用在函数传参的时候,相对于指针来说,具有更好的可读性和实用性
  3. 引用在 C++的内部实现为一个指针常量Type *const name <===> type &name,这里不难理解为什么引用这边没写 const,我们之前已经见到过的,引用常量的时候最开始就已经加了 const:`const int &a = 100;
  4. C++编译器在遇到引用调用的时候,编译器内部会自动实现为指针常量调用
  5. 从使用角度来看,引用会让人以为只是一个别名,没有自己的存储空间,实际上是 C++为实用性做出的细节隐藏

讲师上课讲的是引用的实现为常指针,但是又分明写的是指针常量的形式,可知是他自己记错了

7、引用的特殊用法

(1)、指针的引用
int a = 10;
int *p = &a; // pointer
int *&ptr = p; // reference to pointer p

#include <iostream>

int main() 
{
    int number = 10;
    int* ptr = &number;  // 指针指向变量 number

    int* &ref = ptr;  // 引用指针变量 ptr

    std::cout << *ref << std::endl;  // 输出:10

    *ref = 20;  // 修改通过指针间接访问的变量

    std::cout << *ptr << std::endl;  // 输出:20

    return 0;
}

(2)、函数指针的引用
int (*fun)(void); // Define function pointer
int (*&pfunc)(void) = func(); // reference to function pointer

#include <iostream>

int Add(int a, int b) 
{
    return a + b;
}

int Subtract(int a, int b) 
{
    return a - b;
}

int Multiply(int a, int b) 
{
    return a * b;
}

int main() 
{
    // 声明一个函数指针,指向接受两个int参数并返回int的函数
    int (*ptr)(int, int);

    // 将函数指针指向 Add 函数
    ptr = Add;

    // 使用函数指针调用 Add 函数,并打印结果
    int result = ptr(3, 4);
    std::cout << "Result: " << result << std::endl;

    // 将函数指针指向 Subtract 函数
    ptr = Subtract;

    // 使用函数指针调用 Subtract 函数,并打印结果
    result = ptr(7, 2);
    std::cout << "Result: " << result << std::endl;

    // 将函数指针指向 Multiply 函数
    ptr = Multiply;

    // 使用函数指针调用 Multiply 函数,并打印结果
    result = ptr(5, 6);
    std::cout << "Result: " << result << std::endl;

    return 0;
}

(3)、数组指针的引用
int (*arr)[10]; // Define array pointer
int (*&p)[10] = arr; // reference to array pointer

这里存在一些问题,就是写出来一直存在报错,但是我个人感觉程序本身是不存在问题的:

#include <iostream>

void PrintArray(int* (&ptr)[5]) {
    for (int i = 0; i < 5; ++i) {
        std::cout << ptr[i] << " ";
    }
    std::cout << std::endl;
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};

    int* (&ref)[5] = arr;  // 数组指针的引用

    PrintArray(ref);

    return 0;
}

(4)、函数返回值是引用

标题已经说的很清楚了,函数返回值是引用,可以类比一下指针函数,指针函数意思是返回值为指针的函数,写法为 数据类型 *函数名(){对应数据类型的指针},所以,这里的函数返回值是引用的写法:

数据类型 &函数名(){对应的引用}

#include <iostream>

using namespace std;

class Data
{
public:
    Data() {}
    ~Data() {}

    int& exa()
    {
        int static i = 10;
        int& b = i;

        return b;
    }
};

int main()
{
    Data yi;
    cout << yi.exa();

    return 0;
}

8、引用跟指针的联系区别

在 C++中,引用和指针都是用于处理内存中的数据,并提供对数据的间接访问。它们有一些相似之处,但也有一些重要的区别。下面是引用和指针的联系和区别:

联系:

  1. 间接访问数据:引用和指针都允许间接访问数据。它们可以用来修改所引用或指向的对象的值。

  2. 函数参数传递:引用和指针都可用于函数参数传递。它们可以使函数能够修改调用者提供的数据。

区别:

  1. 语法和操作符:引用使用 & 符号进行声明和定义,并使用被引用对象的名称进行初始化。而指针使用 * 符号进行声明和定义,并使用取址操作符 & 获取对象的地址来进行初始化。

  2. 空值(null):指针可以被赋予空值(nullptrNULL),表示指向无效或空的内存位置。而引用必须在声明时初始化,并且不能指向空值。

  3. 重指向:指针可以在运行时被重新指向不同的对象或空值。而引用在声明后不能重新指向其他对象。

  4. 内存管理:指针可以进行内存分配和释放,例如使用 newdelete。而引用没有自己的内存,它必须引用一个已经存在的对象。

  5. 数组访问:指针可以进行指针算术运算来遍历数组的元素。而引用不支持指针算术运算,它只能直接引用单个对象。

  6. 初始化要求:指针可以不进行初始化,但可能包含一个未知的值。引用必须在声明时进行初始化,并且一直引用同一个对象。

总的来说,引用和指针都提供了对内存中数据的间接访问,但它们的语法、操作符、空值处理、重指向能力、内存管理和数组访问等方面有一些重要的区别。在选择使用引用还是指针时,需要根据具体的需求和场景来进行选择。

七、函数的高级用法

1、函数重载

(1)、关于 C 和 C++的函数重载

在同一个程序中,允许函数名相同,但是参数列表不同(参数类型、参数个数、参数顺序)的函数存在,在调用的时候根据参数传入的不同,有编辑器选择相应的函数执行。


#include <stdio.h>
#include <stdlib.h>

int add(int a, int b)
{
    return a+b;
}

double add(double a, double b)
{
    return a+b;
}

int main(int argc, char const *argv[])
{
    int a = 7, b = 11, c;
    double a1 = 10.1, b1 = 20.1, c1;

    c = add(7, 11);

    printf("c = %d\n", c);

    return 0;
}

可以看到,在 C 语言中,函数名重复是不被允许的。

因为 C 的编译器在编译过程中只检查函数名,而在 C++中,函数名重复是被允许的,因为编译器在编译过程中不仅检查函数名,还检查参数列表。


而在C++中:

#include <cstdio>
#include <iostream>

using namespace std;

int add(int a, int b)
{
    return a+b;
}

double add(double a, double b)
{
    return a+b;
}

int main()
{
    int a = 7, b = 11, c;
    double a1 = 10.1, b1 = 20.1, c1;

    cout <<"c = " << add(a, b) << endl;
    cout <<"c1 = " << add(a1, b1) << endl;

    return 0;
}

(2)、函数重载的要求
  • 函数名相同,但是参数列表不同
  • 函数重载的时候,返回值不能作为重载依据
  • const 修饰的引用或者指针有时也可以作为重载依据

对于第三点,这里做一些解释:

当参数是引用或指针类型时,const 修饰符的存在可以作为重载的依据。编译器会将 const 引用或指针作为不同的参数类型来进行函数匹配。例如,下面的函数重载是合法的:

void foo(int& x);
void foo(const int& x);

第一个函数接受一个非常量引用,第二个函数接受一个常量引用。在调用时,根据提供的参数类型选择匹配的函数。


需要注意的是,函数重载的要求不仅仅限于上述几点。函数的常量性(const 成员函数与非 const 成员函数的重载)、函数的异常声明(抛出不同类型的异常的函数重载)等也可以作为重载的依据。在函数重载时,编译器会通过比较参数的类型和其他相关特征来选择最匹配的函数进行调用。

(3)、多态

函数重载是一种静态多态,它在编译时根据函数的参数列表和相关特征进行决策,以选择正确的函数进行调用。编译器根据提供的参数类型、数量和顺序来确定调用哪个重载函数。函数重载在编译阶段就确定了函数接口,因此它是静态的。

多态性是指同一操作对不同对象产生不同的行为。在运行时,通过基类的指针或引用调用派生类的成员函数,实现动态绑定,即根据对象的实际类型确定调用哪个函数。这种多态性称为动态多态。

在C++中,动态多态可以通过虚函数和继承来实现。通过使用虚函数和基类指针或引用,可以在运行时根据对象的实际类型动态地调用相应的成员函数。

总结起来,函数重载是一种静态多态,通过编译器在编译时确定函数调用;而动态多态是在运行时根据对象的实际类型确定调用哪个函数。


这一部分只需要了解概念就行了,下面是老师讲课的一个例程:

// 尝试使用函数重载完成:传入3个值(数据类型int、char、double)获取其中的最大值

#include <cstdio>
#include <iostream>

using namespace std;

int max(int a, int b, int c)
{
    return (a>b?a:b)>c?(a>b?a:b):c;
}

char max(char a, char b, char c)
{
    return (a>b?a:b)>c?(a>b?a:b):c;
}

double max(double a, double b, double c)
{
    return (a>b?a:b)>c?(a>b?a:b):c;
}

int main()
{
    int a;
    char b;
    double c;

    a = max(3, 44, 1);
    b = max('q', 't', 'A');
    c = max(1.1, 6.5, 0.5);

    cout << a << "---" << b << "---" << c << endl;

    return 0;
}

2、默认参数

在 C++中,对于需要多次从同一个实参的位置赋值给形参的时候,C++提出更简单的处理方法,即给函数的形参提供默认值,如此一来,即使没有实参的传递,那么也可以运行函数。

对于拥有默认值的函数,如果他进行了参数传递,则还是依据传入的实参来执行函数。

  • 只有参数列表的部分才能提供默认值操作
  • 函数中一旦启用默认值数据,那么改参数之后的其他参数也需要成为默认参数
  • 参数列表的默认值必须从右往左依次连续默认
  • 在使用默认参数的时候,函数只需要在声明或者定义的某一个位置赋值即可,要是两个地方都设置了默认值,反而会报错
#include <cstdio>
#include <iostream>

using namespace std;

// Calculate the axis length of the rectangle, and give the parameter the default value
int Length(int w = 5, int h = 4)
{
    return (w + h)*2;
}

int main()
{
    cout << Length() << endl;
    cout << Length(6) << endl;
    cout << Length(1, 2) << endl;

    return 0;
}

3、占位参数

(1)、占位参数是什么

占位参数指的是占用一个形参的位置,但没有对应的形参名,他不参与运算,只作为标志存在。

#include <iostream>

using namespace std;

int add(int a, int b)
{
    return  a + b;
}

int add(int a, int b, int)
{
    return  a + b;
}

int main()
{
    cout << "hello world" << endl;

    // Using placeholder parameters to automatically distinguish API interfaces
    cout << add(12, 23) << endl;
    cout << add(12, 23, 0) << endl;

    return 0;
}


作用:

  • 作为回调函数使用
  • 自增自减运算重载符
  • 为了兼容以前的低版本 C++版本(利用占位参数可以自动识别 API 接口)

下面会对这三个作用分别进行讨论(感谢ChatGPT,泪奔了真的)。

(2)、占位参数作为回调函数使用

回调函数是一种在编程中常见的概念,它是指将一个函数作为参数传递给另一个函数,并在后者中执行该函数以完成特定的任务或回调操作的过程。

回调函数通常用于实现事件处理、异步操作、消息传递、插件扩展等场景。它允许开发人员将特定的行为或逻辑封装在一个函数中,并在需要时将其传递给其他函数或系统,以便在特定事件或条件发生时进行调用。

回调函数的使用方式是,首先定义一个函数作为回调函数,然后将该函数的函数指针或函数对象作为参数传递给其他函数或系统。当特定事件或条件满足时,调用接收回调函数的函数或系统会执行回调函数,并在适当的时候将控制权返回给调用者。

回调函数提供了一种灵活的机制,使程序能够根据运行时的情况动态地扩展或改变其行为。它可以增强代码的可复用性、可扩展性和灵活性,使程序结构更加模块化和解耦。

需要注意的是,回调函数的具体实现方式和使用方法可能因编程语言和应用场景而有所不同。在不同的编程语言和框架中,可能存在不同的回调机制和约定。

#include <iostream>

// 回调函数类型定义,接受两个整数参数
typedef void (*CallbackFunction)(int, int);

// 执行计算操作,接受回调函数作为参数
void PerformCalculation(int a, int b, CallbackFunction callback)
{
    int result = a + b;
    // 调用回调函数,将计算结果作为参数传递给回调函数
    callback(result, a);
}

// 回调函数实现,打印计算结果和原始参数
void PrintResult(int result, int original)
{
    std::cout << "计算结果:" << result << std::endl;
    std::cout << "原始参数:" << original << std::endl;
}

int main()
{
    int a = 5;
    int b = 3;

    // 调用 PerformCalculation 函数,将 PrintResult 函数作为回调函数传递进去
    PerformCalculation(a, b, PrintResult);

    return 0;
}


在给定的例程中,typedef 用于创建一个新的类型定义。它的作用是为一种已存在的类型(函数指针类型)创建一个别名,以方便在后续使用中引用该类型。

在例程中,typedef 用于定义一个名为 CallbackFunction 的新类型。它是一个函数指针类型,指向没有返回值且接受两个整数参数的函数。通过 typedef,我们可以使用 CallbackFunction 来声明和定义具有相同函数签名的函数指针变量。

它将现有的函数指针类型 void (*)(int, int) 定义为新类型 CallbackFunction。这样,我们就可以使用 CallbackFunction 来声明和定义函数指针变量,而不必每次都写出完整的函数指针类型。

(3)、占位参数作为自增自减运算重载符
#include <iostream>

class Counter {
private:
    int count;

public:
    Counter(int c) : count(c) {}

    // 重载前置递增运算符 (++count)
    Counter& operator++() {
        count++;
        return *this;
    }

    // 重载后置递增运算符 (count++)
    Counter operator++(int) {
        Counter temp(count);
        count++;
        return temp;
    }

    // 重载前置递减运算符 (--count)
    Counter& operator--() {
        count--;
        return *this;
    }

    // 重载后置递减运算符 (count--)
    Counter operator--(int) {
        Counter temp(count);
        count--;
        return temp;
    }

    void display() {
        std::cout << "Count: " << count << std::endl;
    }
};

int main() {
    Counter c(5);
    c.display();

    // 使用前置递增运算符
    ++c;
    c.display();

    // 使用后置递增运算符
    c++;
    c.display();

    // 使用前置递减运算符
    --c;
    c.display();

    // 使用后置递减运算符
    c--;
    c.display();

    return 0;
}


Counter& operator++() 中,& 表示返回类型为引用。它的作用是在递增运算符函数中返回对象的引用,而不是创建一个新的对象。

为什么返回类型使用引用呢?

返回引用的目的是允许对同一个对象进行连续的递增操作。如果返回的是值而不是引用,那么每次递增操作都会创建一个新的临时对象,而不是在原始对象上进行修改。这样就无法实现连续递增的效果了。

(4)、占位参数为了兼容以前的低版本 C++版本

在某些情况下,为了兼容之前的代码或低版本的 C++ 标准,可能需要在函数声明或定义中使用占位参数,以确保能够正确匹配相应的函数调用。

在早期版本的 C++ 中,函数重载的解析规则可能较为宽松,导致某些情况下函数调用会出现二义性或者不明确的问题。为了解决这个问题,可以使用占位参数来实现对不同函数的兼容性。

下面是一个简单的例程,展示了占位参数用于兼容旧版本 C++ 的情况:

#include <iostream>

// // 旧版本 C++ 的函数定义
// void foo(int a, int b) {
//     std::cout << "Old version: foo(int, int)" << std::endl;
// }

// // 新版本 C++ 的函数定义
// void foo(int a, int b, int c) {
//     std::cout << "New version: foo(int, int, int)" << std::endl;
// }

// 兼容旧版本 C++ 的占位参数函数
void foo(int a, int b, int = 0) {
    std::cout << "Compatibility version: foo(int, int, int)" << std::endl;
}

int main() {
    foo(1, 2);        // 调用兼容旧版本的函数
    foo(1, 2, 3);     // 调用新版本的函数

    return 0;
}


在上述例程中,我们定义了三个函数 foo,分别是旧版本的 foo(int, int)、新版本的 foo(int, int, int),以及兼容旧版本的函数 foo(int, int, int = 0)

在旧版本的 C++ 中,函数调用时只传递两个参数,因此可以调用旧版本的函数 foo(int, int)。而在新版本的 C++ 中,函数调用时传递三个参数,因此可以调用新版本的函数 foo(int, int, int)

为了兼容旧版本的 C++,我们定义了带有一个占位参数的函数 foo(int, int, int = 0)。这样,无论是调用旧版本的函数还是新版本的函数,都可以通过此兼容性函数进行处理。

当我们调用 foo(1, 2) 时,由于只传递了两个参数,编译器会选择调用兼容性函数,输出 “Compatibility version: foo(int, int, int)”。而当我们调用 foo(1, 2, 3) 时,传递了三个参数,编译器会选择调用新版本的函数,输出 “New version: foo(int, int, int)”。
(GPT的想法很好,但是原来的重载代码其实一起出现是会报错的,想想也只能像我上面那样注释掉之前的版本,只留下一个兼容性版本了)

通过使用占位参数来定义兼容旧版本的函数,我们可以确保代码在不同版本的 C++ 环境下都能正常编译和运行,避免出现二义性或不明确的问题。

(5)、基本格式

首先看看基本的格式:

其实就是在原本函数的基础上,把某个参数去掉变量,只保留其数据类型,就比如下面例子中的:int func(int a, int b, int)

// 标准的应用

#include <cstdio>
#include <iostream>

using namespace std;

int func(int a, int b, int)
{
    return a;
}

int main()
{
    cout << func(2, 3, 4) << endl;

    return 0;
}

(6)、一些探索

值得注意的是,只要是出现了占位参数的地方,调用指定函数的时候,对应于占位参数的那一位上必须要填上数值,而要确保一定填上,占位参数对应位置之前的参数一定不能在定义函数的时候用默认参数,而在占位参数对应位置之后的位置用,调用函数的时候直接不屑重新给改参数则无妨

// 在占位参数对应位置之后的参数用默认参数也可

#include <cstdio>
#include <iostream>

using namespace std;

int func(int, int a, int b = 1)
{
    return a+b;
}

int main()
{
    cout << func(2, 3) << endl;

    return 0;
}

// 在占位参数之前的位置用了默认参数

#include <cstdio>
#include <iostream>

using namespace std;

int func(int a = 1, int, int b)
{
    return a+b;
}

int main()
{
    cout << func(2, 3) << endl;

    return 0;
}

遇到这种情况,即使我在调用函数的时候没有偷懒,也不行:

八、字符串操作符

string 类型是 STL 库封装的一个类,char * 是一个指向字符的指针或者字符串,string 类对 char * 进行了封装,是一个 char * 类型的管理容器。

string 不需要考虑内存的释放和越界,在构建、赋值、销毁等操作都是由 string 自主完成。

而且,string 提供了一系列字符串操作函数,简化了字符串的操作。

1、如何定义并赋值字符串

方式一:string 对象名 = 赋值对象

string name = "李四";


方式二:string 对象名

(即借助赋值符号赋值)

string name;
name = "李四";

方法三:通过 string 的构造函数实现赋值

暂时不懂没关系,知道可以这样用即可,之后的内容会介绍的。

#include <iostream>

using namespace std;

int main()
{
    // Define string class string
    string name1 = "hello";

    string name2;
    name2 = "world";

    string name3("nihao");

    cout << name1 << endl;
    cout << name2 << endl;
    cout << name3 << endl;

    return 0;
}

2、string 如何启动字符串操作函数

由于 string 是类数据,所以在启动类成员函数时,只要在对象之后通过 . 操作符操作响应的函数。

比如,当我需要获取字符串的大小,我们在 C 语言中会直接用到 sizeof 函数,在 C++中我们用的是 size 函数,而且用法跟之前有所区别。


#include <stdio.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    printf("%ld\n", sizeof("helloworld"));

    return 0;
}


#include <cstdio>
#include <iostream>

using namespace std;

int main()
{

    string name = "helloworld";
    cout << name.size() << endl;

    return 0;
}

发现了点小意外,可以看见 C 语言来跑大小出来的是 11,而 C++却是 10,明显,C 计算了 \0,而 C++没有

3、C++与 C 关于字符串用法的比较

(1)、字符串可以直接进行用符号判断相等

C++不愧是 C 的一个 plus,我之前就觉得 C 语言对于字符串的一些操作过于繁琐,没想到在 C++这里直接优化得这么 nice,好评。

我们知道,在 C 语言中,我们用 == 来比较相等,但通常是用来对变量来进行比较,是不允许直接对字符串进行操作的,若是要判断两个字符串是否相等,则需要使用 strcmp 函数。

而在 C++中,这个 == 操作是允许对字符串进行操作的:

#include <iostream>

using namespace std;

int main()
{
    // Define string class string
    string name1 = "hello";
    string name2;
    name2 = "world";
    string name3("nihao");

    cout << name1 << "---" << name2 << "---"  << name3 << endl;

    // Manipulate data with symbols
    if(name1 == name2)
    {
        cout << "equal!\n";
    }
    else
    {
        cout << "unequal\n";
    }

    return 0;
}

(2)、字符串拼接符号

与判断相等的 == 类似,这里也是用符号来对字符串进行连接,对接与 C 语言中的 strcat,而且很有意思的是,这里竟然直接用的 +

#include <iostream>

using namespace std;

int main()
{
    // Define string class string
    string name1 = "hello";
    string name2;
    name2 = "world";

    // Stitching of strings
    cout << name1 + name2 << endl;

    return 0;
}

(3)、获取字符串中的某个位置的字符

这里的操作和 C 语言中是一样的,都是通过下标来实现:

#include <iostream>

using namespace std;

int main()
{
    // Define string class string
    string name1 = "hello";

    // Get the character at a certain position in the string
    char ch = name1[0];
    cout << ch << endl;
    ch = name1[1];
    cout << ch << endl;
    ch = name1[2];
    cout << ch << endl;
    ch = name1[3];
    cout << ch << endl;
    ch = name1[4];
    cout << ch << endl;

    return 0;
}

这里还能用一个字符串函数来写,名字叫做 at

#include <iostream>

using namespace std;

int main()
{
    // Define string class string
    string name1 = "hello";

    // Get the character at a certain position in the string
    char ch = name1.at(0);
    cout << ch << endl;
    ch = name1.at(1);
    cout << ch << endl;
    ch = name1.at(2);
    cout << ch << endl;
    ch = name1.at(3);
    cout << ch << endl;
    ch = name1.at(4);
    cout << ch << endl;

    return 0;
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值