C-关键字和关键库函数细论

sizeof 与 strlen

sizeofstrlen在C语言中各有其独特的用途和限制。sizeof主要用于获取对象或类型所占的内存字节数,而strlen则用于计算字符串的长度(不包括终止的空字符)。

char str[] = "Hello, World!";  
char *ptr = str;  
  
printf("%zu\n", sizeof(str)); // 输出整个数组所占的字节数,包括终止符'\0',可能是13(取决于编译器和平台)  
printf("%zu\n", sizeof(ptr)); // 输出指针变量ptr所占的字节数,通常是4或8(取决于平台)  
printf("%zu\n", strlen(str)); // 输出字符串的长度,不包括终止符'\0',输出12  
printf("%zu\n", strlen(ptr)); // 同样输出12,因为ptr指向str
static关键字

在C++中,static关键字在C++中主要用于限制变量的作用域和生命周期,隐藏函数实现细节,以及实现类的静态成员变量和静态成员函数,这些特性在程序设计中非常有用。

  1. 局部静态变量

    • static用于局部变量时(包括在函数内部定义的变量),这个变量在程序的整个执行期间只被初始化一次,并在函数调用结束后不会销毁,而是保持其值,直到程序结束。这意味着,在后续的函数调用中,这个局部静态变量的值会保持不变,除非在函数内部被显式修改。
  2. 全局静态变量

    • static用于全局变量时,该变量的作用域被限制在定义它的文件内,即它是文件作用域的(file-scoped)。这意呀着,其他文件无法直接访问这个变量,从而避免了命名冲突。全局静态变量具有静态存储期,即在程序开始执行时被分配内存,并在程序结束时释放内存。
  3. 静态函数

    • static用于函数声明时,这个函数就变成了静态函数。静态函数的作用域被限制在定义它的文件内,其他文件无法链接到这个函数。这有助于隐藏函数实现细节,减少命名空间污染。
  4. 静态成员变量

    • 在类定义中,static关键字用于声明类的静态成员变量。静态成员变量属于类本身,而不是类的某个特定对象。这意味着,无论创建了多少个类的对象,静态成员变量都只有一份拷贝,并由所有对象共享。静态成员变量必须在类定义外部进行初始化。
  5. 静态成员函数

    • static关键字也可以用于声明类的静态成员函数。静态成员函数只能访问类的静态成员变量和静态成员函数,不能访问类的非静态成员变量和非静态成员函数(因为它们属于类的特定对象)。静态成员函数不依赖于类的特定对象实例,因此可以通过类名直接调用,而无需创建类的实例。
  6. 静态类(注意:C++标准本身并不直接支持静态类概念,但可以通过静态成员变量和静态成员函数模拟静态类行为):

    • 虽然在C++中没有直接称为“静态类”的语法结构,但可以通过将所有成员变量和成员函数都声明为静态来模拟静态类的行为。这样的类本质上成为了一个只包含静态成员和静态函数的命名空间,不包含任何非静态成员。
const关键字

在C++中,const关键字是一个非常重要的修饰符,它用于声明一个变量、函数参数、返回值或成员函数为常量,即这些实体在声明之后其值(对于变量和函数参数)或行为(对于成员函数)将不能被修改。const的使用可以提高代码的可读性、安全性和可维护性。

  1. 修饰变量:当const修饰变量时,该变量成为常量,其值在初始化之后不能被修改。
const int maxVal = 100; // maxVal是一个常量,其值不能被修改
  1. 修饰指针const可以修饰指针,但需要注意const的位置,因为它决定了是指针指向的值是常量,还是指针本身是常量,或者是两者都是。
const int* ptr; // ptr是一个指向int类型常量的指针,即不能通过ptr修改它所指向的值
int* const ptr; // ptr是一个常量指针,指向int类型,即ptr的值(即它所指向的地址)不能被修改
const int* const ptr; // ptr是一个常量指针,指向int类型的常量,即ptr的值和它所指向的值都不能被修改
  1. 修饰函数参数:将const用于函数参数时,表明函数内部不会修改通过这个参数传入的变量。这可以提高函数的通用性,因为允许传入常量变量作为参数。
void func(const int& x); // x是通过引用传入的,但在函数内部不能被修改
  1. 修饰函数返回值const也可以修饰函数的返回值,但这在大多数情况下并不是特别有用,因为返回值通常会被赋值给另一个变量,而赋值操作会丢弃const修饰符。然而,在返回引用或指针时,const修饰符可以表明返回的引用或指针指向的值不应被修改。
const int& getConstRef() {
    static int value = 0;
    return value; // 返回对静态变量的const引用
}
  1. 修饰成员函数:在类定义中,const可以修饰成员函数,表明该函数不会修改类的任何成员变量(除了那些被声明为mutable的成员变量)。这使得该函数可以被常量对象调用。
class MyClass {
public:
    void func() const {
        // 这里不能修改类的任何成员变量(除了mutable的)
    }
};

const MyClass obj;
obj.func(); // 合法,因为func()是const成员函数
define 与 const区别

defineconst在C/C++等编程语言中都是用于定义常量的方式,但它们之间存在显著的区别。

特性defineconst
编译器处理方式预处理阶段文本替换编译时类型检查
类型检查无类型,仅文本替换有明确类型,进行类型检查
内存占用不占用运行时内存(但可能增加代码段大小)占用运行时内存(可能通过优化减少)
调试调试时难以直接查看值调试时可直接查看值
作用域和生命周期全局有效(除非#undef),无作用域限制受限于定义位置的作用域和生命周期
使用场景全局常量定义,条件编译局部常量、类成员常量,提供类型安全和调试支持
类型安全不提供提供
存储位置预处理后嵌入代码中可能存储在符号表或内存中
灵活性灵活,可用于复杂宏定义适用于简单常量定义,易于理解和维护
inline关键字

inline关键字在C++中是一个非常重要的特性,它用于向编译器建议将函数的调用替换为函数体本身的直接展开,从而消除函数调用的开销,提高程序的执行效率。

// 示例:内联函数的定义  
inline int square(int n) {  
    return n * n;  
}  
  
// 类的成员函数示例  
class MyClass {  
public:  
    MyClass(int value) : m_value(value) {}  
    inline int getValue() const {  
        return m_value;  
    }  
private:  
    int m_value;  
};  
  
// 在类外定义时添加inline关键字  
inline int MyClass::getValue() const {  
    return m_value;  
}
define 与 inline

下表格总结了defineinline之间的主要区别:

特性define宏定义inline内联函数
定义方式预处理指令#defineC++关键字inline
作用阶段预处理阶段编译阶段
作用域无作用域限制,整个源文件有效遵循C++作用域规则
类型检查无类型检查,仅文本替换有类型检查,与普通函数相同
安全性可能引入副作用,如优先级问题、重复求值编译器进行更严格的检查,更安全
调试能力调试困难,因为宏展开后不易识别原始宏调试更容易,与普通函数一样
性能影响可能因重复求值或宏展开过大导致性能问题编译器决定是否内联,可能提高性能
使用场景定义常量、简单的表达式或代码片段,条件编译频繁调用且函数体较小的函数,以减少函数调用开销
编译器行为总是进行文本替换可能忽略inline建议,不内联函数
new 与 malloc

newmalloc 都是用于动态内存分配的机制,new 是 C++ 语言中的操作符,而 malloc是 C 语言标准库中的一个函数。

特性new (C++)malloc ©
所属语言/库C++ 语言操作符C 标准库函数
语法Type* ptr = new Type(initializer);void* ptr = malloc(size);
初始化支持自动初始化(调用构造函数)不自动初始化,需手动(如使用memset
类型安全是,编译器检查类型否,返回void*,需显式类型转换
内存管理使用delete释放,自动调用析构函数使用free释放,不调用析构函数
数组分配使用new[]分配,delete[]释放分配时指定总大小,释放时同样使用free
异常安全在分配失败时抛出std::bad_alloc异常分配失败时返回NULL(或nullptr,取决于C标准版本和编译器)
用途分配并初始化对象分配原始内存块
性能可能稍慢(因为调用构造函数)通常更快(仅分配内存)
调试支持更好的调试信息(如类型信息)调试信息较少,依赖于类型转换和初始化
delete 与 free

在 C++ 中,除非你有特殊理由需要使用 C 风格的内存管理(例如,与 C 代码库互操作),否则通常建议使用 newdelete,因为它们提供了更好的类型安全性和资源管理。

特性delete (C++)free ©
所属语言C++C
用途释放通过 new 分配的内存,并调用对象的析构函数(如果有)释放通过 malloccallocrealloc 分配的内存块
语法delete ptr;(单个对象)
delete[] ptr;(对象数组)
free(void* ptr);
行为释放内存,并调用对象的析构函数(如果对象有析构函数)仅释放内存块,不调用析构函数
类型安全是,与对象的类型相关联否,返回 void*,需显式类型转换
对NULL的处理如果 ptrnullptr(或 NULL,取决于C++标准版本),则不执行任何操作如果 ptrNULL,则不执行任何操作
异常安全分配失败时抛出 std::bad_alloc 异常;释放内存时通常不失败不抛出异常,分配失败时返回 NULL(或 nullptr
内存管理负责释放内存,并调用析构函数进行资源管理负责释放内存,但不负责资源管理(如文件句柄等)
野指针问题释放后应将指针设置为 nullptr(C++11及更高版本)以避免野指针释放后应将指针设置为 NULL 或指向其他有效内存
用途场景C++ 语言中的动态内存和对象生命周期管理C 语言中的动态内存管理
volatile

在C++中,volatile关键字主要用于处理多线程环境或硬件访问时,确保变量的可见性和防止编译器对代码进行过度优化。

  • 可见性:当一个变量被声明为volatile时,它告诉编译器该变量的值可能会在程序的控制之外被改变(例如,由另一个线程或硬件事件改变)。因此,编译器在每次访问该变量时都必须重新从内存中读取它的值,而不是使用可能已经缓存在寄存器或CPU缓存中的值。

  • 防止优化:volatile还阻止编译器对包含该变量的代码进行某些优化,比如将多次读取该变量的操作合并为一个,或者将写入操作延迟到更晚的时间点。

  • 在某些嵌入式系统或需要直接与硬件交互的程序中,volatile可能用于确保对特定内存地址的访问不会被编译器优化掉,因为这些地址可能映射到硬件寄存器上。

struct关键字

虽然C和C++都支持struct,但它们在用法和特性上存在一些差异,尤其是在C++中,随着类的引入,struct的一些行为变得与类相似,但也有一些关键区别。

  • C语言:struct用于定义记录(record),它是一个或多个不同类型变量的集合。使用struct时,你需要首先声明它,然后才能创建该类型的变量。C中的struct默认访问权限是public(虽然C中没有直接使用publicprivate这样的关键字),这意味着结构体内的所有成员都可以从外部直接访问。
struct Person {  
    char name[50];  
    int age;  
    float height;  
};  
  
// 创建Person类型的变量  
struct Person person1;
  • C++:C++在C的基础上扩展了struct的功能,使其更加类似于类。C++中的struct和类的主要区别在于:

    默认访问权限:在C++中,struct的默认访问权限是public,而类的默认访问权限是private

    继承:当struct或类被用作基类时,struct默认是公有继承(public inheritance),而类默认是私有继承(private inheritance)。但这一点在大多数情况下并不显著,因为在实际编程中通常会明确指定继承类型。

struct Person {  
    std::string name;  
    int age;  
    float height;  
  
    // 构造函数  
    Person(std::string n, int a, float h) : name(n), age(a), height(h) {}  
  
    // 成员函数  
    void display() const {  
        std::cout << "Name: " << name << ", Age: " << age << ", Height: " << height << std::endl;  
    }  
};  
  
// 创建Person类型的变量  
Person person1("John Doe", 30, 1.75f);  
person1.display();
struct 与 union 的区别

struct(结构体)和union(共用体)是C和C++中两种用于定义复合数据类型的关键字,它们之间在多个方面存在显著的区别。

  1. 成员存储方式
  • struct:结构体在内存中为每个成员分配独立的内存空间,所有成员变量都同时存在。结构体的大小是其所有成员大小的总和(加上可能的填充字节以满足内存对齐要求)。
  • union:共用体在内存中只分配足够的空间来存储其最大成员,所有成员共享同一段内存空间。在任何时刻,共用体只能存储其一个成员的值,对其他成员的赋值会覆盖当前成员的值。
  1. 内存分配
  • struct:每个成员都有自己的内存位置,且成员在内存中的顺序与它们在结构体中声明的顺序相同。结构体的大小是其成员大小之和加上可能的填充字节,以确保结构体的对齐。
  • union:所有成员共享同一内存位置,共用体的大小是其最大成员的大小(按照该成员类型的倍数进行对齐)。这意味着,无论共用体声明了多少个成员,它在内存中占用的空间都是固定的,且等于其最大成员的大小。
  1. 成员访问和赋值
  • struct:对结构体中不同成员的赋值是独立的,互不影响。可以通过结构体变量直接访问其任何成员。
  • union:对共用体中不同成员的赋值会覆盖当前成员的值。在任一时刻,只能访问并安全地使用共用体中被赋值的那个成员。尝试访问未被赋值的成员将导致未定义行为。

6. 示例

// struct 示例
struct Person {
    char name[50];
    int age;
    float height;
};

// union 示例
union Data {
    int i;
    float f;
    char str[20];
};

// 结构体变量
Person person;
// 共用体变量
Data data;

在上面的示例中,Person结构体将为其成员nameageheight分别分配内存空间,而Data共用体则只分配足够的空间来存储一个int、一个float或一个char数组(取其中最大者),并且这些成员共享这段空间。

extern"C"

在C和C++编程中,extern "C"这个语法结构主要用于在C++代码中链接C代码,确保C++编译器能够正确地处理C语言编写的代码或库。

这是因为C++支持函数重载,而C不支持。C++编译器为了支持函数重载,会对函数名进行修饰(Name Mangling),这样编译后的函数名会包含函数参数类型的信息。然而,C编译器不会进行这样的修饰,因此如果C++代码直接调用C语言编写的函数,链接器会因为找不到未修饰的函数名而报错。

为了解决这个问题,extern "C"告诉C++编译器,被其修饰的代码应该按照C语言的编译和链接规则来处理。

  1. 对单个函数使用

    extern "C" {
        void my_c_function();
    }
    
  2. 对整个文件使用
    如果一个头文件完全是用C语言编写的,并且打算被C和C++代码共同使用,可以在该头文件的开始处加上extern "C"的块包围整个文件的内容。但是,通常的做法是在C++代码中包含该头文件时,使用extern "C",而不是修改头文件本身。

    extern "C" {
    #include "myclib.h"
    }
    
strcpy函数

strcpy函数的主要缺陷在于它不会检查目标缓冲区的大小,这可能导致缓冲区溢出。如果源字符串的长度超过目标缓冲区的大小,strcpy会继续复制直到遇到源字符串的终止符,从而覆盖相邻内存区域的数据,可能引发数据损坏、程序崩溃或安全漏洞。

为避免strcpy的缺陷,推荐使用更安全的字符串复制函数,如strncpy(需手动确保终止符)或strncpy_s(指定目标缓冲区大小)。这些函数提供了额外的边界检查,有助于防止缓冲区溢出。同时,也可以考虑使用高级语言特性,如C++中的std::string类,它自动管理内存并提供了更安全的字符串操作。

lambda 表达式的应用

Lambda 表达式可以捕获其所在作用域中的变量,并允许你在需要函数对象的地方使用它们,而无需显式地定义一个函数或函数对象类型。

  • 基本语法:
[capture](parameters) mutable -> return_type { body }
  • 示例:
#include <iostream>  
#include <vector>  
#include <algorithm>  
  
int main() {  
    std::vector<int> numbers = {1, 2, 3, 4, 5};  
  
    // 使用 Lambda 表达式对 vector 中的元素进行排序  
    std::sort(numbers.begin(), numbers.end(), [](int a, int b) {  
        return a < b; // Lambda 表达式,表示升序排序  
    });  
  
    // 使用 Lambda 表达式和 std::for_each 遍历 vector  
    std::for_each(numbers.begin(), numbers.end(), [](int n) {  
        std::cout << n << ' ';  
    });  
  
    return 0;  
}
explicit 的作用

当构造函数被声明为explicit时,它只能被显式地调用,不能通过隐式类型转换自动调用。explicit关键字主要用于修饰只有一个参数的构造函数。对于多参数构造函数,如果所有参数都有默认值,并且只有第一个参数是必需的,也可以使用explicit(尽管这种情况较少见)。

class MyClass {  
public:  
    explicit MyClass(int value) {  
        // 构造函数的实现  
    }  
};  
  
void func(MyClass obj) {  
    // 函数的实现  
}  
  
int main() {  
    MyClass obj1(10); // 正确,显式调用构造函数  
    // MyClass obj2 = 20; // 错误,如果构造函数是explicit的,则此行会编译失败  
    MyClass obj3(MyClass(20)); // 正确,虽然绕了一点,但显式调用了构造函数  
    func(obj1); // 正确  
    // func(30); // 错误,如果构造函数是explicit的,则此行会编译失败  
    return 0;  
}
define 与 typedef 的区别
特性#definetypedef
执行时间和处理方式在预处理阶段进行文本替换,不进行类型检查或语法分析在编译阶段处理,具有类型检查功能
作用域从定义点开始,直到文件结束,除非使用#undef取消定义;可能跨越文件界限限定在定义的作用域内,如文件、命名空间或函数内
功能定义常量、变量、编译开关、类型别名等;可以进行带参数的宏定义主要用于定义类型的别名,简化复杂的类型声明
用途条件编译、宏展开、定义常量等;也可用于类型别名,但不如typedef清晰提高代码的可读性和可维护性,特别是处理复杂类型声明和跨平台编程时
// #define 示例  
#define INT_PTR int*  
INT_PTR p1, p2; // p1 和 p2 都是 int* 类型  
  
// typedef 示例  
typedef int* IntPtr;  
IntPtr q1, q2; // q1 和 q2 都是 int* 类型
class 与 struct 的异同
  • 相似之处

    自定义数据类型:两者都允许用户定义自己的数据类型,封装数据成员和成员函数。

    成员组成:都可以包含成员变量(属性)和成员函数(方法)。

    继承:都支持继承机制,可以创建基于现有类型的新类型。

    访问控制:都可以使用访问修饰符(如publicprivateprotected)来控制成员的访问权限。

  • 不同之处

    默认访问权限class的默认成员访问权限是privatestruct的默认成员访问权限是public

    继承方式class默认使用private继承。struct默认使用public继承。

    用途和语义class更常用于面向对象编程,表示具有行为和属性的对象。struct虽然也可以用于面向对象编程,但更常用于表示简单的数据结构或记录,其成员通常是可以直接访问的公共数据。

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值