extern
是一个关键字,用于C和C++编程语言中,表示一个变量或函数是在其他文件中定义的,也就是说,它们的定义在其他地方,不在当前文件中。这个关键字对于支持大型项目和模块化编程非常重要,因为它允许程序员在不同的源文件中使用相同的变量或函数。
extern
关键字告诉编译器,变量或函数的定义可能在别的源文件中。这样,编译器在编译时,就会去其他文件中找这个变量或函数的定义。如果找不到,编译就会失败。
在多个源文件中共享变量: 如果你有一个变量,想在多个源文件中使用,就可以用extern
关键字。首先,你在一个文件中定义这个变量,然后在其他文件中用extern
关键字声明它。这样,其他文件就可以使用这个变量了。
在多个源文件中共享函数: 如果你有一个函数,想在多个源文件中使用,也可以用extern
关键字。首先,你在一个文件中定义这个函数,然后在其他文件中用extern
关键字声明它。这样,其他文件就可以调用这个函数了。
都是C/C++代码
extern
关键字声明它。extern
关键字声明它。在C++中调用C的代码
extern "C"
可以告诉C++编译器,这个函数是用C语言写的,不应该对它的名字进行修饰。extern "C"
。这是因为C++编译器需要知道这些函数是用C语言写的,不应该对它们的名字进行修饰。例1:在多个文件中共享变量
假设我们有两个文件,一个是 main.cpp
,一个是 another_file.cpp
。我们想在这两个文件中共享一个变量 shared_variable
。我们可以这样做:
在 another_file.cpp
中:
int shared_variable = 10; // 定义并初始化 shared_variable
在 main.cpp
中:
extern int shared_variable; // 声明 shared_variable
int main() {
std::cout << "Shared Variable: " << shared_variable << std::endl; // 打印 shared_variable 的值
return 0;
}
例2:在 C++ 中调用 C 函数
假设我们有一个 C 函数 c_function
,我们想在 C++ 代码中调用它。我们可以这样做:
在 c_functions.c
文件中:
#include
void c_function() {
printf("Hello from C function!\n");
}
在 main.cpp
文件中:
extern "C" {
void c_function(); // 声明 C 函数
}
int main() {
c_function(); // 调用 C 函数
return 0;
}
注意:尽管extern
关键字可以使变量和函数在多个源文件中可用,但是过度使用它可能会导致代码的可维护性降低。因为,当你在一个文件中看到一个extern
变量或函数时,你必须去其他文件中找到它的定义,这可能会使代码难以理解和维护。因此,一般建议在必要的时候才使用extern
关键字,并尽量减少全局变量的使用。
operator new
或者其它类似的库函数),这个函数会向系统的内存管理器请求分配所需的内存。如果内存管理器无法分配足够的内存,那么operator new
会抛出一个std::bad_alloc
异常(除非你使用的是new(std::nothrow)
,在这种情况下,operator new
会返回一个nullptr
)。new
运算符的调用者。new
运算符返回一个指向新创建的对象的指针。int
,double
等),没有析构函数要调用。operator delete
或其他类似的库函数)来释放对象占用的内存。这个函数告诉内存管理器这块内存现在可以被重新分配给其他对象。delete
仅仅释放内存,并不会把指向该内存的指针设置为nullptr
。在delete
之后,任何还在引用被删除对象的指针现在都是悬挂指针,对其的使用是未定义的行为。为了避免这种情况,一种常见的做法是在delete
一个指针之后,立即把这个指针设置为nullptr
。void*
,需要手动转换为正确的类型void*
在函数体,代码块或类中声明变量:当在这些上下文中使用时,static
意味着该变量的生命周期为程序的整个执行期间,而不是仅仅是其所在的代码块。这样的变量只会被初始化一次,然后在后续的函数调用或代码块执行中保持其值。这对于需要跨多次函数调用或代码块执行保持状态的情况很有用。
void some_function() {
static int call_count = 0;
call_count++;
std::cout << "Function has been called " << call_count << " times\n";
}
在类中声明变量:在类中,static
关键字用于声明静态成员变量。静态成员变量不是类的每个实例的一部分,而是属于类本身的。所有实例共享同一个静态成员变量。这种静态成员变量是与类本身关联的,而不是与类的任何特定对象关联的。因此,静态成员变量在所有类的对象中都是共享的。静态成员变量必须在类外部初始化。
class SomeClass {
public:
static int static_member;
};
SomeClass::static_member = 0;
在类中声明函数:在类中,static
关键字也可以用于声明静态成员函数。静态成员函数可以在没有类的实例的情况下调用,也就是说,它们不依赖于特定的对象。静态成员函数只能访问类的静态成员变量,不能访问类的非静态成员,因为静态成员函数没有this指针。
class SomeClass {
public:
static void static_member_function() {
std::cout << "This is a static member function.\n";
}
};
在全局变量或函数前:当static
关键字用在全局变量或函数前时,它会更改这些实体的链接属性,使它们在其定义的源文件内部可见,而在文件外部则不可见。这被称为内部链接。这可以防止名称冲突,并帮助保持全局命名空间的清洁。
// File: main.cpp
static void some_function() {
// This function is only visible within main.cpp
}
strcpy
、 sprintf
、memcpy
都是在C和C++中常用的字符串和内存操作函数。
strcpy:strcpy
函数用于复制字符串。它将源字符串(包括结束字符’\0’)复制到目标字符串。需要注意的是,strcpy
不检查目标缓冲区的大小,所以可能会导致缓冲区溢出的问题。因此在使用时必须确保目标缓冲区足够大以容纳源字符串。
char src[50] = "example";
char dest[50];
strcpy(dest, src);
// Now dest contains the string "example"
sprintf:sprintf
函数用于将格式化的数据写入字符串。这个函数与printf
函数类似,只是printf
将数据写入到标准输出(通常是屏幕),而sprintf
将数据写入到字符串。与strcpy
一样,sprintf
也不检查目标缓冲区的大小,所以可能导致缓冲区溢出。如果需要防止缓冲区溢出,可以使用snprintf
函数,这个函数需要指定最大的写入字符数。
char buffer[50];
int a = 10;
float b = 3.14;
sprintf(buffer, "a = %d, b = %.2f", a, b);
// Now buffer contains the string "a = 10, b = 3.14"
memcpy:memcpy
函数用于复制内存区域。它将一个源内存区域的前N个字节复制到目标内存区域。与strcpy
和sprintf
不同,memcpy
并不关心数据的内容,它只是简单地复制字节。因此,memcpy
可以用来复制任何类型的数据,包括字符串、结构、数组等。需要注意的是,memcpy
不会检查源和目标内存区域是否重叠,如果重叠,可能会导致未定义的行为。
char src[50] = "example";
char dest[50];
memcpy(dest, src, strlen(src) + 1);
// Now dest contains the string "example"
strcpy
实现字符串拷贝,遇到\0
结束sprintf
用于格式化字符串memcpy
可以实现内存块的拷贝,根据size的大小来复制memcpy
最快strcpy
次之sprintf
最慢strcpy
操作对象为字符串spintf
操作对象可以为多种数据类型memcpy
操作对象为内存地址"野指针"是指针变量的一个常见问题,它指的是指向"不确定区域"的指针。在以下几种情况下,指针通常被视为野指针:
nullptr
。例如,如果你使用delete
或free
释放了一个指针指向的内存,但没有把指针设置为nullptr
,那么这个指针就变成了野指针。使用野指针通常会引发问题,因为它可能会导致以下几种类型的错误:
解引用野指针:如果你试图访问野指针指向的内存,结果通常是未定义的。在最好的情况下,你可能会得到一些随机的值。在最坏的情况下,程序可能会崩溃,因为野指针可能指向一个你的程序没有权限访问的内存区域。
内存泄漏:如果你丢失了一个指针(即让一个指针变成了野指针),而这个指针是唯一指向一块动态分配的内存的指针,那么你就无法再释放那块内存。这被称为内存泄漏,它会导致你的程序消耗越来越多的内存。
数据破坏:如果你不小心使用了野指针,可能会无意中覆盖内存中的其他数据。这可能会导致各种难以跟踪的问题,因为它可能会影响到程序中的任何部分。
初始化指针:当你声明一个新的指针时,立即初始化它。如果没有具体的对象可以指向,那就初始化为nullptr
。
int* p = nullptr; // 初始化为 nullptr
为指针对象分配内存:在使用指针操作对象之前,确保已经为这个对象分配了内存。
p = new int; // 为指针对象分配内存
// or
p = (int*)malloc(sizeof(int)); // 在C中,使用malloc为指针对象分配内存
检查内存分配是否成功:在使用new
或malloc
分配内存后,需要检查指针是否为nullptr
。如果是,说明内存分配失败,这时需要进行错误处理,通常是打印错误信息并退出程序。
p = new(std::nothrow) int; // 使用nothrow版本的new,如果内存分配失败,返回nullptr
if (p == nullptr) {
std::cerr << "Memory allocation failed.\n";
exit(EXIT_FAILURE);
}
对空间数据置零:在C中,分配内存后,为了安全,通常需要将新分配的内存初始化为零。
p = (int*)malloc(sizeof(int));
if (p == nullptr) {
fprintf(stderr, "Memory allocation failed.\n");
exit(EXIT_FAILURE);
}
memset(p, 0, sizeof(int)); // 将内存空间置零
释放后置空:当你使用delete
或free
释放一个指针指向的内存后,立即把这个指针设置为nullptr
。这样,即使你再次错误地尝试释放这个指针,由于它现在是nullptr
,这将是一个无害的操作。
delete p;
p = nullptr; // 释放后置空
使用智能指针:在C++11及以后的版本中,你可以使用智能指针(如std::unique_ptr
和std::shared_ptr
)来自动管理内存。智能指针会在合适的时候自动释放它们所拥有的内存,这可以避免很多与野指针相关的问题。
std::unique_ptr<int> p(new int(42));
// Now you don't have to worry about deleting the memory when you're done with it
指针的作用:
传递参数:指针可以作为函数参数,允许函数访问和修改它们指向的对象。这样可以避免对象数据的复制,特别是在处理大型数据结构时,使用指针而非值传递可以显著提高性能。
void update(int* p) {
*p = 10; // 修改p指向的值
}
实现多态:在面向对象的编程中,指针可以用来实现多态。基类的指针可以指向派生类的对象,并调用虚函数,这样在运行时就能根据对象的实际类型执行正确的函数。
class Base {
public:
virtual void print() const {
std::cout << "Base\n";
}
};
class Derived : public Base {
public:
void print() const override {
std::cout << "Derived\n";
}
};
Base* p = new Derived;
p->print(); // Prints "Derived"
delete p;
代码复用:指针可以帮助实现代码复用。例如,你可以写一个通用的函数,这个函数接受指向不同类型的对象的指针,并对这些对象执行相同的操作。
引用的作用:
传递参数:引用也可以作为函数参数,允许函数访问和修改它们引用的对象。使用引用参数可以避免对象的复制,同时语法上比使用指针更清晰。
void update(int& ref) {
ref = 10; // 修改ref引用的值
}
函数返回值:函数可以返回对象的引用,这样可以避免对象数据的复制。例如,你可以返回对象的成员变量的引用,或者返回数组元素的引用。
int& getFirst(std::vector<int>& vec) {
return vec[0]; // 返回vec的第一个元素的引用
}
指针和引用在很多情况下可以互换使用,但它们各自都有一些特定的用途,例如指针可以用来实现动态多态,而引用可以用来创建别名。在选择使用指针还是引用时,通常应该考虑代码的具体需求和设计意图。
指针和引用在C++中都被用于间接访问对象,但它们之间存在几个关键的区别:
是否需要初始化:
指针:在声明时不一定需要初始化,但出于安全考虑,最好初始化。如果没有具体的对象可以指向,可以将其初始化为nullptr
。
int* p = nullptr; // 指针初始化为 nullptr
引用:必须在声明时初始化,并且一旦被初始化后,就不能改变其绑定的对象。引用不能被初始化为无对象。
int a = 10;
int& ref = a; // 引用初始化,绑定到a
是否允许为空:
nullptr
。是否直接操作对象:
指针:通过某个指针变量指向一个对象,对它所指向的变量进行间接操作。
*p = 20; // 间接操作p所指向的对象
引用:是目标对象的别名,对引用操作就是直接对目标对象操作。
ref = 20; // 直接操作引用的目标对象
是否是对象:
指针:是一个对象,它有自己的地址,并可以被其他指针指向,即可以定义指针的指针。
int** pp = &p; // 定义指针的指针
引用:不是一个对象,它没有实际地址,不能定义引用的引用或引用的指针。
这些区别使得指针和引用在不同的场合有不同的用途。例如,如果你需要一个可以为空的、可以重新绑定的、或者可以指向不同类型对象的“间接访问”,你可能需要使用指针。而如果你需要一个始终绑定到某个对象、并且可以像操作实际对象一样操作的“间接访问”,你可能需要使用引用。