目录
1.2. 浮点型(Floating-Point Types)
C语言是一种广泛使用的高级编程语言,它支持过程化编程、模块化编程和结构化编程。
一、数据类型
在C语言中,基本数据类型是构建程序的基础。这些类型允许声明变量以存储不同类型的数据。以下是C语言中几种基本数据类型的汇总,包括整型(int)、浮点型(float, double)、字符型(char)的详细说明。
1.1. 整型(Integer Types)
整型用于存储整数值,没有小数部分。C语言标准定义了多种整型,但最基本的是int
,它的大小(即它能表示的数值范围)依赖于编译器和操作系统,但通常是16位、32位或64位。
示例:
#include <stdio.h>
int main() {
int a = 10;
float b = 3.14;
char c = 'A';
printf("整型: %d, 浮点型: %f, 字符型: %c\n", a, b, c);
return 0;
}
1.2. 浮点型(Floating-Point Types)
浮点型用于存储有小数部分的数值。C语言提供了两种基本的浮点类型:float
和double
。float
通常提供单精度(大约7位十进制数字的精度),而double
提供双精度(大约15-17位十进制数字的精度)。
示例:
#include <stdio.h>
int main() {
float pi = 3.14f; // 注意f后缀,表示这是一个float字面量
double e = 2.71828;
printf("pi = %f, e = %lf\n", pi, e); // 使用%lf打印double类型
return 0;
}
1.3. 字符型(Character Types)
字符型用于存储单个字符,如字母、数字或标点符号。C语言中的字符型是char
。在大多数系统上,char
类型实际上是使用整型来存储的,但它被特别设计来存储字符。
示例:
#include <stdio.h>
int main() {
char ch = 'A';
printf("ch = %c\n", ch); // 使用%c打印字符
printf("ch的ASCII码 = %d\n", ch); // 字符也可以按整数打印,显示其ASCII码
return 0;
}
1.4. 注意事项
- 在打印
float
和double
类型的变量时,%f
用于float
和double
,但当想要明确指出一个double
类型的变量时(尤其是在某些编译器或设置下),使用%lf
是更好的做法。 - 字符常量(如
'A'
)用单引号括起来,而字符串常量(如"Hello, World!"
)用双引号括起来。 - C语言还提供了其他整型,如
short
、long
、long long
以及它们的无符号版本(通过在类型前加unsigned
关键字),用于存储不同范围的整数值。 - 浮点数的精度是有限的,因此在进行浮点数运算时要特别小心,以避免精度丢失或舍入错误。
二、变量声明与初始化
在C语言中,变量在使用前需要先声明其类型,变量声明是告诉编译器将要使用的一个或多个变量的名称以及它们的数据类型。变量可以在声明时初始化,也可以稍后在程序中赋值。
2.1. 变量声明
变量声明指定了变量的类型,并给变量分配了内存空间(但不分配初始值,除非同时初始化)。
语法:
type variable_name;
其中type
是变量的数据类型(如int
、float
、char
等),variable_name
是变量的名称。
2.2. 变量初始化
变量初始化是在声明变量的同时给它赋予一个初始值。
语法:
type variable_name = initial_value;
其中initial_value
是与变量类型兼容的初始值。
2.3. 示例
#include <stdio.h>
int main() {
// 变量声明
int a;
float b;
char c;
// 变量初始化
int d = 10;
float e = 3.14;
char f = 'A';
// 声明并初始化
int g = 20;
double h = 2.71828;
// 使用变量
a = 5; // 赋值
printf("a = %d, d = %d\n", a, d);
printf("e = %f, f = %c\n", e, f);
printf("g = %d, h = %lf\n", g, h);
return 0;
}
2.4. 注意事项
- 变量名必须以字母或下划线
_
开头,后面可以跟任意数量的字母、数字或下划线。 - 变量名是区分大小写的。
- 变量在声明之后、初始化之前,其值是未定义的(对于局部变量而言)。对于全局变量和静态变量,如果未显式初始化,则它们的值会被自动初始化为0(对于数值类型)或
\0
(对于字符类型)。 - 初始化是可选的,但推荐在声明时初始化变量,以避免使用未定义的值。
- 在C99及以后的标准中,允许在for循环等控制结构的初始化部分中声明变量(这被称为块作用域变量),但在C90及以前的标准中,所有变量都必须在块的开始处声明。
三、运算符
C语言中的运算符用于执行各种算术运算、关系测试、逻辑操作等。下面是对C语言中几类常用运算符的汇总。
3.1. 算术运算符
- 加法(
+
):用于加法运算。 - 减法(
-
):用于减法运算。 - 乘法(
*
):用于乘法运算。 - 除法(
/
):用于除法运算。如果两个操作数都是整数,则结果也将是整数(向下取整)。 - 取模(
%
):也称为模除或求余运算,返回两数相除的余数。
示例:
#include <stdio.h>
int main() {
int a = 10, b = 3;
printf("a + b = %d\n", a + b);
printf("a - b = %d\n", a - b);
printf("a * b = %d\n", a * b);
printf("a / b = %d\n", a / b);
printf("a %% b = %d\n", a % b); // 注意:%需要被转义
return 0;
}
3.2. 关系运算符
- 大于(
>
):如果左侧操作数大于右侧操作数,则结果为真(1)。 - 小于(
<
):如果左侧操作数小于右侧操作数,则结果为真(1)。 - 等于(
==
):如果两侧操作数相等,则结果为真(1)。 - 不等于(
!=
):如果两侧操作数不相等,则结果为真(1)。 - 大于等于(
>=
):如果左侧操作数大于等于右侧操作数,则结果为真(1)。 - 小于等于(
<=
):如果左侧操作数小于等于右侧操作数,则结果为真(1)。
示例:
#include <stdio.h>
int main() {
int x = 5, y = 10;
printf("x > y: %d\n", x > y);
printf("x < y: %d\n", x < y);
printf("x == y: %d\n", x == y);
printf("x != y: %d\n", x != y);
return 0;
}
3.3. 逻辑运算符
- 逻辑与(
&&
):如果两侧操作数都为真(非零),则结果为真(1)。 - 逻辑或(
||
):如果两侧操作数中至少有一个为真(非零),则结果为真(1)。 - 逻辑非(
!
):如果操作数为假(0),则结果为真(1);如果操作数为真(非零),则结果为假(0)。
示例:
#include <stdio.h>
int main() {
int a = 5, b = 10;
printf("a > 5 && b > 10: %d\n", a > 5 && b > 10);
printf("a > 5 || b > 5: %d\n", a > 5 || b > 5);
printf("!(a > b): %d\n", !(a > b));
return 0;
}
3.4. 注意事项
- 逻辑运算符的结果通常是整数
1
(真)或0
(假),但在条件表达式(如if
语句)中,它们通常被隐式地转换为布尔值。 - 关系运算符的结果也是整数
1
(真)或0
(假),用于表示条件测试的结果。 - 在使用取模运算符时,如果左侧操作数是负数,则结果的符号取决于编译器和平台(有的平台结果为正,有的为负)。
- 在进行除法运算时,如果除数为零,将导致运行时错误(通常是除以零的错误)。
四、控制语句
在C语言中,控制语句用于控制程序的流程,根据条件或迭代的需要执行不同的代码块。下面是对几种常见的控制语句的汇总。
4.1. if-else 条件判断
if-else
语句用于基于不同条件执行不同代码块。
示例:
#include <stdio.h>
int main() {
int num = 10;
if (num > 0) {
printf("Positive number\n");
} else {
printf("Non-positive number\n");
}
return 0;
}
4.2. for 循环
for
循环用于重复执行一段代码固定次数。
示例:
#include <stdio.h>
int main() {
for (int i = 0; i < 5; i++) {
printf("Number %d\n", i);
}
return 0;
}
4.3. while 循环
while
循环在给定条件为真时重复执行一段代码。
示例:
#include <stdio.h>
int main() {
int i = 0;
while (i < 5) {
printf("Number %d\n", i);
i++;
}
return 0;
}
4.4. do-while 循环
do-while
循环至少执行一次代码块,然后检查条件;如果条件为真,则继续执行。
示例:
#include <stdio.h>
int main() {
int i = 0;
do {
printf("Number %d\n", i);
i++;
} while (i < 5);
return 0;
}
4.5. switch 语句
switch
语句允许一个变量(或表达式)被测试,并使得程序可以跳转至多个不同代码块中的一个去执行。
示例:
#include <stdio.h>
int main() {
char grade = 'B';
switch (grade) {
case 'A':
printf("Excellent!\n");
break;
case 'B':
case 'C':
printf("Well done\n");
break;
case 'D':
printf("You passed\n");
break;
case 'F':
printf("Better try again\n");
break;
default:
printf("Invalid grade\n");
}
return 0;
}
4.6. 注意
- 在
switch
语句中,break
语句是必须的,除非你希望执行完一个case
后自动进入下一个case
执行(这种用法被称为“fall-through”,但通常不是好的编程实践)。 if-else
、for
、while
和do-while
循环是流程控制的基础,几乎在每个程序中都会用到。switch
语句提供了一种比多个if-else
语句更清晰的方式来处理多个选项。但是,switch
语句中的表达式通常只限于整型(包括字符类型,因为字符在内部是以它们的ASCII值表示的整型)或枚举类型。在C99及以后的标准中,也可以是字符串字面量(尽管这在实践中不太常见)。
五、数组
数组是一种基础且强大的数据结构,它允许程序员以连续的内存空间存储相同类型的多个元素。在C语言中,数组是一个非常重要的概念,它被广泛用于各种编程任务中。以下是数组的汇总。
5.1. 数组的基本特点
- 固定大小:数组在声明时就确定了可以存储的元素数量,这个数量在数组的生命周期内不会改变。
- 同类型元素:数组中的所有元素都必须是相同的数据类型。
- 索引访问:数组中的每个元素都可以通过索引(或下标)来访问,索引通常是从0开始的。
- 连续内存:数组中的元素在内存中连续存储,这使得访问数组元素非常快速。
5.2. 数组的声明和初始化
- 声明数组:
int arr[10]; // 声明一个可以存储10个整数的数组
- 初始化数组:
int arr[5] = {1, 2, 3, 4, 5}; // 声明并初始化一个包含5个整数的数组
如果初始化时提供的元素少于数组的大小,剩余的元素将自动初始化为0(对于数值类型数组)。
5.3. 数组的使用
- 访问数组元素:
int secondElement = arr[1]; // 访问数组的第二个元素(索引为1)
- 遍历数组:
for(int i = 0; i < 5; i++) {
printf("%d ", arr[i]); // 打印数组中的每个元素
}
5.4. 示例:计算数组的平均值
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int sum = 0;
float average;
// 计算数组元素的总和
for(int i = 0; i < 5; i++) {
sum += arr[i];
}
// 计算平均值
average = (float)sum / 5;
// 打印平均值
printf("Average: %.2f\n", average);
return 0;
}
5.5. 注意事项
- 数组越界:访问数组时,如果索引超出了数组的界限(即小于0或大于等于数组的大小),则会导致未定义行为,通常是程序崩溃或数据损坏。
- 数组大小是编译时确定的,因此数组的大小必须是常量表达式。
- 在C语言中,数组名代表数组首元素的地址,因此数组名在表达式中通常被当作指向数组首元素的指针。
六、字符串
在C语言中,字符串是通过字符数组来处理的,其中数组的最后一个元素是一个空字符(\0
),也称为null终止符,用于标识字符串的结束。这使得C语言中的字符串具有固定的结束标志,从而便于函数如strlen
、strcpy
等知道何时停止处理字符串。
6.1. 字符串的定义
字符串可以通过字符数组直接定义,也可以在运行时通过字符数组赋值。
-
直接定义
char str[] = "Hello, World!";
这里,str
是一个字符数组,它被初始化为包含字符串"Hello, World!"
和结尾的空字符\0
。
-
运行时赋值
char str[50];
strcpy(str, "Hello, World!");
这里,str
是一个可以存储49个字符加上一个结尾空字符的数组。使用strcpy
函数将字符串"Hello, World!"
复制到str
中。注意,这里需要确保目标数组str
有足够的空间来存储要复制的字符串,包括结尾的空字符。
6.2. 字符串的示例
下面是一个简单的C程序,展示了如何定义和使用字符串:
#include <stdio.h>
#include <string.h> // 引入字符串处理函数
int main() {
char str1[] = "Hello, ";
char str2[] = "World!";
char str3[50]; // 足够大以存储两个字符串拼接后的结果
// 字符串拼接
strcpy(str3, str1);
strcat(str3, str2); // strcat用于拼接字符串,需要包含<string.h>
// 打印结果
printf("Concatenated String: %s\n", str3);
// 获取字符串长度
printf("Length of the concatenated string: %lu\n", strlen(str3));
return 0;
}
这个程序首先定义了两个字符串str1
和str2
,然后使用strcpy
将str1
复制到str3
中,接着使用strcat
将str2
追加到str3
的末尾。最后,程序使用printf
和strlen
函数分别打印出拼接后的字符串和它的长度。
6.3. 注意事项
- 字符串在C语言中是以null终止的字符数组,这意味着你不能直接使用字符串字面量(如
"Hello, World!"
)作为函数参数,除非该函数明确设计为接受字符指针或字符数组(实际上,字符串字面量在大多数上下文中会被自动转换为指向其首字符的指针)。 - 使用
strcpy
、strcat
等字符串处理函数时,必须确保目标数组有足够的空间来存储结果字符串,包括结尾的空字符。否则,可能会发生缓冲区溢出,这是常见的安全漏洞之一。 - 在处理字符串时,经常需要包含
<string.h>
头文件,因为它提供了许多有用的字符串处理函数。
七、函数
在C语言中,函数是执行特定任务的独立代码块。通过定义函数,可以将复杂的程序分解为更小、更易于管理的部分,并且可以重用这些函数来执行相同的任务,而无需重复编写相同的代码。
7.1. 函数的定义
函数定义的一般形式如下:
返回类型 函数名(参数类型 参数名1, 参数类型 参数名2, ...) {
// 函数体
// 返回值语句(如果函数有返回值)
return 返回值;
}
- 返回类型:指定函数返回值的类型。如果函数不返回任何值,则使用
void
类型。 - 函数名:是函数的唯一标识符,用于调用函数。
- 参数列表:函数可以接收零个或多个参数,每个参数都包括参数类型和参数名。参数是可选的,用于向函数传递数据。
- 函数体:包含执行特定任务的语句。
- 返回值:如果函数有返回值,则使用
return
语句返回该值。如果函数类型为void
,则不需要return
语句,或者可以使用return;
来结束函数。
7.2. 函数的调用
调用函数的基本语法是:
函数名(参数1, 参数2, ...);
7.3. 示例:计算两个数的和
下面是一个简单的示例,展示了如何定义一个计算两个整数和的函数,并调用它。
#include <stdio.h>
// 定义一个函数,用于计算两个整数的和
int sum(int a, int b) {
return a + b;
}
int main() {
int num1 = 5, num2 = 10;
int result;
// 调用函数,并将结果存储在变量result中
result = sum(num1, num2);
// 打印结果
printf("The sum of %d and %d is %d\n", num1, num2, result);
return 0;
}
在这个示例中,sum
函数接收两个整数作为参数,并返回它们的和。在main
函数中,我们调用了sum
函数,并将结果存储在变量result
中,然后打印出来。
7.4. 注意事项
- 函数定义必须在调用它之前,或者至少要在调用它的代码之前声明函数原型。函数原型告诉编译器函数的返回类型、名称和参数类型,但不包括函数体。
- 函数可以嵌套调用,即一个函数可以调用另一个函数。
- 递归函数是一种特殊的函数,它直接或间接地调用自身。递归函数需要谨慎使用,以避免无限递归和栈溢出。
- 函数的参数是通过值传递的,这意味着传递给函数的参数是原始数据的副本。在函数内部对参数所做的任何修改都不会影响原始数据(除非参数是指针)。
八、指针
指针是C语言中一个极其重要的概念,它允许程序直接访问和操作内存地址。通过指针,我们可以动态地分配内存、传递数组或结构体等大型数据结构给函数,以及实现复杂的数据结构和算法。以下是对指针的汇总。
8.1. 指针的基本概念
- 指针变量:指针变量用于存储变量的内存地址。指针变量的类型决定了它所能指向的变量的类型。
- 解引用:通过指针访问它所指向的变量的值的过程称为解引用。在C语言中,我们使用
*
运算符来解引用指针。 - 指针的声明:声明指针变量时,需要在变量类型前加上
*
符号。例如,int *ptr;
声明了一个指向int
类型变量的指针ptr
。 - 指针的赋值:指针可以被赋值为一个变量的地址,这通常通过取地址运算符
&
来实现。例如,ptr = &var;
将变量var
的地址赋给指针ptr
。 - 指针的算术运算:指针可以进行算术运算,如递增(
ptr++
)和递减(ptr--
),这些操作实际上是按照指针所指向的数据类型的大小来移动指针的。
8.2. 指针的示例
示例1:基本指针操作
#include <stdio.h>
int main() {
int var = 20; // 声明一个整型变量
int *ptr; // 声明一个整型指针
ptr = &var; // 将var的地址赋给ptr
printf("Value of var: %d\n", var);
printf("Value of *ptr: %d\n", *ptr); // 解引用ptr,打印var的值
return 0;
}
示例2:指针与数组
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50}; // 声明一个整型数组
int *ptr; // 声明一个整型指针
ptr = arr; // 数组名在表达式中通常被当作指向数组首元素的指针
for(int i = 0; i < 5; i++) {
printf("Value of arr[%d]: %d\n", i, *(ptr + i)); // 使用指针算术访问数组元素
}
return 0;
}
示例3:动态内存分配
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr;
int n = 5; // 假设我们想动态分配一个包含5个整数的数组
// 使用malloc动态分配内存
ptr = (int*)malloc(n * sizeof(int));
if(ptr == NULL) {
printf("Memory not allocated.\n");
exit(0);
}
// 使用指针来访问和修改分配的内存
for(int i = 0; i < n; i++) {
*(ptr + i) = i * i; // 将平方值存储在数组中
}
// 打印数组内容
for(int i = 0; i < n; i++) {
printf("%d ", *(ptr + i));
}
// 释放分配的内存
free(ptr);
return 0;
}
8.3. 注意事项
- 指针必须在使用前被初始化,否则它们将包含不确定的值,这可能导致程序崩溃或不可预测的行为。
- 使用指针时要小心内存泄漏,确保分配的内存最终被释放。
- 指针算术运算仅适用于指向数组元素的指针,并且结果指针必须指向数组的有效范围内。
- 指针解引用时必须确保指针不是
NULL
,否则会导致程序崩溃。
九、总结
C语言基础语法包括变量声明、条件语句(if-else)、循环结构(for, while, do-while)、函数定义与调用、指针操作、数组与字符串处理。掌握这些语法,能构建复杂程序逻辑,处理数据输入输出。通过实践和学习这些基础,可以进一步探索C语言的高级特性和应用。