C++学习资料整合

Makefile是C++项目中的关键工具,它自动化编译过程,管理依赖性,避免冗余编译,支持模块化和定制命令,以及参数化构建,提升开发效率和构建一致性。
摘要由CSDN通过智能技术生成

侵权删

相关岗位需求

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

学习网站

C++
C++自救指南
零声
经典面试题
音视频开发
学习资料汇总

学习资料

1.Makefile

Makefile 是一个包含了一组指令的文件,这些指令用于自动化编译和构建软件项目的过程。它是 make 工具的配置文件,make 是一个广泛使用的工具,旨在简化和管理编译过程。Makefile 的主要作用和特点包括:

1.1. 自动化构建

Makefile 可以自动执行编译整个项目的一系列命令,这包括编译源代码文件、链接生成可执行文件以及其他构建相关的任务。

1.2. 依赖性管理

Makefile 可以定义文件之间的依赖关系。例如,它可以指定一个对象文件依赖于特定的源代码文件。如果源代码文件被修改了,make 会重新编译这个对象文件以及依赖于它的任何其他文件。

1.3. 避免不必要的重编译

make 工具通过检查文件的时间戳来决定是否需要重新编译文件。只有当依赖文件(如源文件)的时间戳比目标文件(如对象文件)更新时,make 才会执行编译命令。这可以显著减少构建时间。

1.4. 模块化构建

在大型项目中,Makefile 允许项目被划分为多个较小的模块,每个模块都可以有自己的 Makefile。这样可以更好地管理复杂项目的构建过程。

1.5. 定制命令

Makefile 允许开发者自定义编译和链接命令,以及执行其他任何构建前后需要执行的脚本或命令。这包括清理项目、生成文档、打包软件等。

1.6. 参数化构建

Makefile 可以包含可配置的变量,使得构建过程可以根据不同的环境或配置参数而有所不同。例如,可以有针对调试和发布的不同编译选项。

7. 广泛应用

Makefile 不仅用于 C/C++ 项目,还可以用于其他语言和类型的项目,如 Java、Python 或任何需要自动化步骤的项目。
总的来说,Makefile 是一个强大的工具,可以帮助软件开发者和工程师自动化和简化编译过程,提高效率,确保构建的一致性和可重复性。

2.Linux

之前对Linux的接触就是搭建深度学习环境的服务器了,一直听同学说在Linux下的视频编解码速度会快点儿,所以现在也记录一下自己学习Linux的一些入门笔记。

主要优势

  1. 开源性

    • Linux 是一个开源操作系统,这意味着其源代码是可公开获取的,用户和开发者可以自由地修改和分发代码。这种开放性促进了创新和快速的问题解决。
  2. 成本效益

    • 大多数 Linux 发行版可以免费获取和使用,这对于预算有限的个人和企业来说是非常有吸引力的。相比之下,Windows 许可证通常需要付费,尤其是在企业环境中。
  3. 安全性

    • Linux 通常被认为比 Windows 更安全。它的权限和用户角色分离做得更加严格,加上有活跃的社区持续监控和修补安全漏洞。此外,Linux 的用户群较小,使得它不太成为恶意软件的主要目标。
  4. 稳定性和可靠性

    • Linux 系统以其高稳定性和可靠性而闻名,非常适合长时间运行的服务器和系统。Linux 系统不需要经常重启来恢复性能,而 Windows 系统更新后通常需要重启。
  5. 灵活性和定制性

    • Linux 提供了极高的定制性,用户可以控制几乎所有的系统方面,包括使用不同的图形用户界面或完全无界面。此外,用户可以选择只安装他们需要的软件和服务,从而优化系统性能和资源使用。
  6. 多平台支持

    • Linux 能够运行在各种硬件上,从个人电脑、Mac 电脑到超级计算机甚至是嵌入式系统,如树莓派。这使得 Linux 在不同的技术领域都有广泛的应用。
  7. 命令行接口(CLI)

    • Linux 的命令行接口是一个功能强大的工具,对开发者尤其有用。它允许用户快速执行复杂的任务,而无需图形用户界面。
  8. 支持开源软件

    • Linux 与广泛的开源软件生态系统紧密集成,为用户提供了丰富的软件选择,这些软件大多数是免费可用的。
  9. 社区支持

    • Linux 拥有一个非常活跃和支持性强的社区。无论是通过论坛、IRC 频道还是邮件列表,用户都可以获得免费的支持和建议。
  10. 适合编程和开发

    • Linux 提供了多种编程语言和工具的原生支持。它是开发者的首选环境,特别是对于服务器和数据库管理、网站和软件开发等领域。

常见版本

  1. Ubuntu

    • Ubuntu 是最受欢迎的 Linux 发行版之一,以其用户友好性和广泛的社区支持而闻名。它是基于 Debian 的,提供了稳定的桌面、服务器和云计算版本。Ubuntu 的用户界面整洁,安装和配置过程简单,非常适合 Linux 新手。
    • ubuntu清华镜像文件下载 企业用18.04较多
  2. Debian

    • Debian 是一个非常稳定的系统,以其庞大的软件仓库和严格的软件包管理策略而著称。它是许多其他 Linux 发行版的基础,包括 Ubuntu 和 Linux Mint。Debian 适合那些寻求稳定性和安全性的高级用户和开发者。
  3. Fedora

    • Fedora 是一个创新型的操作系统,专注于集成最新的自由和开源软件。它与 Red Hat Enterprise Linux(RHEL)紧密相关,并经常作为企业级产品的测试平台。Fedora 对于那些希望体验最新技术的用户来说是理想的选择。
  4. CentOS(红帽就是这个的企业版)

    • CentOS(社区企业操作系统)是 RHEL 的一个免费版本,完全兼容 RHEL 的二进制。它主要用于服务器和在商业环境中非常受欢迎,因为它提供了企业级的稳定性而无需额外的许可费用。2021年,CentOS 的开发方向发生了变化,推出了 CentOS Stream,作为 RHEL 的上游(即测试和开发版)。
  5. Arch Linux

    • Arch Linux 是为高级用户设计的,它提倡 KISS 原则(“保持简单,愚蠢”)。它的特点是滚动更新和用户完全控制安装过程。Arch 也以其广泛的文档和社区支持而闻名,适合那些愿意深入学习 Linux 工作方式的用户。
  6. Linux Mint

    • Linux Mint 是基于 Ubuntu 的另一个非常用户友好的发行版,特别注重提供一个完整的“开箱即用”的体验,包括所有必要的插件、编解码器和支持软件。它的界面和菜单布局让从 Windows 过渡来的用户感觉更为自然和容易上手。
  7. openSUSE

    • openSUSE 是一个德国发起的项目,提供两种版本:Tumbleweed(滚动更新)和 Leap(定期发布)。它以其易于配置的 YaST 管理工具和高度的可配置性而受到好评。openSUSE 适合开发者和专业用户。

VMware

新版安装方法 这个博客写的很全

VSCODE

连接服务器

学习笔记

常量指针 指针常量

在C++中,const关键字用来定义常量值,即这些值一旦初始化后就不可被改变。关于const在指针中的使用,确实有点复杂,因为它可以放在不同的位置以影响指针和指针指向的数据。

  1. const int* p1 = &a;

    • 这里,const修饰的是int,意味着p1是一个指向const int的指针。你不能通过p1修改它指向的值,即*p1是不可修改的。但是,你可以改变p1本身的值,即你可以让p1指向另一个整数。
    • 示例:*p1 = 5; // 错误,不能修改p1指向的值
    • 示例:p1 = &b; // 正确,可以改变指针指向
  2. int* const p2 = &a;

    • 这里,const修饰的是整个指针p2,使得p2成为一个常量指针。p2必须始终指向被初始化的地址(这里是a的地址),你不能改变指针的指向,但可以通过p2来修改它所指向的值。
    • 示例:p2 = &b; // 错误,不能改变指针本身的指向
    • 示例:*p2 = 5; // 正确,可以修改指针指向的值
  3. const int* const p = &a;

    • 这是一个指向const int的常量指针。这里,第一个const修饰int,表示不能通过p来修改指向的值;第二个const修饰指针p本身,意味着p的指向一旦被初始化后也不能改变。
    • 示例:*p = 5; // 错误,不能修改指向的值
    • 示例:p = &b; // 错误,不能改变指针本身的指向

结构体

#include<iostream>
#include<string>
#include<ctime>
using namespace std;
struct Student
{
	string name;
	int age;
	int score;
};
struct Teacher
{
	string name;
	struct Student sArry[5];
};
void inPutInformation(struct Teacher tArry[], int len)
{
	string Name = "ABCDE";
	for (int i = 0; i < len; i++)
	{
		tArry[i].name = "Teacher_";
		tArry[i].name += Name[i];
		for (int j = 0; j < 5; j++)
		{
			tArry[i].sArry[j].name = "Student_";
			tArry[i].sArry[j].name += Name[j];
			int random = rand()% 60 +40;
			tArry[i].sArry[j].score = random;
		}
	}
}
void printInformation(struct Teacher tArry[],int len)
{
	for (int i = 0; i < len; i++)
	{
		cout << "老师的姓名:" << tArry[i].name << endl;
		for (int j = 0; j < 5; j++)
		{
			cout << "\t学生的姓名:" << tArry[i].sArry[j].name << "考试分数:" << tArry[i].sArry[j].score << endl;
		}
	}
}
int main(void)
{	
	srand((unsigned int)time(NULL));
	struct Teacher tArry[3];
	int len = sizeof(tArry) / sizeof(tArry[0]);
	inPutInformation(tArry,len);
	printInformation(tArry,len);
	system("pause");
	return 0;
}

内存分区模型

#include <iostream>

// 全局变量,存放在全局区,程序启动时创建,程序结束时销毁
int globalVariable = 10;

// 静态变量,同样存放在全局区,程序启动时创建,程序结束时销毁
static int staticVariable = 20;

// 常量,存放在全局区的只读部分,程序启动时创建,程序结束时销毁
const int constant = 30;

// 函数,存放在代码区,程序加载时创建,程序结束时销毁
void function(int arg) {
    // 局部变量,存放在栈区,函数调用时创建,函数返回时销毁
    int localVariable = arg;

    std::cout << "Local variable on stack: " << localVariable << std::endl;

    // 动态分配的内存,存放在堆区,手动创建,手动销毁
    int* heapVariable = new int(40);

    std::cout << "Heap variable: " << *heapVariable << std::endl;

    // 释放堆区内存,释放后销毁
    delete heapVariable;
}

int main() {
    std::cout << "Global variable: " << globalVariable << std::endl;
    std::cout << "Static variable: " << staticVariable << std::endl;
    std::cout << "Constant: " << constant << std::endl;

    // 调用函数,参数传递过程中会创建副本在栈上,函数返回后这些副本销毁
    function(50);

    return 0;
}

前缀自增和后缀自增

前缀自增 operator++()

在前缀自增中,函数返回类型为 MyClass&,即返回的是一个对当前对象的引用。这是因为前缀自增的目的是先增加对象的值,然后直接使用增加后的对象。通过返回引用,我们避免了不必要的对象复制,同时允许自增表达式可以作为左值使用。

MyClass& operator++()
{
++m_val; // 首先增加m_val的值
return *this; // 然后返回当前对象的引用
}

在这里,*this 是对调用该方法的对象的引用,因此这个方法直接修改对象的状态并返回修改后的对象。

后缀自增 operator++(int)

后缀自增的情况稍微复杂一些。这里的返回类型是 MyClass 而不是 MyClass&。这意味着返回的是当前对象的一个副本,而非引用。后缀自增的行为是:首先将当前对象的状态保存到一个临时对象中,然后增加当前对象的值,最后返回刚才保存的临时对象(即自增前的状态)。

MyClass operator++(int)
{
    MyClass temp = *this;  // 创建当前对象的副本
    ++m_val;               // 增加当前对象的m_val值
    return temp;           // 返回增加前的副本
}

在这个过程中,temp 是在自增操作之前的对象的一个副本。当你执行 b = a++; 时,a 的值首先被保存到 temp 中,然后 am_val 被增加,最后返回 temp。这意味着 b 将接收到自增前的 a 的值。

关键差异

  • 返回类型:前缀自增返回一个引用(MyClass&),因此更高效;后缀自增返回一个值(MyClass),涉及到对象的复制。
  • 自增操作与返回值:前缀自增返回的是增加后的对象,可以用作左值;后缀自增返回的是增加前的对象的副本,不能用作左值。

这些差异确保了每种操作符的行为符合预期,即前缀自增立即反映变化,后缀自增提供了操作前的状态。希望这能帮助你更好地理解这两种操作的实现和使用场景。

作用域

在C++中,:: 是作用域解析操作符,它用于指定变量、函数、或类型名称的作用域。这个操作符非常有用,尤其是在处理多重继承、命名空间或任何可能引起名称冲突的情况下。

作用域解析操作符(::)

1. 访问全局变量

当局部变量与全局变量同名时,可以使用 :: 前缀来访问全局变量。

int value = 5; // 全局变量

void function() {
    int value = 10; // 局部变量
    cout << value << endl;       // 输出10,局部变量
    cout << ::value << endl;     // 输出5,全局变量
}
2. 类作用域

如果派生类(子类)中有与基类(父类)中同名的成员,可以通过作用域解析操作符来访问基类中被遮蔽的成员。

class Base {
public:
    int m_A = 1;
};

class Derived : public Base {
public:
    int m_A = 2;
};

void function() {
    Derived obj;
    cout << obj.m_A << endl;           // 输出2,访问派生类的m_A
    cout << obj.Base::m_A << endl;     // 输出1,访问基类的m_A
}
3. 类静态成员

可以使用类名和作用域解析操作符来访问类的静态成员,无需创建类的实例。

class MyClass {
public:
    static int value;
};

int MyClass::value = 100;

void function() {
    cout << MyClass::value << endl;  // 访问静态成员
}

作用域概念的重要性

  • 避免命名冲突:当多个标识符可能在不同的作用域中有相同的名称时,作用域解析操作符提供了一种方法来明确指定哪一个标识符被使用。
  • 明确意图:它清晰地表明程序员的意图,使代码更易于理解和维护。
  • 访问控制:它允许程序员在继承中明确地访问基类中的成员,即使在派生类中存在同名成员。
  • 代码组织:通过命名空间或类作用域使用作用域解析操作符,可以更好地组织代码,使其结构清晰。

C++中的作用域解析操作符是一种强大的语言特性,它提供了访问和控制代码不同部分的能力,是处理复杂系统时不可或缺的工具。

static 修饰类成员变量和函数

静态成员变量 (static int count):
静态成员变量是类级别的变量,而不是对象级别的变量。它们在所有对象之间共享。
静态成员变量必须在类外进行定义和初始化,否则会导致链接错误。

静态成员函数 (static void foo()):
静态成员函数可以访问静态成员变量,但不能访问非静态成员变量和非静态成员函数(因为它们没有 this 指针)。
静态成员函数可以直接通过类名调用,而无需创建对象。

#include <iostream>
using namespace std;

class MyClass {
public:
    static int count;  // static 修饰类成员变量
    static void foo() {  // static 修饰类成员函数
        cout << count << endl;
    }
};

// 类外定义和初始化静态成员变量
int MyClass::count = 0;

int main() {
    // 访问静态成员变量
    MyClass::count = 10;
    
    // 调用静态成员函数
    MyClass::foo();  // 输出 count 的值

    return 0;
}

分析 MyClass::count; 和 MyClass::foo();
MyClass::count:

这是对静态成员变量 count 的访问。因为 count 是静态的,它不属于某个特定对象,而是属于整个类。
通过 MyClass::count 可以读取或修改 count 的值。
MyClass::foo():

这是对静态成员函数 foo 的调用。静态成员函数可以通过类名直接调用,而不需要创建类的实例。
在 foo 函数中,会输出静态成员变量 count 的值。
输出
根据上述代码,以下是执行过程及输出结果:

首先在 main 函数中,将 MyClass::count 赋值为 10。
然后调用 MyClass::foo(),该函数会输出 count 的值。
因此,程序的输出将是10

为什么需要字节对齐

因为如果不对数据存储进行适当的对齐,可能会导致存取效率降低。

例如,有些平台每次读取都是从偶数地址开始。如果一个 int 类型(假设为 32 位系统)存储在偶数地址开始的位置,那么一个读周期就可以读取这 32 位。
但如果存储在奇数地址开始的位置,则需要两个读周期,并将两次读取的结果的高低字节拼凑才能得到这 32 位数据。
显然这会显著降低读取效率。

以下是字节对齐的一些基本规则:

  1. 自然对齐边界
    对于基本数据类型,其自然对齐边界通常为其大小。

例如,char 类型的自然对齐边界为 1 字节,short 为 2 字节,int 和 float 为 4 字节,double 和 64 位指针为 8 字节。具体数值可能因编译器和平台而异。

  1. 结构体对齐
    结构体内部的每个成员都根据其自然对齐边界进行对齐。

也就是可能在成员之间插入填充字节。

结构体本身的总大小也会根据其最大对齐边界的成员进行对齐(比如结构体成员包含的最长类型为int类型,那么整个结构体要按照4的倍数对齐),以便在数组中正确对齐。

  1. 联合体对齐
    联合体的对齐边界取决于其最大对齐边界的成员。联合体的大小等于其最大大小的成员,因为联合体的所有成员共享相同的内存空间。

  2. 编译器指令
    可以使用编译器指令(如 #pragma pack)更改默认的对齐规则。这个命令是全局生效的。这可以用于减小数据结构的大小,但可能会降低访问性能。

  3. 对齐属性
    在 C++11 及更高版本中,可以使用 alignas 关键字为数据结构或变量指定对齐要求。这个命令是对某个类型或者对象生效的。例如,alignas(16) int x; 将确保 x 的地址是 16 的倍数。

  4. 动态内存分配
    大多数内存分配函数(如 malloc 和 new)会自动分配足够对齐的内存,以满足任何数据类型的对齐要求。

举例说明下:

#include <iostream>

#pragma pack(push, 1) // 设置字节对齐为 1 字节,取消自动对齐
struct UnalignedStruct {
    char a;
    int b;
    short c;
};
#pragma pack(pop) // 恢复默认的字节对齐设置

struct AlignedStruct {
    char a;   // 本来1字节,padding 3 字节
    int b;    //  4 字节
    short c;  // 本来 short 2字节,但是整体需要按照 4 字节对齐(成员对齐边界最大的是int 4) 
              // 所以需要padding 2
   // 总共: 4 + 4 + 4
};

struct MyStruct {
 double a;    // 8 个字节
 char b;      // 本来占一个字节,但是接下来的 int 需要起始地址为4的倍数
              //所以这里也会加3字节的padding
 int c;       // 4 个字节
 // 总共:  8 + 4 + 4 = 16
};

struct MyStruct1 {
 char b;    // 本来1个字节 + 7个字节padding
 double a;  // 8 个字节
 int c;     // 本来 4 个字节,但是整体要按 8 字节对齐,所以 4个字节padding
  // 总共: 8 + 8 + 8 = 24
};


int main() {
    std::cout << "Size of unaligned struct: " << sizeof(UnalignedStruct) << std::endl; 
    // 输出:7
    std::cout << "Size of aligned struct: " << sizeof(AlignedStruct) << std::endl; 
    // 输出:12,取决于编译器和平台
    std::cout << "Size of aligned struct: " << sizeof(MyStruct) << std::endl; 
    // 输出:16,取决于编译器和平台
    std::cout << "Size of aligned struct: " << sizeof(MyStruct1) << std::endl;
     // 输出:24,取决于编译器和平台
    return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值