刚工作菜鸟的小总结1

刚工作菜鸟的小总结1

1. explicit关键字

explicit 用于修饰单参数的构造函数,可以防止隐式类型转换。当一个类的构造函数只有一个参数时,通常我们可以使用隐式类型转换来将参数类型自动转换类的对象。但是,有时候这种隐式类型转换可能会导致意外的行为或者不符合设计意图

通过在单参数的构造函数前添加 explicit 关键字,我们可以禁止该构造函数进行隐式类型转换,只允许显式地进行类型转换。

举个例子:

class MyClass {
public:
    explicit MyClass(int value) : m_value(value) {}
    int getValue() const { return m_value; }
private:
    int m_value;
};

int main() {
    // 使用隐式类型转换
    MyClass obj1 = 42;  // 错误,禁止隐式类型转换

    // 使用显式类型转换
    MyClass obj2(42);  // 正确,显式转换

    int value = obj2.getValue();  // 正确,获取对象的值
    return 0;
}

总结来说,explicit 关键字用于修饰单参数的构造函数,防止隐式类型转换,只允许显式类型转换。

2. initializer_list

initializer_list 是 C++11 引入的一种特殊数据结构,是一个特殊的容器,但存的都只是常量,不可修改,且 initializer_list 不拥有元素的所有权,只能用于传递值。所以 initializer_list 主要用于以下两种场合:

  1. 用于给构造函数传递参数,一次传递多个值;
  2. 用于给函数或算法传递参数;

主要用法如下:

  1. 声明和创建 initializer_list 对象:

     std::initializer_list< T > name {val1, val2, ..., valn};
    

    其中,T 是元素的类型,name 是对象的名称,val1, val2, …, valn 是要传递的值。

  2. 使用 initializer_list 初始化对象:

    class MyClass {
    public:
        MyClass(std::initializer_list values) {
        // 此处可以访问 values 以获取传递的值
        }
    };
    
    MyClass obj = {1, 2, 3, 4};  // 创建 MyClass 对象,并传递 initializer_list 列表
    
  3. 使用范围型 for 循环遍历 initializer_list 中的元素:

    for (auto elem : myInitializerList) {
    // 对每个元素执行操作
    }

3. char data[0](变长数组)

char data[0]是一个变长数组,虽然声明的是一个长度为零的数组,但可以在运行时动态地去给数组分配内存空间。

当把它用在结构体的后面时,该结构体的实例可以动态分配附加长度的内存。这种技术在实现变长结构体时非常有用,如动态字符串结构体等。

struct MyStruct {
    int length;
    char data[0];
};

在这个示例中,data 数组被用作动态字符串,length 用于记录字符串的长度。通过动态分配内存,可以根据实际需要存储不同长度的字符串。柔性数组允许将更多的数据附加到结构体实例的末尾,使结构体的大小变得可变。这对于一些需要动态增长的结构体非常有用。

4. #pragma

#pragma是一个C/C++的编译预处理指令,用于向编译器提供特定的编译指示。它可以被用来控制编译过程中的各种行为和选项。

常见的#pragma指令包括:

  1. #pragma once: 用于确保头文件只被编译一次,以避免重复包含。

  2. #pragma comment(lib, “libname”): 用于在编译过程中指定链接的库文件。这样做的好处是,在编译时无需手动指定要链接的库文件,使得代码更加简洁和可移植。

  3. #pragma warning: 用于控制编译器警告的行为,可以开启、关闭或修改警告等级。

  4. #pragma pack: 用于控制结构体成员的字节对齐方式,可以调整内存布局。

    默认情况下,编译器会根据特定的规则进行字节对齐,以提高内存访问的效率。例如,对于某些架构,编译器可能要求结构体成员在内存中的地址必须是4的倍数或8的倍数。这样做可以提高内存访问的速度,但会浪费一些空间。

    而#pragma pack(1)则是告诉编译器使用紧凑的字节对齐方式,也就是按照成员的声明顺序依次分配内存,不进行任何对齐。这样可以减少内存的浪费,但可能会导致内存访问效率降低。

  5. #pragma message: 用于在编译时输出自定义的提示信息。

  6. #pragma region / #pragma endregion: 用于将一段代码块分组,方便折叠和展开。

5. 函数后加了等号

  1. static void *(*cJSON_malloc)(size_t sz) = malloc;
    这行代码定义了一个函数指针 cJSON_malloc,该指针指向一个函数,该函数具有返回类型为 void*,参数为 size_t sz 的函数签名。并且将其初始化为指向标准库函数 malloc。作用就是将 malloc 函数赋值给 cJSON_malloc 函数指针,以便在后续的代码中可以通过 cJSON_malloc 来调用分配内存的函数。

  2. DeleteCopy(const DeleteCopy&) = delete;
    void operator=(const DeleteCopy&) = delete;
    这段代码展示了C++中如何禁用拷贝构造函数和赋值运算符函数,即使在类的定义中使用了 DeleteCopy 类型的变量,也无法进行拷贝构造和赋值操作。

    这是一种常见的技巧,常用于实现单例模式或者确保某些类不被拷贝。通过将拷贝构造函数和赋值运算符函数声明为 delete,可以防止编译器自动生成默认的拷贝构造函数和赋值运算符函数。

    这样,在编译时如果尝试进行拷贝构造或者赋值操作,会触发编译错误。

  3. DeleteCopy() = default;
    ~DeleteCopy() = default;
    这段代码展示了在类的定义中使用 = default 来声明默认的构造函数和析构函数。通过这种方式,可以显式地声明编译器生成默认构造函数和析构函数。

    使用 = default 的主要目的是为了显式地指定编译器生成默认的构造函数和析构函数,以确保它们的存在,并且是被允许使用的。这在某些情况下可能会被需要,例如当需要自定义其他特殊成员函数,但仍希望保留默认构造函数和析构函数的功能时。

    需要注意的是,默认构造函数和析构函数的声明为 = default 通常与其他特殊成员函数(如拷贝构造函数、移动构造函数、拷贝赋值运算符、移动赋值运算符等)的禁用或自定义实现配合使用,以满足特定的类设计需求。

  4.   class DeleteCopy
      {
      public:
          DeleteCopy(const DeleteCopy&) = delete;
          void operator=(const DeleteCopy&) = delete;
    
      protected:
          DeleteCopy() = default;
          ~DeleteCopy() = default;
      };
    

    DeleteCopy 的派生类无法进行拷贝构造和拷贝赋值操作。这也是一种技巧,只需先写 DeleteCopy 类,后面有其他类需要禁用拷贝构造和拷贝赋值的话,直接继承 DeleteCopy 类,就可以实现,从而达到代码复用的效果。

6. Shell

Shell是一种命令行解释器,它为用户提供了一种与操作系统内核交互的方式。Shell通常提供了一些简单的命令和工具,用于执行常见的任务,比如文件操作、进程管理、网络通信等等。Shell还支持脚本编程,使得用户可以将多个命令组合在一起,实现更加复杂的任务。

在Unix和Linux系统中,有许多不同的Shell可供选择,最常见的是Bash Shell(Bourne-Again SHell),它是Linux、macOS和大多数Unix发行版的默认Shell。其他常见的Shell包括Zsh(Z Shell)、Fish Shell、KornShell等等。

7. source的作用

在命令行中,source 是一个 Shell 内置命令,用于读取指定的脚本文件,并将其内容作为当前 Shell 环境下的命令执行。它常用于加载环境变量,设置别名和自定义函数等操作。

具体来说,使用 source 命令可以使 Shell 在当前进程中执行指定的脚本文件,并把该脚本文件中所定义的变量、函数等添加到当前 Shell 环境中。这使得我们可以方便地在当前会话中使用该脚本文件所定义的变量、函数和别名,而不是在一个新的子进程中执行该脚本文件,从而使得所有定义的变量、函数和别名都丢失。

例如,在 Bash Shell 中,可以使用以下语法来执行指定的脚本文件:

source filename

或者使用简化形式:

. filename

其中,filename 是要执行的脚本文件名,它必须是一个可执行的 Shell 脚本文件。使用 source 命令执行脚本文件时,不需要指定该文件的执行权限,因为它不是在一个新的子进程中执行脚本文件,而是在当前 Shell 环境中执行脚本文件。

8. 互斥锁在调用完自动解锁的小技巧

#include

class M_Mutex{
private:
      pthread_mutex_t mutex;
public:
      M_Mutex(){
             pthread_mutex_init(&mutex,NULL);
      }
      ~M_Mutex(){
             pthread_mutex_destroy(&mutex);
      }
      void lock(){
             pthread_mutex_lock(&mutex);
      }
      void unlock(){
             pthread_mutex_unlock(&mutex);
      }
      pthread_mutex_t* getPthreadMutex(){
             return &mutex;
      }
};

//当获得该类的对象时就上锁,析构时就解锁
class M_MutexGuard(){
private:
      M_Mutex& mutex;
public:
      explicit M_MutexGuard(M_Mutex& mutex):mutex(mutex){
              mutex.lock();
      }
      ~M_MutexGuard(){
              mutex.unlock();
      }
};

例如:

```cpp
void func(){
		M_Mutex mutex;
		M_MutexGuard(mutex);
		....
}

在调用该函数时自动就会上锁,当执行完,就会自动解锁。

9. noexcept关键字

在C++中,可以在构造函数后面加上 noexcept 关键字来指示该构造函数不会抛出异常。这样可以提供编译器有关构造函数的额外信息,以便进行优化和代码生成。

class MyClass {
public:
    MyClass() noexcept; 
    // 其他成员函数
private:
    // 数据成员
};

使用 noexcept 的主要优点是可以改善性能,并使代码更加可靠。当一个构造函数被声明为 noexcept 时,编译器可以利用这个信息进行一些优化,例如省略某些异常处理代码或者避免不必要的栈展开操作。此外,noexcept 还可以让使用该构造函数的代码更清晰明确,因为调用 noexcept 构造函数的代码可以假设构造过程不会抛出异常。

在使用 noexcept 时需要注意以下几点:

  1. noexcept 关键字只能出现在函数声明和定义中的尾部。
  2. 如果一个类的析构函数或移动构造函数(Move Constructor)是 noexcept 的,则该类的默认构造函数(如果有)也会被隐式地声明为 noexcept。
  3. noexcept 并不能保证构造函数绝对不会抛出异常,如果在 noexcept 构造函数中调用了其他可能抛出异常的函数或发生了未能处理的系统错误,仍然可能导致异常被抛出。

10. 引用头文件的先后问题

在 C++ 中引用库时,一般是需要先引用系统的头文件再引用第三方库的头文件。这是因为系统的头文件中可能包含了某些必要的定义、声明或宏,供第三方库的头文件使用。如果没有先引用系统的头文件,就直接引用第三方库的头文件,可能会导致编译错误或者无法正确使用第三方库。

另外,还需要注意的是,引用头文件的顺序也可能对编译结果产生影响。因为头文件中可能存在相互依赖的情况,所以头文件的顺序可能会影响到符号定义的可见性和重复定义等问题。一般情况下,遵循以下几个原则可以避免潜在的问题:

  1. 先引用系统的头文件,然后再引用第三方库的头文件。

  2. 在同一个源文件中,按照依赖关系从上到下的顺序进行引用。如果 A 头文件依赖于 B 头文件,则应该先引用 B 头文件,再引用 A 头文件。

  3. 对于同一个库的多个头文件,按照它们之间的依赖关系进行引用,确保每个头文件的依赖都已经被包含。

举一个例子,假设当前文件是test.cpp

#include   //系统的头文件
#include "cJSON.h"   //第三方的头文件
#include "test.h"    //自己的头文件

有这样的一个顺序。

11. 虚函数和纯虚函数

虚函数(Virtual Function)和纯虚函数(Pure Virtual Function)是面向对象编程中的重要概念,它们有以下区别:

  1. 虚函数:

    • 虚函数是在基类中声明并使用 virtual 关键字修饰的成员函数。
    • 虚函数可以被派生类重写(覆盖),实现多态性。
    • 如果派生类没有重写基类中的虚函数,那么将调用基类中的虚函数。
    • 基类中的虚函数可以有默认实现,如果派生类没有重写它,则会使用基类中的默认实现。
  2. 纯虚函数:

    • 纯虚函数是在基类中声明并使用 virtual 关键字修饰的函数,并且没有提供实现。
    • 纯虚函数相当于占位符,要求派生类必须实现它,否则派生类也将变成抽象类。
    • 使用纯虚函数将基类定义为抽象类,抽象类无法实例化,只能作为接口给其他具体派生类使用。
    • 派生类必须提供纯虚函数的实现,才能被实例化。

虚函数和纯虚函数都是实现面向对象编程中的多态性。虚函数通过动态绑定,在运行时确定要调用的实际函数,实现运行时多态性。而纯虚函数则要求派生类必须提供实现,强制子类遵循接口定义,实现编译时多态性。在使用时,需要根据具体需求来选择使用虚函数还是纯虚函数。

12. 占位符

在 C++ 中,占位符主要用于以下两个方面:

  1. 函数占位符:
    在函数定义或函数调用中,可以使用占位符作为参数,表示该参数的值在后续的使用中会提供。
    例如,在函数定义中可以使用以下形式的占位符:void functionName(int, int);,其中的占位符表示参数的类型。
    在函数调用中,可以有类似 functionName(10, _); 的形式,其中的 _ 表示该位置的参数值由调用方提供。

  2. Lambda 表达式占位符:
    在使用 Lambda 表达式时,可以使用占位符 _ 来表示未指定的参数。
    例如,可以有类似 [&](int x) { return x + _; } 的 Lambda 表达式,其中的 _ 表示待提供的参数。

需要注意的是,C++ 中的占位符并非语言内置的特性,而是一种约定俗成的用法。在实际编码中,占位符的具体实现可以依赖于不同的库或者约定。

13. 网络黏包

网络黏包(Network Packet Congestion)是指在网络传输过程中,由于数据发送和接收的不同步,导致多个数据包被粘合在一起,形成一个较大的数据块,使得接收方无法准确地还原出原本的数据包。

网络黏包可能发生在各种网络环境下,特别是在高负载、高并发的情况下更容易出现。它可能由以下几个原因引起

  1. 数据写入操作不及时:发送方在发送数据时,可能会将多个数据包连续地写入到网络缓冲区中,而接收方由于某些原因未能及时处理,导致多个数据包被黏在一起。

  2. TCP优化算法:TCP协议为了提高传输效率,可能会对数据进行优化,比如使用 Nagle 算法或延迟确认机制,这些优化算法会导致数据包的延迟发送或合并发送,从而增加了网络黏包的概率。

  3. 网络传输层 MTU 限制:如果发送方的数据包大小超过了网络链路的最大传输单元(Maximum Transmission Unit,MTU),则数据包会被分片发送,接收方需要重新组装这些分片,可能导致黏包的问题。

为了解决网络黏包问题,可以使用以下几种方法

  1. 显式协议约定:在应用层协议中定义数据包的边界,比如在数据包的头部添加长度字段,接收方根据长度字段来切分数据包。

  2. 分隔符:在数据包之间使用特定的分隔符进行分割,接收方根据分隔符来区分数据包。

  3. 数据包序号:为每个数据包分配一个唯一的序号,接收方根据序号来还原数据包。

  4. 使用固定长度的数据包:将所有数据包都固定为相同的长度,如果不足则补充填充,接收方按照固定长度来处理数据包。

  5. 使用专业的网络传输库或框架:一些现代化的网络传输库或框架提供了可靠的数据传输机制,如TCP粘包解决方案、协议缓冲区等,可以减少黏包问题的发生。

14. extern “C”

extern “C” 是一个用于在 C++ 中声明使用 C 语言编写的函数或变量的关键字

在 C++ 中,默认情况下,函数和变量会经过名称修饰(name mangling),以便支持函数重载和命名空间等特性。而 C 语言并不支持这些特性,因此在 C++ 中调用 C 语言编写的函数时,需要使用 extern “C” 声明,告诉编译器按照 C 语言的规则进行链接,即不进行名称修饰。

例如,在 C++ 中如果要调用一个 C 语言编写的函数时,可以将该函数的声明包裹在 extern “C” 块中,如下所示:

extern "C" {
    // C 函数声明
    void myCFunction(int arg);
}

这样,在编译和链接过程中,该函数的名称不会被改变,以符合 C 语言的规则,从而可以正确地调用该函数。

需要注意的是,extern “C” 只能应用于全局函数和全局变量,不能用于类成员函数。如果想要在 C++ 类中调用 C 函数,可以使用中间的静态成员函数或者其他手段来间接调用。

要注意!!!

C 语言不支持函数重载。函数重载是 C++ 的特性,它允许在同一个作用域内定义多个具有相同名称但参数列表不同的函数。编译器根据函数的参数列表来区分不同的重载函数,并进行正确的调用。

而在 C 语言中,函数名是唯一的标识符,不能存在同名但参数列表不同的函数。如果在 C 语言中定义了多个同名的函数,会导致函数重复定义的错误。

15. :: (作用域解析运算符)

在C++中,:: 是作用域解析运算符,它用于指定全局作用域
例如,在Socket编程中,::bind() 函数是一个全局函数,属于操作系统提供的函数库,不属于任何特定的命名空间或类。当调用全局函数时,为了明确指定使用的是全局作用域中的函数而不是任何其他作用域中可能存在的同名函数,我们使用 ::bind() 来解决名称冲突。这样可以确保调用的是全局作用域中的 bind() 函数。

16. 析构函数可以是虚函数吗

析构函数可以是虚函数。在 C++ 中,虚函数是用于实现运行时多态性的一种机制,可以在基类中声明并在派生类中进行重写。虚函数通过动态绑定来确定要调用的实际函数。

当基类指针指向一个派生类对象时,如果基类的析构函数是虚函数,则在 delete 基类指针或基类指针超出作用域时,会调用派生类的析构函数。这样可以确保在删除派生类对象时,能够正确地调用派生类的析构函数,释放派生类特有的资源。

例子如下:

class Base {
public:
    virtual ~Base() {
        // 基类析构函数代码
    }
};

class Derived : public Base {
public:
    ~Derived() {
        // 派生类析构函数代码
    }
};

int main() {
    Base* ptr = new Derived();
    // 使用基类指针指向派生类对象

    delete ptr;
    // 调用派生类的析构函数

    return 0;
}

在上述代码中,Base 类的析构函数被声明为虚函数 virtual ~Base(),而 Derived 类继承自 Base 类,并重写了析构函数 ~Derived()。当使用基类指针 ptr 指向派生类对象时,通过 delete 删除基类指针时,会正确地调用派生类的析构函数 ~Derived()。

17. atomic< T >(原子操作)

atomic< T >是 C++11 标准库提供的一个类型,它是一个原子类型(Atomic type),也称为原子操作,支持多线程并发访问,能够确保所有的操作都是原子性的,不会出现数据竞争和死锁等问题,在多线程编程中非常有用。

它提供了一下几个特性:

  1. 原子性:std::atomic< T > 中的操作是原子的,即对该类型的赋值、读取和比较操作等都是一次性完成的,否则就是没有完成的。

  2. 内存模型:std::atomic< T > 是基于内存模型的,可以保证多线程之间的协作和同步。

  3. 线程安全:std::atomic< T > 是线程安全的,多个线程可以同时对其进行访问,不需要额外的同步机制。

18. 自定义头文件互相包含的问题

在使用自定义头文件时,通常需要将自定义头文件包含在源文件中。如果自定义头文件中引入了其他头文件,那么在包含该自定义头文件时,也会包含进去引入的头文件。

例如,假设有以下文件结构:

// file1.h
#ifndef FILE1_H
#define FILE1_H

#include 

void function1();

#endif

// file2.h
#ifndef FILE2_H
#define FILE2_H

#include "file1.h"

void function2();

#endif

// main.cpp
#include "file2.h"

int main() {
    function2();
    return 0;
}

在上述代码中,file2.h 引入了 file1.h,而 main.cpp 中包含了 file2.h。当编译 main.cpp 时,预处理阶段会先展开 file2.h 的内容,其中会包含 file1.h 的内容,从而 file1.h 中引入的 < iostream> 也会被包含进来。因此,可以正常使用 std::cout 等来自 < iostream> 的功能。

需要注意的是,在实际编程中,为了避免重复包含和依赖循环等问题,通常会使用条件编译指令(如 #ifndef、#define 和 #endif)来保证头文件只被包含一次。这样可以防止重复定义和编译错误。

总之,当你包含一个自定义头文件时,会包含这个头文件中引入的其他头文件,以确保在使用自定义头文件的地方能够正常访问到所需的类型、函数和宏等定义。

19. 关于ifndef、define来避免头文件重复包含

下面是在自定义头文件中使用预处理指令 #ifndef、#define 和 #endif 来保护 < iostream> 头文件的示例:

#ifndef MY_HEADER_FILE_H
#define MY_HEADER_FILE_H

// 包含所需的其他头文件
#include "other_header_file1.h"
#include "other_header_file2.h"

#ifndef IOSTREAM_INCLUDED
#define IOSTREAM_INCLUDED
#include 
#endif

// 头文件其他内容

#endif // MY_HEADER_FILE_H

在以上示例中,MY_HEADER_FILE_H 是一个自定义的宏名称,可以根据需要进行修改。当包含该头文件时,首先会检查是否已经定义了 MY_HEADER_FILE_H 宏,如果没有定义,则执行 #include < iostream> 将 < iostream> 头文件包含进来,并定义 MY_HEADER_FILE_H 宏。这样就可以避免多次包含同一个头文件。

在实际使用中,你可以将上述代码保存在一个独立的头文件中(比如 my_header.h),然后在需要使用 < iostream> 的源文件中通过 #include “my_header.h” 来包含该自定义头文件。

另外,需要注意的是,尽管使用了预处理指令来保护头文件的重复包含,但在编写代码时仍需要注意遵循良好的编程实践,避免在头文件中定义全局变量或直接在头文件中编写大量代码,以免引发其他问题。

20. 为什么要避免在头文件中定义全局变量

实际上,在 C++ 中是可以在头文件中定义全局变量的。但是,这样做可能会引起一些问题,尤其是在多个源文件中包含同一个头文件时。

具体来说,如果将全局变量的定义放在头文件中,并且将该头文件包含在多个源文件中,那么在编译时就会出现多重定义的问题(“multiple definition”)。

例如,假设有以下文件结构:

#ifndef GLOBAL_VAR_H
#define GLOBAL_VAR_H

int g_i;

#endif // GLOBAL_VAR_H

// main1.cpp
#include "global_var.h"

int main() {
    g_i = 0;
    return 0;
}

// main2.cpp
#include "global_var.h"

int main() {
    g_i = 1;
    return 0;
}

在上述代码中,global_var.h 定义了一个全局变量 g_i。然后,main1.cpp 和 main2.cpp 分别包含了 global_var.h 头文件,并分别对 g_i 进行了赋值。

当编译这两个源文件时,就会出现多重定义的错误。这是因为,在编译器将源文件编译为目标文件时,如果多个源文件都包含了同一个头文件,那么就会出现多重定义问题。

为了避免这种问题,通常建议将全局变量的定义放在源文件中,而不是头文件中。在头文件中只需要声明变量即可。例如:

// global_var.h
#ifndef GLOBAL_VAR_H
#define GLOBAL_VAR_H

extern int g_i;

#endif // GLOBAL_VAR_H

// global_var.cpp
#include "global_var.h"

int g_i; // 定义全局变量

// main1.cpp
#include "global_var.h"

int main() {
    g_i = 0;
    return 0;
}

// main2.cpp
#include "global_var.h"

int main() {
    g_i = 1;
    return 0;
}

在上述代码中,global_var.h 头文件声明了全局变量 g_i,而 global_var.cpp 源文件定义了该变量。这样,在编译器编译源文件时,只会对 global_var.cpp 中的全局变量进行一次定义,从而避免了多重定义错误。

21. volatile关键字

在 C++ 中,volatile 是用来修饰变量的关键字。它声明的变量可以被随时改变,且不受编译器优化的影响。

通常情况下,编译器对代码进行优化时会认为变量的值是不会中途被修改的,因此它可能会将一些操作重写成更为高效的形式,如使用缓存或寄存器中的值。但某些情况下,我们需要确保所操作的变量始终从内存读取最新值,而不使用缓存或寄存器中的旧值,这时就需要使用 volatile 符号。

volatile 可以防止编译器将变量的引用优化成寄存器或其他形式的缓存,强制要求每次从内存中读取值,从而确保最新的值被读取。通常情况下,在多线程编程、硬件编程或者并发编程中,都需要使用 volatile 来确保数据的正确性。

需要注意的是,volatile 并不能保证线程安全,只能保证数据内存可见性。如果多个线程同时读写同一个 volatile 变量,仍然需要通过其他机制来保证正确性,例如 mutex、atomic 等。

22. sizeof和strlen的区别

它们的主要区别如下:

  1. sizeof是一个运算符,返回的是变量、数据类型、数组和指针等所占用内存的大小。strlen是一个函数,返回的是字符串的实际长度,不包括字符串结束符\0
  2. 在计算字符串的长度时,sizeof会把\0一起算进去,而strlen是遇到\0就结束计算,并且不包括\0在内。
  3. sizeof是在编译时确定的,结果是一个常量表达式。而strlen是在运行时动态计算字符串长度的,结果根据实际字符串的内容而改变。

需要注意的是,当在计算char*字符串时,sizeof计算的是指针的大小,而strlen计算的才是字符串的长度。例子如下:

#include 
#include 

int main() {
    const char* str = "Hello, World!";
    
    size_t size = sizeof(str);
    size_t length = strlen(str);

    printf("Size of the str: %zu\n", size);     //输出4
    printf("Length of the string: %zu\n", length);  //输出13

    return 0;
}

对于 char* 类型的数据,sizeof 返回的是指针本身的大小,而不是指向的字符串的长度。如果需要获取字符串的长度,应使用 strlen() 函数。 而如果是char[]类型的话,效果会不一样,例子如下:

#include 
#include 

int main() {
    const char str[] = "Hello, World!";
    
    size_t size = sizeof(str);
    size_t length = strlen(str);

    printf("Size of the str: %zu\n", size);         //输出14
    printf("Length of the string: %zu\n", length);  //输出13

    return 0;
}

注意sizeof会把\0计算在内。

23. 新建回话选项(Shell和SSH)

“Shell” 和 “SSH” 是新建会话时的选项,它们代表了不同的连接方式和使用环境。

  1. Shell

    • Shell 选项表示你将在一个本地的命令行环境中进行会话。
    • 当你选择 Shell 选项时,你会进入一个交互式的命令行终端,可以直接在终端中输入命令并执行。
    • 这种方式适用于在本地机器上进行开发、调试和执行命令等操作,不需要远程连接到其他计算机或服务器。
  2. SSH

    • SSH 选项表示你将通过 SSH(Secure Shell)协议连接到远程计算机或服务器进行会话。
    • SSH 是一种加密的网络协议,用于安全地远程登录到其他计算机,并在远程计算机上执行命令。
    • 当你选择 SSH 选项时,你需要提供远程计算机的 IP 地址或主机名、用户名和密码(或密钥)来建立连接。
    • 通过 SSH 连接,你可以在远程计算机上执行命令、管理文件、部署应用程序等。

总结来说,“Shell” 选项适用于在本地机器上进行命令行操作,而 “SSH” 选项适用于通过 SSH 协议连接到远程计算机或服务器进行远程操作。选择哪个选项取决于你想要在哪个环境中进行会话和操作。

24. MVC软件框架

MVC 是一种软件架构模式,它将应用程序分为三个主要组件:模型(Model)、视图(View)和控制器(Controller)。这种分层架构使代码的组织更加清晰,易于维护和扩展。

下面是 MVC 架构中各个组件的功能和作用:

  1. 模型(Model):

    • 模型代表应用程序的数据和业务逻辑。
    • 它负责处理数据的获取、存储、验证、计算和操作等任务。
    • 模型通常包含数据结构、数据库访问、文件操作、网络请求等相关代码。
  2. 视图(View):

    • 视图负责展示用户界面和呈现数据给用户。
    • 它通常是用户界面的一部分,包括了用户所看到的界面元素、布局、样式等。
    • 视图可以根据模型的数据来动态地更新自己,以便向用户显示最新的信息。
  3. 控制器(Controller):

    • 控制器充当模型和视图之间的中介,处理用户的输入和交互。
    • 它接收用户的操作,根据需要更新模型的状态,并将更新后的数据传递给视图进行展示。
    • 控制器还可以处理与模型和视图之间的通信,例如数据的转换、格式化和验证等。

MVC 的核心思想是将应用程序的逻辑分离成三个独立的组件,每个组件都有自己的职责。这种分离使得代码更加模块化、可维护性更高,并且允许不同的组件可以独立地进行开发和测试。此外,MVC 还提供了一种松耦合的架构,使得修改其中一个组件不会对其他组件产生过多的影响。

25.透传指令

透传指令(Transparent Command)是一种通信协议中的概念,用于将特定指令或数据直接传递给下层设备或模块,而无需进行解析或处理

在某些通信协议中,透传指令允许上层设备或系统将原始指令或数据透明地传输到下层设备,下层设备可以直接处理这些指令或数据,而不需要上层设备介入或解析。透传指令的目的是提供一种直接、高效的通信方式,使得上层设备可以与下层设备进行灵活的交互和控制。

透传指令通常用于嵌入式系统中,例如与外部模块(如无线模块、传感器等)进行通信时,上层设备可以通过透传指令直接向下层设备发送指令或数据,下层设备可以根据指令执行相应的操作,然后将结果返回给上层设备。

透传指令的优点是简化了通信协议的设计和实现,减少了通信过程中的数据解析和处理的复杂性。同时,它也提供了更高的灵活性,使得上层设备可以直接控制和配置下层设备的行为。

需要注意的是,透传指令需要在上层设备和下层设备之间进行约定和规范,以确保双方能够正确理解和处理透传的指令或数据。通常会定义一套特定的指令格式、协议规范和通信流程,以确保透传指令的有效性和可靠性。

总结起来,透传指令是一种通信协议中的概念,用于将特定指令或数据直接传递给下层设备或模块,而无需进行解析或处理。它提供了一种直接、高效的通信方式,使得上层设备可以与下层设备进行灵活的交互和控制。

26. 路径 /A/B 和 A/B 的区别

在Linux系统中,/A/B 和 A/B 这两个路径之间的区别在于它们是否以斜杠(/)开头。
如果路径以斜杠(/)开头,则表示该路径是绝对路径,即从根目录(/)开始的完整路径。在这种情况下,路径是相对于根目录的,不受当前工作目录的影响。例如,/A/B 表示从根目录开始的完整路径,A是根目录下的一个目录。如果不以斜杠(/)开头,则表示该目录是相对路径,在这种情况下,路径是相对于当前工作目录的,受当前工作目录的影响。例如 A/B 表示相对于当前工作目录的路径,其中 A 是当前工作目录下的一个目录。

27. tpyename 关键字

在 C++ 中,typename 是一个关键字,它用于告诉编译器,后面的类型名是类型别名或类型。例如,typename Operation::second_argument_type 是在告诉编译器,Operation::second_argument_type 是一个类型。这是必要的,因为编译器在编译时需要知道这个类型。在模板编程中,尤其是使用模板类型特性时,这种用法非常常见。在这种情况下,Operation 可能是一个模板类型,它有一个名为 second_argument_type 的类型成员。通过使用 typename,编译器就能知道这是一个类型,而不是一个静态成员或函数。
如果你省略 typename,编译器可能会将 Operation::second_argument_type 视为一个静态成员或函数,从而导致编译错误。使用 typename 可以确保编译器正确地将其视为一个类型。

28. 临时对象

typename(类型) + () = 临时对象

例如,less< int >() ,这样就是一个临时对象。

29. ~/

~/ 是一个路径表示法,表示当前用户的主目录。
在大多数操作系统中,每个用户都有一个主目录(也称为家目录),用于存储用户的个人文件和配置。主目录通常以用户名命名,并位于特定的位置。例如,在Linux和macOS系统中,主目录通常位于 /home/username 或 /Users/username 目录下,其中 username 是当前用户的用户名。而在Windows系统中,主目录通常位于 C:\Users\username 目录下。

~ 符号是一个常见的缩写,表示当前用户的主目录。因此,~/ 就是指向当前用户主目录的根目录。具体路径取决于操作系统和当前登录用户。

举例来说,如果当前用户的用户名是 john,那么在Linux或macOS系统中,~/ 可能对应的实际路径是 /home/john/;而在Windows系统中,可能是 C:\Users\john\。

30. -I (Linux的命令选项)

I 是 gcc 编译器的选项之一,它用于指定头文件的搜索路径

在编译 C/C++ 代码时,我们通常需要包含一些头文件来引用所需的函数、变量和类型定义。这些头文件可以是标准库的头文件,也可以是自定义的头文件。

-I 选项后面接一个路径参数,表示要添加的头文件搜索路径。编译器在编译过程中会在这些路径下查找所需的头文件。可以通过多次使用 -I 选项来指定多个头文件搜索路径。

例如,假设我们有一个名为 myheader.h 的自定义头文件,并且它位于 /path/to/header 目录下。为了让编译器能够找到并包含这个头文件,我们可以使用 -I 选项来指定搜索路径:

gcc -c myfile.c -I/path/to/header

在上述示例中,-I/path/to/header 表示将 /path/to/header 目录添加到头文件搜索路径中,这样编译器就能够找到并包含 myheader.h 头文件。

31. SVN 提交时提示冲突

当我删除了 SVN 服务器上的文件后,想要重新上传这时发生了冲突,最后的解决方式就是,我把本地的整个文件的删了,然后重新才 SVN 去拉取,拉取后发现我在服务器上删除了的文件还在,我再重新把本地的那个要删除的给删了,然后把要上传的文件拷进来,重新上传就可以了。
c

你可能感兴趣的:(c++,linux)