C++(1)

一、从 C 到 C++

1、引用(掌握)
1.1 概念
  • 别名机制:引用本质上是对变量的一种别名,它就像变量的另一个名字,对引用的操作实际上就是对原变量的操作。从底层实现来看,引用可能是通过指针来实现的,但在使用上,它比指针更加直观和安全。引用在很多面向对象的编程语言中都有广泛应用,它提供了一种简洁而高效的方式来操作变量。
  • 示例

cpp

#include <iostream>

using namespace std;

int main()
{
    int a = 1;
    // b是a的引用
    int &b = a;

    cout << a << " " << &a << endl; 
    cout << b << " " << &b << endl; 

    return 0;
}

在这个示例中,b 是 a 的引用,输出结果可以看到 a 和 b 的值相同,并且它们的内存地址也相同,这表明它们指向同一个内存空间。

1.2 引用的性质
  • 初始化要求
    • 必须初始化:声明引用时,必须同时进行初始化,因为引用一旦被创建,就必须绑定到一个具体的变量上。例如:

cpp

// 错误:未初始化
// int &b; 

  • 不能指向 NULL:普通引用不能初始化为 NULL,因为引用必须引用一个实际存在的对象。例如:

cpp

// 错误:常量不能直接起别名
// int &b = NULL; 

  • 常量引用
    • 当使用 const 修饰引用时,它可以绑定到字面量,例如 const int &b = 12;。这种引用被称为常量引用,它的值不能被修改。这在函数参数传递中非常有用,当函数不需要修改传入的参数时,可以使用常量引用,这样既可以避免参数的拷贝,又能保证参数的安全性。
    • 虽然常量引用本身的值不能被修改,但如果原变量的值被改变,引用的值也会相应改变。例如:

cpp

#include <iostream>

using namespace std;

int main()
{
    int a = 2;
    const int &b = a;

    // 错误:b是只读的
    // b++; 
    a++;
    cout << a << " " << &a << endl; 
    cout << b << " " << &b << endl; 

    return 0;
}

  • 指针与引用
    • 可以将变量引用的地址赋值给一个指针,此时指针指向的还是原来的变量。例如:

cpp

#include <iostream>

using namespace std;

int main()
{
    int a = 1;
    int &b = a;
    int *c = &b;    // c同时指向了a和b

    cout << a << " " << &a << endl; 
    cout << b << " " << &b << endl; 
    cout << *c << " " << c << endl; 
    return 0;
}

  • 还可以对指针建立引用,例如 int *&d = c;,这里 d 是 c 的引用,对 d 的操作等同于对 c 的操作。
  • 不可重新绑定:引用一旦绑定到某个变量,就不能再绑定到其他变量。例如:

cpp

#include <iostream>

using namespace std;

int main()
{
    int a = 1;
    // b是a的引用
    int &b = a;

    int c = 3;

    b = c;  // 赋值,不是引用,b还是a的引用
    b++;

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

    return 0;
}

在这个例子中,b = c; 只是将 c 的值赋给了 b(也就是 a),而不是让 b 成为 c 的引用。

1.3 引用的参数
  • 传引用优势:在函数参数传递时,使用引用可以避免参数的拷贝,从而提高程序的运行效率。特别是当传递的对象比较大时,这种效率提升更为明显。例如,交换两个变量的值的函数,如果使用值传递,会创建两个变量的副本,而使用引用传递则可以直接操作原变量。

cpp

#include <iostream>

using namespace std;

// C++编程方式,符合需求
void swap(int &a, int &b)
{
    int temp = a;
    a = b;
    b = temp;
}

int main()
{
    int a1 = 1;
    int b1 = 2;
    swap(a1, b1);

    cout << a1 << endl;
    cout << b1 << endl;

    return 0;
}

  • const 修饰:如果在函数中不打算修改传入的引用参数,建议使用 const 进行修饰,这样可以保证引用的安全性。例如:

cpp

#include <iostream>

using namespace std;

void printValue(const int &a)
{
    // 错误:const修饰,无法修改
    // a++; 
    cout << a << endl;
}

int main()
{
    int a1 = 1;
    printValue(a1);

    cout << a1 << endl;

    return 0;
}

面试题:指针与引用的区别?

  1. 初始化要求:引用必须在声明时进行初始化,而指针可以先声明,之后再赋值,也可以初始化为 NULL
  2. 重新绑定能力:引用一旦绑定到某个变量,就不能再重新绑定到其他变量;而指针可以在任何时候指向其他地址。
  3. 本质区别:引用是变量的别名,它本身并不是一个独立的对象;而指针是一个独立的变量,它存储的是另一个对象的地址。
  4. 内存占用sizeof(引用) 返回的是引用所绑定的变量的大小,而 sizeof(指针) 返回的是指针本身的大小(通常在 32 位系统上是 4 字节,在 64 位系统上是 8 字节)。
  5. 使用安全性:引用在使用时不需要进行解引用操作,因此使用起来更加直观和安全;而指针在使用时需要进行解引用操作,如果不小心使用了空指针或野指针,可能会导致程序崩溃。
2、赋值(熟悉)
  • 初始化语法
    • 传统赋值int a = 1; 这是最常见的赋值方式,在 C 和 C++ 中都可以使用。
    • 括号初始化int a(1);int b(a); 这种方式在功能上等同于传统赋值,但在某些情况下,括号初始化可以避免一些潜在的问题,例如在模板编程中。

cpp

#include <iostream>

using namespace std;

int main()
{
    int a(1);   // 等同于 int a = 1;
    cout << a << endl;

    int b(a);   // 等同于 int b = a;
    cout << b << endl;

    int c(a + b); // 等同于 int c = a + b;
    cout << c << endl;
    return 0;
}

  • C++11 列表初始化int b3{b}; 这种初始化方式在 C++11 中引入,它对数据窄化(例如将 double 类型的值转换为 int 类型)会提出警告。这有助于避免一些潜在的数据丢失问题。

cpp

#include <iostream>

using namespace std;

int main()
{
    int a(1);   // 等同于 int a = 1;
    cout << a << endl;

    double b = 3.14;
    int b1 = b;
    cout << b1 << endl; // 3

    int b2(b);
    cout << b2 << endl; // 3

    int b3{b};  // 升级:对数据窄化提出警告
    cout << b3 << endl; // 3

    return 0;
}
3、键盘输入(熟悉)
  • 基本输入cin >> a >> str; 可以使用 cin 从标准输入读取数据,并将其赋值给变量。cin 会自动根据变量的类型进行数据的转换。这种方式可以连续读取多个数据,数据之间用空格分隔。

cpp

#include <iostream>
#include <string>

using namespace std;

int main()
{
    int a;
    // C++的字符串是string
    string str;

    cout << "请输入一个数字和字符串" << endl;

    cin >> a >> str;    // 接收键盘输入,一个整数和一个字符串,可以连续操作

    cout << a << str << endl;

    return 0;
}

  • 带空格字符串:当需要读取包含空格的字符串时,使用 getline(cin, str); 可以读取整行输入,包括空格。

cpp

#include <iostream>
#include <string>

using namespace std;

int main()
{
    // C++的字符串是string
    string a;

    cout << "请输入一个字符串,可以包含空格" << endl;
    getline(cin, a);
    cout << a << endl;

    return 0;
}
4、string 字符串类(掌握)
  • 头文件:使用 string 类需要包含头文件 #include <string>,注意不是 string.hstring.h 是 C 语言中用于处理字符串的头文件。string 类是 C++ 标准库中的一个类,它提供了方便的字符串操作功能,并且可以自动管理内存,避免了手动管理内存的麻烦。
  • 常用方法
    • 长度获取str.size() 和 str.length() 都可以返回字符串的长度,它们的功能是相同的。
    • 字符访问:可以使用 str[i] 和 str.at(i) 来访问字符串中的第 i 个字符。str[i] 的执行效率较高,但不进行越界检查;而 str.at(i) 会进行越界检查,如果越界会抛出异常,因此更加安全。

cpp

#include <iostream>
#include <string>

using namespace std;

int main()
{
    string str = "helloworld";
    cout << str.size() << endl; 
    cout << str.length() << endl; 
    cout << str[5] << endl; 
    cout << str.at(5) << endl; 

    return 0;
}

  • 遍历方式
    • 普通循环:使用 for 循环可以遍历字符串中的每个字符,例如:

cpp

#include <iostream>
#include <string>

using namespace std;

int main()
{
    string str = "helloworld";

    // 以for循环的方式进行输出字符串
    for (int i = 0; i < str.size(); i++)
    {
        cout << str.at(i);
    }
    cout << endl;

    return 0;
}

  • C++11 范围循环:C++11 引入了范围循环,使用 for (char c : str) 可以更简洁地遍历字符串中的每个字符。

cpp

#include <iostream>
#include <string>

using namespace std;

int main()
{
    string str = "helloworld";

    // 以for each的方式循环遍历字符串
    for (char c : str)
    {
        cout << c;
    }
    cout << endl;

    return 0;
}

  • 类型转换
    • 字符串转数字:可以使用 istringstream 来将字符串转换为数字。例如:

cpp

#include <iostream>
#include <sstream> 
#include <string>

using namespace std;

int main()
{
    string s = "123";
    // string → int
    istringstream iss(s);
    int i;
    iss >> i;
    cout << i << endl;

    return 0;
}

  • 数字转字符串:使用 stringstream 可以将数字转换为字符串。例如:

cpp

#include <iostream>
#include <sstream> 
#include <string>

using namespace std;

int main()
{
    int i = 123;
    // int → string
    stringstream ss;
    ss << i;
    string s2 = ss.str();
    cout << s2 << endl;

    return 0;
}
5、函数
5.1 内联函数(熟悉)
  • 作用:内联函数的主要作用是取代 C 语言中宏定义的函数。在编译时,内联函数会直接将函数体展开到调用它的地方,这样可以减少函数调用的开销,提高程序的执行效率。特别是对于那些代码简短、频繁调用的函数,使用内联函数可以显著提高性能。
  • 建议场景:通常将具有以下性质的函数写为内联函数:
    • 代码长度在 5 行以内,这样可以保证函数体不会过于庞大,展开后不会导致代码膨胀。
    • 不包含复杂的控制语句,如 for 循环、while 循环、switch 语句等,因为复杂的控制语句会增加代码的复杂度,不适合内联展开。
    • 频繁被调用的函数,这样可以充分发挥内联函数减少调用开销的优势。
  • 关键字inline 是声明内联函数的关键字。需要注意的是,手动添加 inline 关键字只是给编译器一个建议,编译器会根据自己的判断准则来决定是否将函数真正内联。例如:

cpp

#include <iostream>
#include <string>

using namespace std;

// 内联函数
inline void print_string(string str)
{
    cout << str << endl;
}

int main()
{
    print_string("helloworld");

    return 0;
}
5.2 函数重载(重点)
  • 条件:C++ 中允许使用函数重载,即多个函数可以使用同一个名称。函数重载的条件是函数名称相同,但参数列表不同,具体可以是参数的类型不同、数量不同或者参数的前后顺序不同。需要注意的是,函数重载与返回值类型无关。例如:

cpp

#include <iostream>
#include <string>

using namespace std;

void print_show(int i)
{
    cout << "调用了int重载:" << i << endl;
}

void print_show(float i)
{
    cout << "调用了float重载:" << i << endl;
}

void print_show(string i)
{
    cout << "调用了string重载:" << i << endl;
}

int main()
{
    print_show(1);
    print_show(1.2f);
    print_show("hello");

    return 0;
}

  • 错误示例:仅返回值类型不同或仅 const 修饰参数(如 const string)无法实现函数重载,因为编译器在调用函数时,无法根据返回值类型或 const 修饰来区分应该调用哪个函数。例如:

cpp

// 错误:名称相同,参数类型相同,编译器无法区分
// 与返回值类型无关
// int print_show(int s)
// {
//     cout << "调用了int2重载:" << s << endl;
//     return s;
// }

// 错误:const关键字,也无法作为判断依据
// void print_show(const string s)
// {
//     cout << "调用了string重载:" << s << endl;
// }
5.3 哑元函数(熟悉)
  • 定义:函数的参数只有类型,没有名称,这样的函数就是哑元函数。例如:

cpp

#include <iostream>

using namespace std;

// 哑元函数
void print_show(int)
{
    cout << "调用了int重载" << endl;
}

int main()
{
    print_show(1);

    return 0;
}

  • 作用
    • 区分函数重载:哑元函数可以用来区分函数重载,例如:

cpp

#include <iostream>

using namespace std;

// 哑元函数
void print_show(int a, double)
{
    cout << "调用了int, double重载" << endl;
}

void print_show(int a)
{
    cout << "调用了int重载" << endl;
}

int main()
{
    print_show(1);
    print_show(1, 2.0);

    return 0;
}

  • 运算符重载:在运算符重载中,哑元函数也有重要的应用。例如,在重载后置自增运算符 ++ 时,通常会使用一个哑元参数来区分前置和后置自增运算符。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值