C++ - String/引用/函数进阶

String字符串:

  • C++的标准库中,提供了一种用来表示字符串的数据类型string,这种类型能够表示长度可变的字符序列。使用它必须包含string头文件

  • 定义和初始化:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 默认初始化,空字符串
    string s1;
    // 用另一个字符串变量,做拷贝初始化
    string s2 = s1;
    // 用一个字符串字面值,做拷贝初始化
    string s3 = "Hello World!";
    // 用一个字符串字面值,做直接初始化
    string s4("hello world");
    // 定义字符和重复的次数,做直接初始化,得到 hhhhhhhh
    string s5(8, 'h');
  • 处理字符串中的字符:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    string str = "hello world";
    // 获取第3个字符
    cout << "str[2] = " << str[2] << endl;
    // 将第1个字符改为'H'
    str[0] = 'H';
    // 将最后一个字符改为'D'
    str[str.size() - 1] = 'D';

    cout << "str = " << str << endl;
  • 字符串相加:

    1
    2
    3
    4
    5
    6
    // 字符串相加
    string str1 = "hello", str2("world");
    string str3 = str1 + str2; //str3 = "helloworld"
    string str4 = str1 + "," + str2 + "!";//str4 = "hello,world!"

    //string str5 = "hello," + "world!";//错误,不能将两个字符串字面值相加
  • 比较字符串:

    1
    2
    3
    4
    5
    6
    7
    str1 = "hello";
    str2 = "hello world!";
    str3 = "hehehe";

    str1 == str2; // false
    str1 < str2; // true
    str1 >= str3; // true
    • 如果两个字符串长度相同,每个位置包含的字符也都相同,那么两者“相等”;否则“不相等”;
    • 如果两个字符串长度不同,而较短的字符串每个字符都跟较长字符串对应位置字符相同,那么较短字符串“小于”较长字符串;
    • 如果两个字符串在某一位置上开始不同,那么就比较这两个字符的ASCII码,比较结果就代表两个字符串的大小关系

引用:(&)

  • 在做声明时,可以在变量名前加上“&”符号,表示它是另一个变量的引用。引用必须被初始化。

    1
    2
    3
    4
    5
    6
    7
    int a = 10;
    int& ref = a; // ref是a的引用
    //int& ref2; // 错误,引用必须初始化
    cout << "ref = " << ref << endl; // ref等于a的值

    cout << "a的地址为:" << &a << endl;
    cout << "ref的地址为:" << &ref << endl;// ref和a的地址完全一样
  • 引用本质上就是一个“别名”,它本身不是数据对象,所以本身不会存储数据,而是和初始值“绑定”(bind)在一起,绑定之后就不能再绑定别的对象了。

  • 定义了应用之后,对引用做的所有操作,就像直接操作绑定的原始变量一样。所以,引用也是一种间接访问数据对象的方式。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ref = 20;               // 更改ref相当于更改a
    cout << "a = " << a << endl;//a=20

    int b = 26;
    ref = b; // ref没有绑定b,而是把b的值赋给了ref绑定的a
    cout << "a的地址为:" << &a << endl;
    cout << "b的地址为:" << &b << endl;
    cout << "ref的地址为:" << &ref << endl;
    cout << "a = " << a << endl;

  • 当然,既然是别名,那么根据这个别名再另起一个别名也是可以的:

    1
    2
    3
    4
    5
    6
    // 引用的引用  地址都是一样的
    int& rref = ref;
    cout << "rref = " << rref << endl;
    cout << "a的地址为:" << &a << endl;
    cout << "ref的地址为:" << &ref << endl;
    cout << "rref的地址为:" << &rref << endl;
  • “引用的引用”,是把引用作为另一个引用的初始值,其实就是给原来绑定的对象又绑定了一个别名,这两个引用绑定的是同一个对象。

  • 注意:引用只能绑定到对象上,而不能跟字面值常量绑定;也就是说,不能把一个字面值直接作为初始值赋给一个引用。而且,引用本身的类型必须跟绑定的对象类型一致。

    1
    2
    3
    //int& ref2 = 10;       // 错误,不能创建字面值的引用
    double d = 3.14;
    //int& ref3 = d; // 错误,引用类型和原数据对象类型必须一致

内联函数:

  1. 内联函数是C++为了提高运行速度做的一项优化

  2. 定义内联函数,只需要在函数声明或者函数定义前加上inline关键字

    1
    2
    3
    4
    5
    //内联函数
    inline const string& longerStr(const string& str1, const string& str2)
    {
    return str1.size() > str2.size() ? str1 : str2;
    }
  3. 内联仅仅只是给编译器一个建议,编译器不一定会接受这种建议,如果你没有将函数声明为内联函数,那么编译器也可能将此函数做内联编译。一个好的编译器将会内联小的、简单的函数

默认参数:

  1. c++在声明函数原型的时可为一个或者多个参数指定默认(缺省)的参数值,当函数调用的时候如果没有指定这个值,编译器会自动用默认值代替

    1
    2
    3
    4
    5
    6
    7
    8
    // 默认实参
    string stuInfo(string name = "", int age = 18, double score = 60)
    {
    string info = "学生姓名:" + name + "\t年龄:" +
    to_string(age) + "\t平均成绩:" + to_string(score);

    return info;
    }
  2. 注意:一旦某个形参被定义了默认实参,那它后面的所有形参都必须是默认实参。也就是说,所有默认实参的指定,应该在形参列表的末尾。

    1
    2
    3
    4
    // 错误,默认实参不在形参列表末尾
    //string stuInfo(string name = "", int age = 18, double score);
    // 正确,可以前面的形参没有默认实参
    string stuInfo(string name, int age = 18, double score = 60);
  3. 也可以在声明的时候定义实参:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    void Fun(char a='x',int b=66);
    int main(void)
    {
    cout << "hello world" << endl;
    Fun();
    return 0;
    }
    void Fun(char a,int b)
    {
    cout << "a:" << a << endl;
    cout << "b:" << b << endl;
    }
  4. 默认实参定义时要优先放到形参列表的尾部;而调用时,只能省略尾部的参数,不能跳过前面的形参给后面传值。

    1
    2
    3
    4
    5
    6
    7
    cout << stuInfo() << endl;              // "",18, 60.0
    cout << stuInfo("张三") << endl; // "张三",18, 60.0
    cout << stuInfo("李四", 20) << endl; // "李四",20, 60.0
    cout << stuInfo("王五", 22, 85.5) << endl;// "王五",22, 85.5

    //cout << stuInfo(19, 92.5) << endl;// 错误,不能跳过前面的形参给后面传值
    //cout << stuInfo(, , 59.5) << endl;// 错误,只能省略末尾的形参
  5. 注意点:

    • 注意函数与重载的二义性。
    • 函数的默认参数从左向右,如果一个参数设置了默认参数,那么这个参数之后的参数都必须设置默认参数。
    • 如果函数声明和函数定义分开写,函数声明和函数定义不能同时设置默认参数。

占位参数:

  1. c++在声明函数时,可以设置占位参数。占位参数只有参数类型声明,而没有参数名声明。一般情况下,在函数体内部无法使用占位参数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    void Fun(char ,int ,float );//只有类型,没有变量名
    void Fun(char a,int b,float c,char);
    int main(void)
    {
    cout << "hello world" << endl;
    Fun('a',1,1.1);
    Fun('b',2,2.2,'3');
    return 0;
    }
    void Fun(char a,int b,float c)
    {
    cout << "a1:" << a << endl;
    cout << "b1:" << b << endl;
    cout << "c1:" << c << endl;
    }
    void Fun(char a,int b,float c,char)
    {
    cout << "a2:" << a << endl;
    cout << "b2:" << b << endl;
    cout << "c3:" << c << endl;
    }
  2. 注意:占位参数也可以指定缺省值

函数重载:

  1. 在C++中,同一作用域下,同一个函数名是可以定义多次的,前提是形参列表不同(参数类型、数量或顺序不同)。这种名字相同但形参列表不同的函数,叫做“重载函数”。

    1
    2
    3
    4
    5
    6
    7
    8
    void func(){}
    void func(int x){}
    void func(int x,char y){}

    //经过 g++编译环节之后
    _Z7funcv //v 代表 void,无参数
    _Z7funci //i 代表参数为 int 类型
    _Z7funcic //i 代表第一个参数为 int 类型,第二个参数为 char 类型

extern “C”:

  1. 以下在 Linux 下测试:

    • c 函数: void MyFunc(){} ,被编译成函数: MyFunc

    • c++函数: void MyFunc(){},被编译成函数: _Z7Myfuncv

    • 通过这个测试,由于 c++中需要支持函数重载,所以 c 和 c++中对同一个函数经过编译后生成的函数名是不相同的,这就导致了一个问题,如果在 c++中调用一个使用 c 语言编写模块中的某个函数,那么 c++是根据 c++的名称修饰方式来查找并链接这个函数,那么就会发生链接错误,以上例,

    • c++中调用 MyFunc 函数,在链接阶段会去找 _Z7Myfuncv,结果是没有找到的,因为这个 MyFunc 函数是 c 语言编写的,生成的符号是 MyFunc。

  2. 那么如果我想在 c++调用 c 的函数怎么办?

    • extern “C”的主要作用就是为了实现 c++代码能够调用其他 c 语言代码。
    • 加上 extern “C”后,这部分代码编译器按 c 语言的方式进行编译和链接,而不是按 c++的方式。
1
2
3
4
5
6
7
8
9
10
11
12
#ifdef __cplusplus       // 如果是C++编译器编译,定义此宏
extern "C" { // 开始C链接约定
#endif

/* 这里放C语言兼容的函数声明/变量声明 */
/* 例如: */
void c_function(int param);
int global_var;

#ifdef __cplusplus // 对应开始的#ifdef
} // 结束C链接约定
#endif

参考链接: