C语言基础之关键字+宏

关键字类型

我们通常将关键字分为4个部分:数据类型关键字、控制语句关键字、存储类型关键字、其它关键字。

  1. 数据类型关键字:
  • char 声明字符型变量或函数
  • double 声明双精度变量或函数
  • enum 声明枚举类型
  • float 声明浮点型变量或函数
  • int 声明整型变量或函数
  • long 声明长整型变量或函数
  • short 声明短整型变量或函数
  • signed 声明有符号类型变量或函数
  • struct 声明结构体变量或函数
  • union 声明联合数据类型
  • unsigned 声明无符号类型变量或函数
  • void 声明函数无返回值,声明无类型指针

2.控制语句关键字

  • for
  • break
  • while
  • do
  • continue 结束当前循环,开始下一轮循环
  • if
  • else
  • goto 无条件跳转语句
  • switch
  • case
  • default 开关语句中的“其他”分支
  • return

3.存储类型关键字

  • auto 声明自动变量 一般不使用
  • extern 声明变量是在其他文件正声明(也可以看做是引用变量)
    (定义只能定义一次,声明可以多次声明)
  • register 声明积存器变量
  • static 声明静态变量或函数

4.其它关键字

  • const 声明只读变量
  • sizeof
  • typedef 用以给数据类型取别名(当然还有其他作用)
  • volatile 说明变量在程序执行中可被隐含地改变

先来重点说几个关键字:

typedef
它是类型定义,可以理解为是给类型重命名。

#include<stdio.h>
typedef unsigned int int_15;//将unsigned int重命名为int_15,int_15也是一个类型名。
int main()
{
	unsigned int x = 0;
	int_15 y = 0;
 	return 0;
 }

static
在C语言中static是修饰变量和函数的
我们先来看一段代码

#include<stdio.h>
void Show()
{
	 int i = 0;//局部变量
	 i++;
	 printf("%d\n", i);
}
int main()
{
	int i = 0;	
	for (; i < 10; i++)
	{
		  Show();
	}
	return 0;
}

这个程序打印出来都是1,是因为当我们的for循环每一次去调用函数Show时,函数内部的变量i就会被自动创建,函数返回时又会被自动释放,这就是局部变量的特征。

#include<stdio.h>
void Show()
{
  static int i = 0;//static修饰局部变量
  i++;
  printf("%d\n", i);
}
int main()
{
 int i = 0; 
 for (; i < 10; i++)
 {
    Show();
 }
 return 0;
}

这里static修饰变量i,那么i的生命周期就发生了改变。当第二次调用函数时,就直接从i++开始了。
即:
static修饰局部变量改变了变量的生命周期,让静态局部变量出了作用域依然存在,到程序结束,生命周期才结束,但是他的作用域其实是没有变化的

当static修饰全局变量时,代表该全局变量只在本文件内有效。当然全局变量是支持跨文件访问的。
当static修饰函数时,代表该函数只在本文件内有效。当然函数时支持跨文件访问的

#include<stdio.h>
#define M 123456
int main(){
	printf("M=%d\n", M);
	return 0;
}

宏只做简单的文本替换,便于代码的维护性,方便阅读

宏还有一种用法我们称为“宏函数”,但是它其实和函数没有一点关系,只是用法相似。不推荐使用。

#include <stdio.h>
#define Square(x) x*x
int main()
{
	printf("%d\n", Square(10));
	return 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]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值