1. C语言入门:从Hello World到基础语法
1.1 第一个C程序:Hello World解析
每个C语言学习者的旅程都从Hello World开始。这个看似简单的程序蕴含着C语言的基本框架:
c复制#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("Hello world!\n");
return 0;
}
这段代码中,#include是预处理指令,用于包含标准输入输出库(stdio.h)和标准库(stdlib.h)。main()函数是程序的入口点,所有C程序都必须包含这个函数。printf()是标准输出函数,\n表示换行符。return 0;表示程序正常结束。
注意:在较新的C标准中,main()函数应该明确声明参数类型,如
int main(void)或int main(int argc, char *argv[]),这更符合现代编程规范。
1.2 格式化输出:%d的使用技巧
%d是C语言中的格式说明符,用于输出十进制整数。它就像一个占位符,会被后面提供的整数值替换:
c复制#include <stdio.h>
int main(void)
{
printf("20+36=%d", 20+36); // 输出:20+36=56
return 0;
}
在实际开发中,我们经常需要输出多个变量:
c复制int a = 10, b = 20;
printf("a=%d, b=%d, sum=%d", a, b, a+b);
经验分享:printf()的返回值是成功输出的字符数,这在调试时很有用。例如:
int count = printf("Hello");,count的值将是5。
2. C语言输入与变量基础
2.1 变量声明与scanf函数
变量是存储数据的基本单元,在C语言中必须先声明后使用。scanf()函数用于从标准输入读取数据:
c复制#include <stdio.h>
int main(void)
{
int price;
printf("请输入金额:");
scanf("%d", &price); // &是取地址运算符
printf("商品价格为:%d", price);
return 0;
}
常见问题:初学者常忘记在scanf中使用&符号,这会导致程序崩溃。记住:scanf需要知道变量的内存地址来存储输入的值。
2.2 常量定义与const关键字
const关键字用于定义常量,一旦初始化后就不能修改:
c复制const int AMOUNT = 100;
const常量有以下几个特点:
- 必须在定义时初始化
- 不能作为左值出现在赋值语句中
- 提高了代码的可读性和安全性
编程技巧:习惯使用const可以避免意外修改重要数据,编译器会帮助检查这类错误。
2.3 基本数据类型详解
C语言提供了多种基本数据类型:
- 整型:
int(通常4字节)、short(2字节)、long(4或8字节) - 浮点型:
float(单精度,4字节)、double(双精度,8字节) - 字符型:
char(1字节)
c复制#include <stdio.h>
int main(void)
{
int integer = 10;
float singlePrecision = 3.14159f;
double doublePrecision = 3.141592653589793;
char character = 'A';
printf("int: %d\n", integer);
printf("float: %f\n", singlePrecision);
printf("double: %lf\n", doublePrecision);
printf("char: %c\n", character);
return 0;
}
注意:浮点数比较时不要直接使用==,因为浮点运算可能有精度误差。应该比较它们的差值是否小于某个很小的数(如1e-6)。
3. 指针:C语言的精髓
3.1 指针基础概念
指针是存储内存地址的变量,使用&获取变量地址,*访问指针指向的值:
c复制#include <stdio.h>
int main(void)
{
int i = 42;
int *p = &i; // p是指向i的指针
printf("i的地址:%p\n", &i);
printf("p存储的地址:%p\n", p);
printf("通过p访问的值:%d\n", *p);
*p = 100; // 通过指针修改i的值
printf("修改后i的值:%d\n", i);
return 0;
}
指针的几个关键点:
- 指针类型必须与指向的变量类型匹配
- 未初始化的指针是危险的(野指针)
NULL是空指针常量,表示指针不指向任何有效地址
3.2 指针的典型应用
3.2.1 交换两个变量的值
c复制#include <stdio.h>
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int main(void)
{
int x = 10, y = 20;
printf("交换前:x=%d, y=%d\n", x, y);
swap(&x, &y);
printf("交换后:x=%d, y=%d\n", x, y);
return 0;
}
3.2.2 通过指针返回多个值
c复制#include <stdio.h>
void getMinMax(int arr[], int size, int *min, int *max)
{
*min = *max = arr[0];
for(int i=1; i<size; i++)
{
if(arr[i] < *min) *min = arr[i];
if(arr[i] > *max) *max = arr[i];
}
}
int main(void)
{
int numbers[] = {3, 1, 4, 1, 5, 9, 2, 6};
int min, max;
getMinMax(numbers, 8, &min, &max);
printf("最小值:%d,最大值:%d\n", min, max);
return 0;
}
3.3 const与指针的组合
const和指针结合时有多种形式,含义各不相同:
const int *p:指针指向的内容不可变int * const p:指针本身不可变(不能指向其他地址)const int * const p:指针和指向的内容都不可变
c复制int a = 10, b = 20;
const int *p1 = &a; // 可以改变p1指向,但不能通过p1修改a的值
*p1 = 30; // 错误
p1 = &b; // 正确
int * const p2 = &a; // p2永远指向a,不能改变指向
*p2 = 30; // 正确
p2 = &b; // 错误
const int * const p3 = &a; // p3永远指向a,且不能通过p3修改a
*p3 = 30; // 错误
p3 = &b; // 错误
4. 字符串处理基础
4.1 字符串的表示方式
C语言中没有专门的字符串类型,通常用字符数组或字符指针表示:
c复制char str1[] = "Hello"; // 栈上分配的数组
char *str2 = "World"; // 指向字符串常量的指针
两者的关键区别:
- 数组形式可以修改内容(因为是栈上分配的)
- 指针形式指向字符串常量,内容不可修改
重要提示:字符串常量存储在程序的只读数据段,尝试修改会导致未定义行为(通常是程序崩溃)。
4.2 字符串的基本操作
虽然C标准库提供了string.h来处理字符串,但理解底层实现很重要:
c复制#include <stdio.h>
// 自定义字符串长度函数
int my_strlen(const char *s)
{
int len = 0;
while(*s++) len++;
return len;
}
// 自定义字符串复制函数
void my_strcpy(char *dest, const char *src)
{
while((*dest++ = *src++));
}
int main(void)
{
char greeting[20];
my_strcpy(greeting, "Hello");
printf("'%s'的长度是%d\n", greeting, my_strlen(greeting));
return 0;
}
4.3 命令行参数处理
main函数可以接收命令行参数:
c复制#include <stdio.h>
int main(int argc, char *argv[])
{
printf("程序名:%s\n", argv[0]);
printf("参数个数:%d\n", argc-1);
for(int i=1; i<argc; i++)
{
printf("参数%d:%s\n", i, argv[i]);
}
return 0;
}
运行程序时,参数以空格分隔。例如:./program arg1 arg2。
5. 常见问题与调试技巧
5.1 初学者常见错误
-
忘记初始化指针
c复制int *p; // 未初始化 *p = 10; // 危险! -
数组越界访问
c复制int arr[5]; arr[5] = 10; // 越界 -
字符串没有预留空间给结束符'\0'
c复制char name[5] = "Smith"; // 需要6个字节空间 -
混淆=和==
c复制if(a = 10) // 总是为真,因为这是赋值不是比较
5.2 调试技巧
- 使用printf调试:在关键位置打印变量值
- 使用assert进行断言检查
c复制#include <assert.h> assert(ptr != NULL); - 分步测试:先测试小段代码,确认无误后再继续
- 代码审查:逐行检查逻辑是否正确
- 使用调试器(如gdb)进行单步调试
5.3 性能优化建议
- 减少函数调用开销:对小而频繁调用的函数考虑内联
- 避免不必要的内存分配:特别是循环中的malloc/free
- 使用局部变量而非全局变量:访问速度更快
- 注意数据缓存:顺序访问比随机访问快得多
- 选择合适的数据结构:数组vs链表,根据场景选择
在实际嵌入式开发中,C语言的这些基础概念会不断被应用和深化。指针操作、内存管理和位操作是嵌入式C编程的核心技能。建议通过实际项目练习,比如开发简单的硬件驱动程序或嵌入式应用,来巩固这些基础知识。