1. 联系

CC++之间的关系是紧密且复杂的。C++最初是作为C语言的一个扩展开发的,目的是在不放弃C的强大功能和效率的同时,增加对象导向编程、泛型编程和其他一些特性。下面是CC++之间主要的关系和区别:
1. 兼容性C++在很大程度上是与C兼容的。这意味着许多C程序可以在C++编译器中编译并运行,尽管可能需要一些小的修改。
2. 面向对象编程(OOPC++引入了面向对象编程。它允许使用类和对象,而C是一个过程性语
言,不支持这些概念,或者说支持的不好,麻烦。
3. 模板C++支持模板,这是一种允许程序员编写与数据类型无关的代码的功能。C没有这个功能。
4. 标准库C++有一个更丰富的标准库,包括STL(标准模板库),这为数据结构和算法提供了广泛的支持。而C的标准库相对较小。
5. 类型检查C++C提供更严格的类型检查。这意味着某些在C中可行但可能导致错误的代码在
C++中可能无法编译。
6. 异常处理C++支持异常处理,这是一种处理程序运行时错误的机制。C没有内置的异常处理机制。
7. 命名空间C++引入了命名空间,这有助于防止名称冲突。C没有这个概念。

2. 命名空间

2.1.1 命名空间作用

•  创建自己的命名空间是 C++ 中组织代码的一种好方法,特别是在开发大型项目或库时。命名空间可以帮助你避免名称冲突,并且清晰地组织代码。
• std是 C++ 标准库的命名空间。它是一个定义在C++标准库中的所有类、函数和变量的命名空间。
• 新建一个C++工程默认有的代码是
#include <iostream>
using namespace std;
int main()
{
    cout << "Hello World!" << endl;
    return 0;
}
• 在C++中,如果你想使用std标准库中的任何类、函数或对象,你通常有两种选择:
1. 使用 std:: 前缀:这是最常见的方式,它明确指定了你正在使用的是位于 std 命名空间中的元
素。
std::cout << "Hello, world!" << std::endl;
2. 使用using namespace std; 这允许你在不显式指定std::的情况下使用 std 命名空间中的
所有元素。
using namespace std;
cout << "Hello, world!" << endl;
• std包含的内容:
• std 命名空间包含了许多类、函数和对象,例如:
        • 输入输出库(如 std::cout , std::cin , std::endl
        • 容器类(如 std::vector , std::map , std::set
        • 字符串类( std::string )
        • 异常类( std::exception 和相关子类)
        • 算法(如 std::sort , std::find
        • 实用工具(如 std::pair , std::tuple
        • 其他许多功能

2.1.2 自定义命名空间

• 定义命名空间,在一个头文件里面创建了一个命名空间

#ifndef CIR_H
#define CIR_H


namespace Cir {
    double pi = 3.14;
    double area(double r){//求圆的面积
        return pi * r * r;
    }
    double getLength(double r){//求圆的周长
        return 2 * pi * r;
    }
}

#endif // CIR_H
• 使用命名空间
#include <iostream>
#include "Cir.h"

using namespace std;
using namespace Cir;
int main()
{
    system("chcp 65001 > nul");//强制控制台用UTF-8编码
    cout << "Hello World!" << endl;
    double r ;
    cout << "请输入半径:" << endl;
    cin >> r;
    cout << "对应的周长是:" << getLength(r) << ",对应的面积是:" << area(r) << endl;
    return 0;
}
• 通过使用自定义命名空间,你可以有效地组织你的代码,并减少不同库之间的名称冲突。

3.  基础

3.1 输入输出

• 常用的是 iostream 库,它提供了用于输入和输出的基本流类,包括 cin,cout,cerrclog
• 标准错误流 ( cerr ) 和标准日志流 ( clog )
        • cerr用于输出错误消息。与cout不同, cerr 不是缓冲的,这意味着它会立即输出。
        • clog类似于cerr ,但它是缓冲的。它通常用于记录错误和日志信息。

3.2 <climits>这个头文件

• 用途:查询数据类型的取值范围

#include <climits>//引用了这个头文件之后
• 然后,可以使用它提供的各种常量,例如:
       • INT_MAX : int 类型的最大值。
       • INT_MIN : int 类型的最小值。
       • UINT_MAX : unsigned int 类型的最大值。
       • LONG_MAX : long int 类型的最大值。
       • LONG_MIN : long int 类型的最小值。
       • LLONG_MAX : long long int 类型的最大值。
       • LLONG_MIN : long long int 类型的最小值。

3.3 内联函数

• 内联函数(Inline Function)C++中一种特殊的函数,其定义直接在每个调用点展开。这意味着编译器会尝试将函数调用替换为函数本身的代码,这样可以减少函数调用的开销,尤其是在小型函数中。
• 特点
        1. 减少函数调用开销:内联函数通常用于优化小型、频繁调用的函数,因为它避免了函数调用的常规开销(如参数传递、栈操作等)。
        2. 编译器决策:即使函数被声明为内联,编译器也可能决定不进行内联,特别是对于复杂或递归函数。
        3. 适用于小型函数:通常只有简单的、执行时间短的函数适合做内联。
        4. 定义在每个使用点:内联函数的定义(而非仅仅是声明)必须对每个使用它的文件都可见(必须是完整的代码),通常意味着将内联函数定义在头文件中。
• 使用方法
//通过在函数声明前添加关键字inline来指示编译器该函数适合内联
inline int max(int x, int y) {
    return x > y ? x : y;
}

• 例子

#include <iostream>

using namespace std;

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

int main()
{
    cout << "Hello World!" << endl;
    int sum = add(3,5);//编译器可能会将此替换为:int sum = a +b = 5 + 3;
    cout << "sum:" << sum << endl;
    return 0;
}

• 注意事项:

• 过度使用的风险:不应滥用内联函数,因为这可能会增加最终程序的大小(代码膨胀)。对于大型函数或递归函数,内联可能导致性能下降。
• 编译器的决定:最终是否将函数内联是由编译器决定的,即使函数被标记为 inline
• 适用场景:最适合内联的是小型函数和在性能要求高的代码中频繁调用的函数。

3.4 Lambda表达式

• Lambda表达式是C++11引入的一种匿名函数的方式,它允许你在需要函数的地方内联地定义函数,而无需单独命名函数。
• Lambda 表达式的基本语法如下:
[capture clause](parameters) -> return_type {
    // 函数体
    // 可以使用捕获列表中的变量
    return expression; // 可选的返回语句
}
• Lambda 表达式由以下部分组成:
• 捕获列表(Capture clause):用于捕获外部变量,在 Lambda 表达式中可以访问这些变量。捕
  获列表可以为空,也可以包含变量列表 [var1, var2, ...]
• 参数列表(Parameters):与普通函数的参数列表类似,可以为空或包含参数列表(param1, param2, ...) 。
• 返回类型(Return_type):Lambda表达式可以自动推断返回类型auto,也可以显式指定返回类
-> return_type 。如果函数体只有一条返回语句,可以省略返回类型。
• 函数体(Body):Lambda 表达式的函数体,包含需要执行的代码。
• 示例1:使用 Lambda 表达式进行加法,作为一种简洁而快速的方式来定义小型函数
#include <iostream>

using namespace std;

int main()
{
    //cout << "Hello World!" << endl;
    //定义一个简单的Lambda表达式进行加法
    auto sum = [](int a,int b)-> int {//这是lambad表达式,但是此时如果不用回调调用的话,是没法调用的
        //所以此时需被迫加上名字(显示取了一个名字)
        return a + b;
    };

    //使用Lambda表达式计算两个数的和
    int ret = sum(5,3);//有点“函数嵌套”的感觉。
    cout << "ret:" << ret;

    return 0;
}

• 示例2:使用一个函数来找出两个数中的较大数,这个函数将接受一个lambda函数作为回调来比较这两个数。Lambda函数将直接在函数调用时定义,完全是匿名的。

• 先使用函数指针作为参数的例子。

#include <iostream>

using namespace std;


bool myCompare(int a,int b){
    return a > b;
}

int getMaxValue(int a,int b,bool (*p1)(int , int)){
    if(p1(a,b)){
        return a;
    }else {
        return b;
    }
}

int main()
{
    int max = getMaxValue(8,5,myCompare);
    cout << "MAX_Value:" << max << endl;
    return 0;
}

• 然后再使用lamdba表达式作为参数的例子。

#include <iostream>

using namespace std;


int getMaxValue(int a,int b,bool (*p1)(int , int)){
    if(p1(a,b)){
        return a;
    }else {
        return b;
    }
}

int main()
{
    int a = 8,b = 16;
    //直接在函数调用中定义匿名 lambda 函数
    int max = getMaxValue(a,b,[](int a,int b){
        return a > b;
    });
    cout << "MAX_Value:" << max << endl;
    return 0;
}

• 参数捕获:在 Lambda 表达式中,参数捕获是指 Lambda 表达式从其定义的上下文中捕获变量的能力。这使得Lambda可以使用并操作在其外部定义的变量。捕获可以按值(拷贝)或按引用进行

• 示例:使用带参数捕获的 Lambda 表达式
#include <iostream>

using namespace std;

int main()
{
    int x = 10;
    int y = 20;

    //捕获 x 和 y 以便在 Lambda 内部使用
    //这里的捕获列表 [x, y] 表示 x 和 y 被按值捕获
    auto sum = [x,y](){
        //        y++;
        //        x++;会报错,按值捕获,关注的是值本身,无法修改
        return x + y;
    };
    cout << "Sum is: " << sum() << endl;
    cout << "x is now: " << x << ", y is now: " << y << endl;

    //捕获所有外部变量按值捕获(拷贝)
    int z = 30;
    auto mul = [=](){
        // z++;会报错,按值捕获,关注的是值本身,无法修改
        return x * y * z;
    };
    cout << "mul is: " << mul() << endl;

    // 捕获所有外部变量按引用捕获
    auto modifyAndSum = [&](){
        x = 15;//修改x的实际值
        y = 50;//修改y的实际值, 引用捕获可以修改
        return z * x * y;
    };
    cout << "modifyAndSum is: " << modifyAndSum() << endl;
    cout << "x is now: " << x << ", y is now: " << y << ", z is now: " << z << endl;
    return 0;
}

• 内联函数和lambda表达式的区别

特性 Lambda表达式 内联函数
定义
一种匿名函数,通常用于定义在需要它们的地方。
一种常规函数,通过inline 关键字定义。
用途
提供一种快捷方式来定义临时的、小型的函数。
用于优化小型函数,减少函数调用的开销。
语法
使用 [capture](params) { body }
的形式定义。
使用常规函数定义语法,但在前面加上inline 关键字。
生命周期
在定义它们的作用域内有效。
在整个程序执行期间有效。
捕获外部变量
可以捕获外部作用域中的变量(按值或按引用)。
不能直接捕获外部变量,只能通过参数传递。
调用方式
作为函数对象,可直接调用。
像普通函数一样调用。
优化
可以被编译器自动内联化,但这取决于编译器优化策略。
明确请求编译器尝试内联,但实际内联化也取决于编译器。
可见性
通常只在定义它们的局部作用域内可见。
可以在定义它的任何作用域内可见。
使用场景
适合于一次性使用的场景,如作为回调、 在算法中使用等。
适合于频繁调用的小型函数

3.5 字符串string类型

• C语言中对字符串的表示通常用指针,但是可能会面临内存泄漏或者段错误等众多问题。
• 在 C++ 中,string类是标准库的一部分,用于表示和操作字符串。它是对传统的C风格字符串(以空字符 '\0' 结尾的字符数组)的一个更安全、更方便的封装。 string 类是在<string>头文件中定义的,并且位于 std 命名空间中。
• string类提供了许多有用的功能和特性,包括:
1. 动态大小:C风格的字符串不同, string对象的字符串可以动态改变大小,这意味着你可以在运行时添加或移除字符,而不需要担心分配和释放内存。
2. 安全性:由于string管理自己的内存,因此减少了内存泄漏和缓冲区溢出的风险。
3. 方便的成员函数:string类提供了各种操作字符串的方法,如 append() (添加),insert()(插入)、 erase() (删除), substr()(获取子字符串)等。
4. 操作符重载:string类重载了多个操作符,使得字符串比较、连接和赋值更加直观。例如,你可
以使用 + 操作符来连接两个字符串,或者使用 == 操作符来比较两个字符串是否相等。
5. 迭代器支持:像其他标准库容器一样, string 类也支持迭代器,使得你可以使用迭代器来遍历字
符串中的字符。
6. C 风格字符串的兼容性:string类提供了与C风格字符串互操作的功能,例如,你可以使用
c_str() 方法来获取一个与C风格字符串兼容的、以null结尾的字符数组。
• 常用字符串函数
函数名 功能 参数 返回值类型
length() size()
返回字符串的长度
size_t
empty()
检查字符串是否为空
bool
append(const string& str)
向字符串末尾添加另一个字符串
要追加的字符串
string&
substr(size_t pos = 0, size_t len = npos)
返回一个子字符串
pos:子字符串 的起始位置
len:子字符串的长度
string
find(const string& str,size_t pos = 0)
查找子字符串出 现的位置
str:要查找的
字符串
pos:搜索起始位置
size_t
compare(const string& str)
比较两个字符串
要比较的字符串
int
erase(size_t pos = 0, size_t len = npos)
删除字符串中的 一部分
pos:起始位置
len:要删除的 长度
string&
insert(size_t pos, const string& str)
在指定位置插入 字符串
pos:插入位置
str:要插入的字符串
string&
replace(size_t pos, size_t len, const string& str)
替换字符串中的
一部分
pos:起始位置
len:要替换的 长度
str:替换的字符串
string&
c_str()
返回 C 风格字符串表示
const char*
operator[] (size_t pos)
访问指定位置的
字符
pos:字符位置
char&

示例

#include <iostream>
#include <string>


using namespace std;

int main()
{
    system("chcp 65001 > nul");//强制控制台用UTF-8编码
    string str = "hello";
    if(!str.empty()){
        cout << "该字符串不为空,长度为:" << str.length() << endl;//长度为5

    }

    str.append(",world");
    cout << "拼接后的字符串为:" << str << endl;

    //cout << "查找到子串为" << str.substr(0,6);
    //cout << "子串首次出现的位置是" << str.find("ll",0) << endl;
//    string strT = "hello";
//    cout << str.compare(strT) << endl;
//    strT.append(",world");
//    cout << str.compare(strT) << endl;//0

//    cout << "删除的字符串为:" << str.erase(5,6) << endl;
//    cout << "删除之后的字符串为:" << str << endl;

//    str.insert(5,",world");
//    cout << "insert之后的字符串为:" << str << endl;
//    str.replace(5,6,"xiaole");
//    cout << "replace之后的字符串为:" << str << endl;
//    const char * s = str.c_str();
//    printf("c风格的字符串为%s\r\n",s);

    cout << "当前字符为:" << str.operator[](2) << endl;
    return 0;
}

4. 类

4.1 类的初识

• 在C++中的类(class)是一种编程结构,用于创建对象。这些对象可以拥有属性(即数据成员)和行为(即成员函数或方法)。类的概念是面向对象编程的核心之一,其主要目的是将数据和与数据相关的操封装在一起。例如,如果你有一个汽车类,它可能包含颜色、品牌、型号等属性(数据成员),以及启动、停止、加速等行为(成员函数)。每当你基于这个类创建一个对象时,你就有了一个具体的汽车,具有这些属性和行为。
• 类的基本结构通常包含:
1. 数据成员(Attributes):定义类的属性。这些是类内部的变量,用于存储对象的状态
2. 成员函数(Methods):定义类的行为。这些是可以操作对象的数据成员的函数。
3. 构造函数和析构函数:特殊的成员函数。构造函数在创建对象时自动调用,用于初始化对象。析构 函数在对象销毁时调用,用于执行清理操作。
4. 访问修饰符:public , private , protected ,用于控制对类成员的访问权限。例如,public成员可以在类的外部访问,而 private 成员只能在类内部访问。
5. 继承:允许一个类继承另一个类的特性。这是代码重用和多态性的关键

4.2 组合

在C++中,一个类包含另一个类的对象称为组合(Composition)。这是一种常见的设计模式,用于表示一个类是由另一个类的对象组成的。这种关系通常表示一种"拥有"("has-a")的关系。
#include <iostream>
#include <string>
using namespace std;



class Wheel
{
public:
    string brand;
    int year;
    void wheelPrintInfo();
};
void Wheel::wheelPrintInfo()
{
    cout << "我的轮胎品牌是:" << brand << endl;
    cout << "我的轮胎日期是:" << year << endl;
}

//在C++中,一个类包含另一个类的对象称为组合(Composition)
class Car{ //汽车“类”
    
public:
    string color; //颜色
    string brand; //品牌
    string type; //车型
    int year; //年限
    Wheel wl;
    Wheel *pwl;
    
    //函数指针,指向车介绍函数
    //其实也是成员数据,指针变量,指向函数的变量,并非真正的成员函数
    //保存某个函数地址的变量
    void (*printCarInfo)(string color,string brand,string type, int year);
    void (*carRun)(string type); //函数指针,指向车运行的函数
    void (*carStop)(string type); //函数指针,执行车停止的函数
    
    void realPrintCarInfo();//声明成员函数
};

void Car::realPrintCarInfo(){
    string str = "车的品牌是:" + brand
            + ",型号是: " + type
            + ",颜色是:" + color
            + ",上市年限是:" + std::to_string(year);
    cout << str << endl;
}
void bwmThreePrintCarInfo(string color,string brand,string type, int year)
{
    string str = "车的品牌是:" + brand
            + ",型号是: " + type
            + ",颜色是:" + color
            + ",上市年限是:" + std::to_string(year);//通过std::tostring()函数,将整型数转化成字符串
    cout << str << endl;
}
void A6PrintCarInfo(string color,string brand,string type, int year)
{
    string str = "车的品牌是:" + brand
            + ",型号是: " + type
            + ",颜色是:" + color
            + ",上市年限是:" + std::to_string(year);
    cout << str << endl;
}
int main()
{
    system("chcp 65001 > nul");//强制控制台用UTF-8编码
    Car BWMthree;
    BWMthree.color = "白色";
    BWMthree.brand = "宝马";
    BWMthree.type = "3系";
    BWMthree.year = 2023;
    BWMthree.wl.year = 2023;
    BWMthree.wl.brand = "米其林";
    BWMthree.wl.wheelPrintInfo();
    BWMthree.printCarInfo = bwmThreePrintCarInfo;
    BWMthree.printCarInfo(BWMthree.color,BWMthree.brand,BWMthree.type,BWMthree.year);
    BWMthree.realPrintCarInfo();
    
    Car *AodiA6 = new Car();
    AodiA6->color = "黑色";
    AodiA6->brand = "奥迪";
    AodiA6->type = "A6";
    AodiA6->year = 2008;
    AodiA6->pwl = new Wheel();
    AodiA6->pwl->year = 2024;
    AodiA6->pwl->brand = "kk牌";
    AodiA6->pwl->wheelPrintInfo();
    AodiA6->printCarInfo = A6PrintCarInfo;
    AodiA6->printCarInfo(AodiA6->color,AodiA6->brand,AodiA6->type,AodiA6->year);
    AodiA6->realPrintCarInfo();
    return 0;
}

5. 权限修饰

5.1 基本介绍

• C++中的访问权限主要分为三种:public,privateprotected。这些权限决定了类成员(包括数据成员和成员函数)的可访问性。以下是一个总结表格,说明了在不同情况下这些权限如何应用:
访问权限 类内部 同一个类对象 子类 类外部
public
✔️ 可访问
✔️ 可访问
✔️ 可访问
✔️ 可访问
private
✔️ 可访问
不可访问
不可访问
不可访问
protected
✔️ 可访问
不可访问
✔️ 可访问
不可访问
• 使用权限(如public,privateprotected)C++中是一种关键的封装手段,它们旨在控制对类成员的访问。下面是一个表格,总结了使用权限的主要好处和潜在缺点:
好处/缺点 描述
好处
封装性
通过隐藏类的内部实现(私有和受保护成员),提高了代码的安全性和健壮性。
接口与实现的分离
公开接口(公开成员)与私有实现分离,有助于用户仅关注于如何使用类而不是如何实现。
易于维护
修改类的内部实现不会影响使用该类的代码,从而降低了维护成本。
控制读写访问
通过设置访问权限,可以精确控制类成员的读写访问。
继承的灵活性
protected成员在派生类中是可访问的,使得继承更加灵活
缺点
增加复杂性
过度使用或不当使用权限可能导致代码结构复杂,难以理解。
测试难度
私有成员的测试比公共成员更困难,因为它们不能从类的外部访问。
灵活性降低
过于严格的封装可能限制了某些有效的用法,降低了灵活性。
可能导致紧耦合
过多依赖friend类或函数可能导致类之间的耦合过紧。

6. 引用

• 引用是变量的一个别名,也就是说,它是某个已存在变量的另一个名字。
• 一旦把引用初始化为某个变量,就可以使用该引用名称或者变量名称来指向变量。
• 在C语言中,一个数据对应一个内存,通过由一个变量名来访问这个内存空间的数据,叫做直接访问,相对直接访问,有个间接访问的说法,叫做指针。
• 而引用相当于又给这个内存中的数据提供了一个新的变量名,
• 这个变量名功能比传统变量名更特殊,是直达地址的。

6.1 和指针的区别

• 引用很容易与指针混淆,它们之间有三个主要的不同:
        • 不存在空引用。引用必须连接到一块合法的内存。
        • 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
        • 引用必须在创建时被初始化。指针可以在任何时间被初始化。
        • 官方没有明确说明,但是引用确实不是传统意义上的独立变量,它不能“变嘛 ,试想变量名称是变量附属在内存位置中的标签,您可以把引用当成是变量附属在内存位置中的第二个标签。因此,您可以通过原始变量名称或引用来访问变量的内容。例如:
int i = 17; int* p = &i; *p = 20;
• 可以为 i 声明引用变量,如下所示:
int& r = i;
double& s = d;
• 在这些声明中,&读作引用。因此,第一个声明可以读作 "r是一个初始化为i的整型引用",第二个声明可以读作 "s是一个初始化为ddouble类型引用"

6.2 引用作为函数参数

#include <iostream>
using namespace std;
// 函数声明
void swap(int& x, int& y);
int main ()
{
    system("chcp 65001 > nul");//强制控制台用UTF-8编码
    // 局部变量声明
    int a = 100;
    int b = 200;
    cout << "交换前,a 的值:" << a << endl;//100
    cout << "交换前,b 的值:" << b << endl;//200
    /* 调用函数来交换值 */
    swap(a, b);
    cout << "交换后,a 的值:" << a << endl;//200
    cout << "交换后,b 的值:" << b << endl;//100
    return 0;
}
// 函数定义
void swap(int& x, int& y)
{
    int temp;
    temp = x; /* 保存地址 x 的值 */
    x = y; /* 把 y 赋值给 x */
    y = temp; /* 把 x 赋值给 y */
    return;
}

6.3 引用作为返回值

• 当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边。 例如:
#include <iostream>

using namespace std;

double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};

double& setValue(int i){
    double& ref = vals[i];
    return ref;//返回一个指向ref返回值的隐式指针
}



int main()
{
    system("chcp 65001 > nul");//强制控制台用UTF-8编码
    cout << "改变前的值" << endl;
    for ( int i = 0; i < 5; i++ ){
        cout << vals[i] << endl;
    }
    setValue(2) = 37.5;
    setValue(4) = 101.5;

    cout << "改变后的值" << endl;
    for ( int i = 0; i < 5; i++ ){
        cout << vals[i] << endl;
    }

    return 0;
}
• 补充:当返回一个引用时,要注意被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐