作为一个小白,在各大视频和参考书籍(C Primer Plus等)的帮助下,马马虎虎完成了C基础的理论学习和部分项目的实现,以下是我学习的相关书籍顺序以及我认为平常学习需要注意的点,个人拙见,可能存在不足和错误,望大家慷慨指导,谢谢!
1. C语言的概述及Ubuntu上编写程序的操作
C语言的概述:
C语言是一种通用的高级编程语言,由Dennis Ritchie在20世纪70年代早期在贝尔实验室开发。C语言以其简洁、高效和灵活的特点而受到广泛欢迎。它在系统编程、嵌入式系统和操作系统开发中得到广泛应用。
Ubuntu上编写程序的操作:
1. 安装编译器:Ubuntu默认安装了GNU C编译器(GCC)。如果没有安装,可以在终端中运行以下命令安装:`sudo apt-get install gcc`
2. 编写代码:使用文本编辑器(如`nano`、`vim`或`gedit`)编写C代码,并将其保存为.c文件。
3. 编译代码:在终端中,使用GCC编译器将C代码编译成可执行文件。命令格式:`gcc a.c`
4. 运行程序:在终端中,通过输入可执行文件名来运行程序。例如:`./a.out`
2. C语言程序的组成
1.预处理指令:
预处理指令以`#`符号开头,告诉编译器在实际编译之前对代码进行预处理。例如,`#include`用于包含头文件,`#define`用于定义常量。
2.函数定义:
C程序由一个或多个函数组成。每个C程序至少有一个主函数(`main`函数),它是程序执行的起点。
3.全局变量声明:
全局变量在函数外部定义,可以在整个程序中使用。它们在函数调用之间保持值的持久性。
4.主函数:
主函数(`main`函数)是C程序的起点,程序从这里开始执行。它必须存在,并且只能有一个。
3. C语言的基本构成元素和数据类型
1.标识符:
在C语言中,标识符是用来标识变量、函数、结构等的名称。标识符由字母、数字和下划线组成,必须以字母或下划线开头。
2.关键字(32个):
关键字是C语言中预定义的具有特殊含义的单词,例如`int`、`if`、`else`等。它们不能用作标识符。
参考百度百科:
auto :声明自动变量
break:跳出当前循环
case:开关语句分支
const :声明只读变量
continue:结束当前循环,开始下一轮循环
default:开关语句中的“默认”分支
double :声明双精度浮点型变量或函数返回值类型
else :条件语句否定分支(与 if 连用)
enum :声明枚举类型
extern:声明变量或函数是在其它文件或本文件的其他位置定义
float:声明浮点型变量或函数返回值类型
for:一种循环语句
goto:无条件跳转语句
if:条件语句
int: 声明整型变量或函数
long :声明长整型变量或函数返回值类型
register:声明寄存器变量
short :声明短整型变量或函数
static :声明静态变量
switch :用于开关语句
typedef:用以给数据类型取别名
unsigned:声明无符号类型变量或函数
3.常量:
常量是在程序中固定不变的值,可以是整数常量、浮点数常量、字符常量或字符串常量。
4.变量:
变量是用于存储数据的内存位置。在使用变量之前,必须先声明其数据类型。
变量分为:整型变量([ ]int),实型变量(float, double, long double),字符型变量(char)。
5.数据类型:
C语言提供了基本数据类型,如整型(`int`)、字符型(`char`)、浮点型(`float`)等。还有派生数据类型,如数组、结构体和指针。
注意:
1.理解标识符的命名规则和约定,避免命名冲突和错误。标识符由字母、数字和下划线组成,必须以字母或下划线开头。
2.理解不同数据类型的用途和表示范围,特别是在进行数据类型转换时要注意数据溢出和精度丢失。
3.auto、static、register、extern变量的作用及具体用法。
4.隐式类型转换和强制类型转换的使用,C语言强转中要把类型说明符给括起来,k=(int)(a+b);
4. 运算符和表达式
1.算术运算符:
C语言提供了基本的算术运算符,如`+`、`-`、`*`、`/`和`%`。它们用于执行基本的算术计算。
2.逻辑运算符:
逻辑运算符用于执行逻辑操作,如与(`&&`)、或(`||`)和非(`!`)。
3.关系运算符:
关系运算符用于比较两个值,如等于(`==`)、不等于(`!=`)、大于(`>`)、小于(`<`)、大于等于(`>=`)和小于等于(`<=`)。
4.赋值运算符:
赋值运算符(`=`)用于将值赋给变量。a=10;即将10赋值给变量a。
5.位运算 计算机中特有的运算 所有运算目标 均针对二进制而言
按位 & 将二进制中的每一位进行与操作 与0得0 与1不变
按位 | 将二进制中的每一位进行或操作 或1得1 或0不变
按位取反 ~ 单目运算
按位异或 ^ 相同为0 不同为1
移位运算:
逻辑左移 << 高位移出丢弃 低位补0
算数右移 >> 低位移出对齐 无符号数 高位补0 有符号数 高位补符号位。
6. 表达式:
表达式由运算符和操作数组成,用于执行计算并生成结果。表达式的结果可以是常量、变量或复杂的算术值。
注意:
1.熟悉各种运算符的优先级和结合性,避免表达式计算出现错误。在表达式中使用括号来明确优先级。如:a=2, 5-1, 4+3, 6*9; 此时a的值为2,不是54,因为赋值运算符的优先级高于逗号运算符,可以加()以提升逗号运算符的优先级。
2.注意逻辑运算符的短路特性,以避免不必要的计算:
(表达式1)&&(表达式2):
如果表达式1为假,则表达式2不会进行运算,因为此时不管表达式2值为什么,输出都为0,编译器直接跳过表达式2,即表达式2“被短路”。
(表达式1)||(表达式2):
如果表达式1为真,则表达式2不会进行运算,即表达式2“被短路”。
若表达式2存在’=’赋值运算,如a=3,此时3因为短路特性将不会被赋值到a中,若此时求a的值,输出不为3,特别注意。
5. 用户与计算机交互——输入/输出
1.输出函数(`printf`):
`printf`函数用于将数据输出到屏幕上。它可以输出字符串、变量的值以及其他格式化的输出。
2.输入函数(`scanf`):
`scanf`函数用于从用户获取输入数据。它根据格式字符串读取用户输入,并将输入的值存储在变量中。
注意:
1.在使用printf函数时,注意控制输出格式,以确保输出内容的正确性和可读性。使用格式化字符串,如%d、%f、%c和%s来输出不同类型的数据。
2.使用scanf函数时,注意对输入的验证和错误处理,以避免程序崩溃或陷入死循环。在输入数据时,确保输入的数据类型和格式与scanf函数中的格式字符串相匹配。
6. 分支结构程序设计
1.`if`语句:
`if`语句用于根据条件执行不同的代码块。如果条件为真,执行`if`语句后的代码块;否则,跳过`if`语句,执行其他代码。
2.`else`语句:
`else`语句用于在条件为假时执行另一个代码块。
3.`switch`语句:
`switch`语句根据表达式的值,选择执行不同的代码块。它可以替代多个`if-else`语句。
注意:
1.对于多个条件的判断,理解if-else和switch语句的选择使用,以及注意避免嵌套过多的if-else语句。
2.在使用switch语句时,注意每个case后面需要添加break语句,以防止出现不必要的执行。但针对特定情形可以利用switch…case…不加break完成程序的连续累加。例如,求某个日期是当年的第几天,可以通过这一特性将每月的天数累加到一个变量中。
7. 循环结构与转移语句
1. `for`循环:
`for`循环用于重复执行一段代码,它有初始化、条件和更新三个部分,可以在循环执行之前和之后执行一些操作。
2. `while`循环:
`while`循环在循环执行之前检查条件是否为真,如果为真,则执行循环内的代码。
3. `do-while`循环:
`do-while`循环先执行一次循环内的代码,然后在循环条件为真时重复执行。
4. 转移语句:
`break`语句用于在循环中提前跳出循环。`continue`语句用于结束当前循环迭代,进入下一次循环迭代。
5.嵌套循环:
指在一个循环中包含另一个循环。常用于按行和列显示数据,就是外循环处理行,内循环处理列。
#include <stdio.h>
int main() {
int rows = 5;
for (int i = 1; i <= rows; i++) {
// 打印空格
for (int j = 1; j <= rows - i; j++) {
printf(" ");
}
// 打印星号
for (int k = 1; k <= i; k++) {
printf("* ");
}
printf("\n");
}
return 0;
}
输出的结果如图:
*
* *
* * *
* * * *
* * * * *
注意:
1.理解循环结构的退出条件和循环体的执行次数,避免无限循环,如while(1),for(;;){}。
2.注意在使用转移语句时,确保程序逻辑正确,避免使用过多的转移语句造成程序流程混乱。
8. 数组
1.一维数组:
一维数组是由相同类型的元素组成的数据结构,通过索引可以访问数组中的元素。
2. 二维数组:
二维数组是由多个一维数组组成的数据结构,可以使用两个索引来访问数组中的元素。
3.字符数组:
字符数组是用于存储字符串的一种特殊的一维数组。
4.数组的排序算法:
常见的排序算法,如冒泡排序、插入排序和快速排序等,用于对数组进行排序。
5.字符串处理函数:
C语言提供了许多用于处理字符串的库函数,例如`strlen`、`strcpy`和`strcat`等。
6.数组的遍历:
数组是C语言中的一种数据结构,用于存储一系列相同数据类型的元素。数组提供了一种有效的方式来组织和访问大量相似数据,并且在程序中使用广泛。
声明和初始化数组:
在C语言中,声明数组需要指定数组的数据类型和数组名,并使用方括号`[]`来表示数组的大小。
// 声明一个整型数组arr,包含5个元素
int arr[5];
数组的元素可以通过索引访问,数组索引从0开始,直到数组大小减1。
数组的初始化可以在声明时进行,也可以在后续代码中逐个赋值。以下是两种初始化数组的方式:
// 声明并初始化整型数组arr
int arr1[5] = {1, 2, 3, 4, 5};
// 声明整型数组arr,并逐个赋值
int arr2[5];
arr2[0] = 1;
arr2[1] = 2;
arr2[2] = 3;
arr2[3] = 4;
arr2[4] = 5;
数组的大小是固定的,一旦定义后,大小不能再改变。
访问数组元素:
数组元素可以通过数组名和索引来访问。索引用于标识数组中的元素位置,可以是整型表达式或常量。
int arr[5] = {10, 20, 30, 40, 50};
// 访问数组元素
int firstElement = arr[0]; // 第一个元素,值为10
int thirdElement = arr[2]; // 第三个元素,值为30
// 修改数组元素
arr[1] = 25; // 将第二个元素的值修改为25
需要注意的是,数组索引应该在合法范围内,否则会导致访问越界,可能引发未定义行为或程序崩溃。
多维数组:
C语言支持多维数组,即数组中的每个元素也是一个数组。常见的二维数组是最常用的多维数组。
// 声明一个二维整型数组matrix,包含2行3列
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
多维数组的访问需要指定每个维度的索引。
// 访问二维数组元素
int element = matrix[1][2]; // 访问第2行第3列的元素,值为6
数组的传递和函数:
在函数中,数组可以作为参数传递。数组作为函数参数时,传递的是数组的地址。因此,函数内部可以直接操作原始数组。
void printArray(int arr[ ], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[ ] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);
printArray(arr, size); // 数组作为参数传递给函数
return 0;
}
总结:
数组是C语言中用于存储一系列相同数据类型元素的数据结构。通过索引,可以访问数组中的每个元素。数组的大小在声明时确定,一旦定义后,大小不能再改变。C语言支持多维数组,使得数组可以用于更复杂的数据组织。在函数中,数组作为参数传递时,传递的是数组的地址,函数内部可以直接操作原始数组。使用数组可以提高程序的效率和可读性,是C语言中常用的数据结构。
注意:
1.理解数组的内存布局和索引,特别注意数组越界访问的问题。数组越界可能导致程序崩溃或产生未定义行为。
2.理解字符数组与字符串的关系,以及字符串处理函数的正确使用。字符串在C语言中以null字符\0结尾。
9. 函数
1.函数的定义和调用:
函数的定义和调用是C语言中非常重要的概念。函数允许我们将代码划分成独立的块,并在程序中多次使用。下面我们来详细讲解函数的定义和调用:
函数的定义:
在C语言中,函数的定义包括函数头和函数体。函数头指定了函数的返回类型、函数名和参数列表,函数体包含了函数的具体实现。
函数定义的一般形式如下:
返回类型 函数名(参数列表) {
// 函数体
// 在这里编写具体的代码
return 返回值; // 返回值的类型必须与函数声明中的返回类型匹配
}
其中:
返回类型:指定函数返回的数据类型。C语言支持多种返回类型,如整型(`int`)、浮点型(`float`)、字符型(`char`)等。
函数名:标识函数的名称。函数名必须是一个有效的标识符,用于在程序中调用函数。
参数列表:指定函数接受的输入参数。参数列表包含了参数的数据类型和名称,多个参数之间用逗号分隔。如果函数不需要接受参数,可以使用空的参数列表`void`表示。
以下是一个简单的函数定义的例子:
// 函数定义,该函数接受两个整型参数并返回它们的和
int add(int a, int b) {
int sum = a + b;
return sum; // 返回计算结果
}
函数的调用:
函数定义只是定义了函数的结构和实现,并没有实际执行。要调用函数,需要在程序的其他地方使用函数名加上一对小括号,并在括号内传递函数所需的参数。
函数调用的一般形式如下:
返回值变量 = 函数名(参数1, 参数2, ...);
其中:
返回值变量:用于接收函数的返回值。函数的返回值可以是任何数据类型,需要用一个对应的变量来接收。
函数名:指定要调用的函数的名称。
参数1, 参数2, ...:是传递给函数的实际参数。这些参数的数据类型和顺序必须与函数定义中的参数列表匹配。
以下是使用上面定义的`add`函数进行调用的例子:
int result = add(5, 3); // 调用add函数,传递参数5和3,并将返回值保存到result变量中
在调用函数时,程序会跳转到函数的定义位置,执行函数体中的代码,然后将结果返回到调用处。函数调用使得程序结构更加清晰,能够重复使用特定功能的代码块。
总结:函数的定义和调用是C语言中重要的概念。定义函数时需要指定返回类型、函数名和参数列表,并在函数体内实现具体的功能。调用函数时,需要使用函数名并传递相应的参数,以获取函数的返回值或执行特定的功能。函数的使用使得程序模块化,提高了代码的可读性和重用性。
2.函数参数传递:
函数参数传递的不同方式,包括值传递(单向的,实参到形参)、指针传递(双向,但是回来的路与去的路不一样,相当于进入另一个车道回来,而不是原车道。)和引用传递。
在C语言中,没有显式的引用类型,但可以通过指针来模拟引用传递。引用传递与指针传递类似,但更直观,更容易理解。
// 引用传递示例(使用指针模拟引用传递)
void swap(int &x, int &y) {
int temp = x;
x = y;
y = temp;
}
int main() {
int a = 5, b = 10;
swap(a, b); // 通过传递a和b的引用(指针)来进行参数传递
printf("a = %d, b = %d\n", a, b); // 输出结果为 "a = 10, b = 5"
return 0;
}
注意:C语言中没有直接的引用类型,引用传递是通过传递指针来实现的。在C++中有引用类型,可以更直接地实现引用传递。
3.递归函数:
了解递归函数的概念和用法,以及递归函数的优缺点和适用条件。
4.编译多源代码文件的程序:
在Linux系统中安装GNU C编译器GCC。编译两个内含C函数的文件,用gcc a.c b.c生成名为a.out的可执行文件,这是C语言模块化的直接体现,C语言模块化离不开函数。
注意:
1.理解函数的返回值和参数传递,注意函数调用时参数的匹配。
2.在使用递归函数时,注意控制递归深度和递归结束条件,以防止栈溢出和无限递归。
3.C语言模块化是为了方便一个项目进行分工协作,最后再由“总工”合并,完成一个项目。