C语言的一个关键字——static

本文详细介绍了C语言中static关键字的用途,包括修饰变量和函数的作用。对于变量,static可以限定其作用域并保持其值在多次函数调用间不丢失;对于函数,static限制其仅在当前文件内可见。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

C语言的一个关键字——static

Static在C语言里面有两个作用,第一个是修饰变量,第二个是修饰函数

1、Static修饰变量

按照作用范围的不同,变量分为局部变量全局变量。如果用static修饰变量,不论这个变量是全局的还是局部的都是存储在静态数据区。下面分开来讲:

¨          如果用static修饰全局变量,我们称其为静态全局变量。

我们用static修饰全局变量的主要目的就是:使得其作用域仅限于变量被定义的文件中(即从变量定义处到本文件结尾处),其它文件不论通过什么方式都不能访问。

¨          如果用static修饰局部变量,我们称其为静态局部变量。

我们用static修饰局部变量的目的主要有两个:

1)         在某个函数体里面定义的静态局部变量,只能在本函数体被访问,即使同一个文件的其它函数也访问不了。

2)         静态局部变量总存储在静态数据区,所以即使这个函数运行结束,这个静态局部变量的值不会被销毁,函数下次使用时仍然要用到这个值。

 

下面给出一个例程:


 

#include <stdio.h>

 

static int j;  // 静态全局变量

int fun1(void)

{

         static int i = 0;

         i++;

         return i;

}

 

int fun2(void)

{

         j = 0;

         j++;

         return j;

}

 

int main(int argc, char *argv[])

{

         int k = 0, m = 0, n = 0;

         for(k=0; k<10; k++){

                   m = fun1( );

                   n = fun2( );

                   printf("%d,%d\n", m, n);

         }

         return 0;

}

 

结果:

1,1

2,1

3,1

4,1

5,1

6,1

7,1

8,1

9,1

10,1

 

从例程的运行结果可以看出:

对于fun1( ),运行一次之后,变量i保持了原来的数据,并没有销毁,而是在下一次调用的时候继续使用,所以才会有1、2、3…10这样的结果。而对于fun2( ),运行一次之后,变量j的值被销毁了,所以第二次调用结束j仍然保持原值“1”,所以循环十次,每次都是1

 

 

2、Static修饰函数

在函数前加static,则此函数成为静态函数(内部函数),我们用static修饰函数的主要目的是:用来表示不能被其它文件访问的一个函数(和用static修饰全局变量的目的一样,都是不允许其它文件访问)。这样一来便有一个好处:程序员不用担心自己编写的函数与其他文件的函数同名

 

 

 

对局部静态变量的特别说明

(1)静态局部变量数据属于静态存储类别,在静态存储区内分配内存单元,在整个程序运行期间都不释放。而自动变量(即动态局部变量)属于动态存储类别,占动态存储区空间而不占用静态存储区空间,函数调用结束后立即释放空间。

(2)静态局部变量时在编译时被赋值的,即自始至终只赋值一次,在程序运行时它已经有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的值。而自动变量赋初值,不是在编译时进行的,而是在运行时进行,所以每调用一次函数就赋一次初值。

(3)如在定义局部变量时不赋初值,则对静态局部变量来说,编译时自动赋一个初值0(对数值型变量)或空字符(对字符变量)。而对自动变量来说,如果不赋初值,则它的值是一个不确定的值。这是由每次动态分配的内存单元不同所决定的。

<think>嗯,用户想了解C语言中const关键字的基础用法。从查询内容来看,用户特别标注了"基础用法",说明可能是初学者或需要系统性梳理。需要重点围绕概念解释、作用说明和代码示例展开。用户引用的三篇资料其实提供了不错的切入点:[1]提到const修饰变量不能用于C89数组长度;[2]比较了const和#define的区别;[3]强调嵌入式开发中的价值。虽然引用说明"不是真正需求",但这些细节可以融入基础讲解中。注意到用户上一次询问的是系统级指令规范,这次突然转向C语言语法,可能是不同场景下的独立问题。需要确保讲解足够清晰完整:首先定义const的核心机制——通过编译器保证数据不变性,这是所有用法的基础。示例要突出三个典型场景:普通变量、指针修饰、函数参数。尤其指针部分需要拆解清楚"常量指针"和"指针常量"这对易混淆概念。根据引用[2],必须强调const与#define的关键差异:类型检查、内存占用等特性直接影响工程实践。对于基础用法而言,这是常被忽视的重点。引用[1]提到的C89数组限制正好可作为注意事项的实例。最后考虑嵌入引用[3]提到的优化价值,虽然基础篇不需深入编译器原理,但点明"可能提升效率"能让用户意识到语法背后的设计思想。示例代码准备用注释明确标注编译错误位置,比纯文字描述更直观。</think> 好的,我们来详细探讨一下C语言中`const`关键字的基础用法、作用以及示例。它在编程中主要用于确保数据的“只读”特性。 **C语言 `const` 关键字基础用法详解** `const`关键字的核心作用在于**将变量或指针目标声明为只读(Read-Only)**。它告诉编译器和相关程序员,被`const`修饰的对象在定义后其值不应该被程序修改。这有助于: 1. **提升代码安全性与健壮性**:防止意外修改本不该改变的值,减少Bug。 2. **提高代码可读性与可维护性**:清晰地向其他开发者(以及未来的自己)表明哪些值在逻辑上是常量。 3. **辅助编译器优化**:编译器知道某些值不变后,可能进行更好的优化(如常量传播)。 --- **一、基础用法:修饰普通变量** * **作用:** 将变量定义为只读常量。必须在定义时初始化,之后**不能修改**其值。 * **语法:** ```c const 数据类型 变量名 = 初始值; // 或(两者等价) 数据类型 const 变量名 = 初始值; ``` * **示例:** ```c const float PI = 3.14159; // 定义一个圆周率常量 const int MAX_SIZE = 100; // 定义一个最大尺寸常量 int const MIN_VALUE = 0; // 等价于 const int MIN_VALUE = 0; PI = 3.14; // 编译器错误!不能修改const变量 MAX_SIZE = 200; // 编译器错误!不能修改const变量 ``` --- **二、重要用法:修饰指针** 指针与`const`结合比较复杂,主要有三种情况,其含义截然不同: 1. **指向常量的指针 - 指针指向的数据不可变 (`const` 在数据类型前或后)** * **作用:** 指针本身可以指向不同的内存地址,**但不能通过该指针修改它所指向内存中的数据**。指针指向的目标被看作是只读的。 * **语法:** ```c const 数据类型 *指针变量名; // 最常用写法 数据类型 const *指针变量名; // 等价写法 ``` * **示例:** ```c int value = 10; const int *ptr = &value; // ptr是指向常量int的指针 // *ptr = 20; // 编译器错误!不能通过ptr修改value的值(即使value本身非const) value = 20; // 合法,因为value本身不是const ptr = &some_other_int; // 合法,ptr可以指向其他地方 ``` 2. **常量指针 - 指针本身不可变 (`const` 在指针变量名前)** * **作用:** 指针本身的值(它存储的地址)不可改变,必须固定指向定义时初始化的那块内存,但**可以通过该指针修改它所指向内存中的数据**(只要那部分内存本身不是只读的)。 * **语法:** ```c 数据类型 *const 指针变量名 = 初始化地址; ``` * **示例:** ```c int a = 5, b = 6; int *const ptr = &a; // ptr是一个常量指针,固定指向a *ptr = 10; // 合法,可以修改a的值。现在a = 10 // ptr = &b; // 编译器错误!不能修改ptr的值(即它不能再指向b) ``` 3. **指向常量的常量指针 - 指针本身和其指向的数据都不可变** * **作用:** 指针既不能改变指向(不能修改存储的地址),也不能通过该指针修改它所指向的数据。完全“锁定”。 * **语法:** ```c const 数据类型 *const 指针变量名 = 初始化地址; // 或 数据类型 const *const 指针变量名 = 初始化地址; ``` * **示例:** ```c int data = 100; const int *const ptr = &data; // ptr是指向常量int的常量指针 // *ptr = 200; // 编译器错误!不能通过ptr修改data // ptr = &some_other_int; // 编译器错误!不能修改ptr的值(地址) ``` **关键区别口诀:** 读`const`时,从右向左结合。 * `const int *p`:`p`是`*`(指针),指向`int const`(常量int)。=> **指向常量数据的指针**。 * `int *const p`:`p`是`*const`(常量指针),指向`int`。=> **常量指针(指向的数据可变)**。 * `const int *const p`:`p`是`*const`(常量指针),指向`int const`(常量int)。=> **指向常量数据的常量指针**。 --- **三、重要用法:修饰函数参数** * **作用:** * 保护传入的参数不被函数内部意外修改。特别是当参数是指针或引用时,防止函数修改指针指向的数据或引用的对象。 * 向函数的调用者明确说明:“我不会修改这个参数值”,提升接口清晰度。 * 允许函数接受指向常量的指针作为实参(一个指向非常量数据的指针可以安全地传递给接受指向常量数据的指针形参的函数)。 * **语法:** ```c // 保护原始值(按值传递时意义不大,通常用于按指针/引用传递) void func(const int param); // 按值传递const, const保护副本,实际意义较小 // 保护指针指向的数据(极其常用) void print_string(const char *str); // 保证不会通过str修改传入的字符串 // 保护引用指向的数据(C++中常见,C无引用) // void modify_array(const int arr[], int size); // 保证不会修改数组内容 void modify_array(const int *arr, int size); // C语言写法:等同于上一行注释 ``` * **示例 (保护指针指向的数据):** ```c #include // strlen函数的典型声明(模拟) size_t my_strlen(const char *str) { size_t length = 0; while (*str != '\0') { length++; str++; // str可以移动(改变自身指向的位置) } // *str = 'X'; // 编译器错误!不能修改str指向的内容 return length; } int main() { char message[] = "Hello"; printf("Length of '%s' is %zu\n", message, my_strlen(message)); return 0; } ``` 在这个例子中,`const char *str`确保`my_strlen`函数内部无法修改传入的字符串`message`的内容,这是`const`在函数参数中非常经典和重要的应用,在标准库函数(如`strlen`, `strcmp`, `fopen_s`的路径参数等)中被广泛使用[^2]。 --- **四、`const` 与 `#define` 宏定义的区别** 用户提供的引用[1]和[2]很好地指出了关键区别: 1. **类型检查:** * `const 变量`有明确的数据类型(如`const int`, `const char *`),编译器会进行严格的类型检查。 * `#define`宏只是简单的文本替换,**没有类型检查**,容易引入不易察觉的类型错误。 2. **作用域与生命周期:** * `const 变量`遵循C语言的变量作用域规则(块作用域、文件作用域),可以被封装在函数、模块内。 * `#define`宏在**预处理阶段**即被替换,没有作用域概念。一般定义在文件开头或头文件中,影响范围从定义点开始到文件结束(或宏被`#undef`取消)。 3. **内存占用与地址:** * `const 变量`会被分配存储空间(通常位于只读数据段或栈上),**拥有内存地址**。可以通过指针(如不安全的强制转换)在运行时访问,但修改行为是未定义的(可能导致程序崩溃)。 * `#define`宏在预处理后就被其定义的文本替换掉了,**不占用内存空间**,也没有地址的概念。 4. **调试:** * 可以调试`const 变量`(查看其值、地址)。 * 宏在编译前就被替换了,无法在调试器中看到宏的名字,只能看到替换后的值。 **简单总结:** 优先使用`const`定义常量,因为它更安全(类型检查)、更符合代码作用域规则(避免全局污染)、更利于调试(符号可见)。`#define`常用于定义**真正的常量字面值**(如数组大小在C89/C99 VLA前)或**条件编译标记**。 --- **五、重要注意事项与提示** 1. **初始化:** `const`变量**必须在定义时初始化**,否则之后没有机会给它赋值。 2. **C89限制 - 非真常量:** 在**C89标准**下,`const`变量不能用于定义静态数组大小(因为数组大小要求是常量表达式,C89认为`const`变量是一个运行时才能确定值的对象,尽管它定义后不变)。 * 用户提供的引用[1]指出了这点:`const int size = 10; int arr[size]; // C89中错误`。 * **C99支持变长数组 (VLA)**:C99允许使用非`const`的局部变量定义数组大小(但仍不能是`static`或`extern`的`const`变量),但VLA的实用性存在争议,且**`const`变量在C99/C11中依然不是可用于静态数组大小或`case`标签值的常量表达式**。定义编译时静态数组大小仍需使用`#define`宏或枚举常量。例如,`#define SIZE 10` 或 `enum { SIZE = 10 };`。 3. **运行时只读:** `const`在C语言中主要是一种**编译器层面的检查**。它告诉编译器“别让我修改这个值”,并阻止普通赋值操作。但它**不能保证该值物理存储在硬件的只读内存中**(如ROM)。嵌入式系统中若需要真正的硬件只读,需依赖链接器脚本将特定段(如`.rodata`)映射到ROM地址空间[^3]。 4. **绕过限制 (不推荐):** 理论上,可以通过**强制类型转换**将指向`const`类型的指针转换为非`const`类型指针,然后尝试修改。**这违反了`const`的语义,结果是未定义的(Undefined Behavior,UB)** ,可能导致程序崩溃(尤其是在该数据物理只读时)、产生错误结果或不稳定行为。**绝对应避免**。 --- **总结** `const`关键字是C语言中提升代码质量(安全性、可读性、可维护性)和利用编译器优化的重要工具。理解其基础用法: 1. **声明只读变量**:`const int a = 5;` 2. **修饰指针**(区分三类): * `const int *p`:指向常量的指针(数据只读)。 * `int *const p`:常量指针(指针本身只读)。 * `const int *const p`:指向常量的常量指针(指针和指向的数据都只读)。 3. **修饰函数参数**:主要保护指针/数组参数指向的数据不被函数内部修改,是接口设计良好实践,广泛用于库函数[^2]。 4. **与`#define`区别**:`const`有类型、作用域、地址;`#define`是预处理器文本替换、无类型[^1]。 5. **注意事项**:必须初始化、不是C89数组大小的常量表达式(C99 VLA除外)、编译期约束非物理只读保证。 合理使用`const`是写出健壮、清晰C语言程序的关键一步,在嵌入式开发等对稳定性和效率要求高的领域尤为重要[^3]。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值