C语言入门~又爱又恨

写在前面

果然,学完了SQL的我又想来霍霍C语言了~

环境搭建

  • MacBookPro 2018 13(丐中丐版)+ Mojeve
  • Xcode (在App Store就可以安装,用Xcode麻烦少很多)
  • CLion(edu邮箱注册,教育优惠可以免费用一年)

配置 CLion

启动CLion后在设置中选择”Build, Execution, Deployment“ -> “Toolchains“,选择 “Xcode“ 作为默认工具链。

创建新项目

在 CLion 中选择 “File“ -> “New Project“,在左侧菜单中选择 “C Executable“。在右侧面板中选择项目名称、路径等信息,并设置工具链为 “Xcode“。

写代码

创建一个新的.c文件,输入C语言代码。CLion提供了丰富的代码编辑功能,例如语法高亮、自动补全等。

构建和运行

在CLion中选择”Run“ -> “Run“来构建和运行程序。运行之后,程序的输出会显示在控制台窗口。

需要注意的是,如果在Xcode中安装了其他编译器,也可以在CLion中进行配置并使用。只需在CLion的”Toolchains“ 设置中选择其他编译器即可。

C语言入坑指南

首先当然是基本语法:

  • C语言中的保留字
  • 变量、常量
  • 库函数的使用
  • 输入、输出
  • 条件控制
  • 循环语句

接着学习更复杂的操作:

  • 子程序设计、递归
  • 数组
  • 指针
  • 结构体
  • 文件操作

其实到了这一步,难度都不会是语言本身,而是算法、数据结构以及计算机基础。

零碎知识点

变量

整型变量(int):整型变量用于存储整数值,可以表示正数、负数和零。

整型变量在内存中占用固定的字节数,通常是4个字节。

1
int num = 10; // 声明一个整型变量num 来存储整数值10。

浮点型变量(float和double):浮点型变量用于存储实数值,可以表示小数和指数形式的数值。

浮点型变量在内存中占用的字节数通常比整型变量多,因为它们需要存储小数部分。

C语言提供了两种浮点型变量,分别是单精度浮点型(float)和双精度浮点型(double)。

1
double num = 3.14; // 声明一个双精度浮点型变量来存储小数值3.14。

字符型变量(char):字符型变量用于存储单个字符,例如字母、数字或标点符号。

字符型变量在内存中占用1个字节,因为一个字符通常只需要8个比特位来表示。

1
char ch = 'A'; // 声明一个字符型变量来存储字符'A'。

长整型变量(long):长整型变量用于存储较大的整数值,通常占用8个字节。

它们比普通整型变量更适合用于存储非常大的整数值。

1
long num = 1234567890; // 声明一个长整型变量来存储整数值1234567890。

短整型变量(short):短整型变量用于存储较小的整数值,通常占用2个字节。

它们比普通整型变量更适合用于存储非常小的整数值。

1
short num = 10; // 声明一个短整型变量来存储整数值10。

常量

常量是指在程序运行时其值不会发生变化的量,它们可以帮助我们简化代码和提高程序的可读性。常量可以是整数、浮点数、字符或字符串等类型。

1、整数常量

整数常量是指不带小数部分的数字,可以是正数、负数或零。在C语言中,整数常量默认是十进制数,也可以使用其他进制表示。下面是几个整数常量的例子:

1
2
3
4
42   // 十进制整数常量
-15 // 负整数常量
0x7F // 十六进制整数常量
0o57 // 八进制整数常量

2、浮点数常量
浮点数常量是指带有小数部分的数字,可以是单精度浮点数(float)或双精度浮点数(double)。在C语言中,可以使用小数点或指数形式来表示浮点数常量。下面是几个浮点数常量的例子。

1
2
3
4
3.14159      // 单精度浮点数常量
2.99792e+8 // 科学计数法表示的单精度浮点数常量
1.234567890 // 双精度浮点数常量
-6.02214076e+23 // 科学计数法表示的双精度浮点数常量

3、字符常量
字符常量是指用单引号括起来的单个字符,例如字母、数字或标点符号。在C语言中,每个字符常量在内存中占用一个字节,可以使用转义字符来表示一些特殊的字符。下面是几个字符常量的例子。

1
2
3
4
5
6
'A' // 字符常量
'1' // 数字字符常量
'\n' // 换行符
'\t' // 制表符
'\\' // 反斜杠
'\"' // 双引号

4、字符串常量
字符串常量是指由一串字符组成的字符数组,用双引号括起来。在C语言中,字符串常量以空字符(’\0’)结尾,占用的内存空间根据字符串的长度而定。

1
"Hello, world!"

5、const关键字来定义常量

1
const int NUM = 42;

试图修改这个常量时,编译器会报错

6、宏定义常量

使用#define预处理指令来为一个符号(通常是一个常量)定义一个标识符,以后使用该标识符时,预处理器会自动将其替换成所定义的值。下面是一个宏定义常量的例子:

1
#define PI 3.14159

在这个例子中,我们定义了一个名为PI的常量,它的值为3.14159。在程序中,我们可以直接使用PI这个标识符来表示这个常量,例如:

1
float area = PI * radius * radius;

在预处理之后,这个表达式会被替换成:

1
float area = 3.14159 * radius * radius;

需要注意的是,宏定义是一种纯文本替换,没有数据类型,也没有作用域限制,因此它的使用可能会带来一些问题。比如,如果我们定义了一个整数常量:

1
#define SIZE 10

在程序中使用这个常量时,如果不小心将它和一个整数变量相加,就可能会出现不正确的结果:

1
2
int a[SIZE + 2]; // 正确
int b[SIZE] + 2; // 错误

为了避免这种问题,建议使用const关键字来定义常量,const定义的常量有数据类型和作用域限制,可以更好地保护程序的正确性和可读性。

头文件

包含头文件。举例,如果要使用数学库中的函数,则需要包含math.h头文件。

1
#include <math.h>

调用库函数。包含math.h头文件后,可以在程序中调用库中的函数。

可以调用数学库中的sin函数来计算正弦值。

1
double result = sin(0.5);

至于该使用哪些库、哪些函数,可以去查找文档或者直接在百度搜。

输入输出(IO)

printf() 函数用于向屏幕输出文本:

1
2
3
4
5
6
7
#include <stdio.h>

int main()
{
printf("Hello World!");
return 0;
}

scanf() 函数用于从用户输入中读取值:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main()
{
int num;
printf("Enter a number: ");
scanf("%d", &num);
printf("The number is: %d", num);
return 0;
}

条件控制

if-else 语句用于根据条件执行不同的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

int main()
{
int num;
printf("Enter a number: ");
scanf("%d", &num);
if (num > 0)
{
printf("The number is positive");
}
else
{
printf("The number is negative");
}
return 0;
}

switch-case语句也用于根据不同的条件执行不同的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>

int main()
{
char grade;
printf("Enter your grade: ");
scanf("%c", &grade);
switch(grade)
{
case 'A':
printf("Excellent");
break;
case 'B':
printf("Good");
break;
case 'C':
printf("Fair");
break;
case 'D':
printf("Poor");
break;
default:
printf("Invalid grade");
}
return 0;
}

循环语句

for循环用于执行指定次数的代码:

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

int main()
{
int i;
for (i = 1; i <= 10; i++)
{
printf("%d\n", i);
}
return 0;
}

whiledo-while循环用于在条件为真时重复执行代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int main()
{
int num;
printf("Enter a number: ");
scanf("%d", &num);
while (num > 0)
{
printf("%d\n", num);
num--;
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

int main()
{
int num;
printf("Enter a number: ");
scanf("%d", &num);
do {
printf("%d\n", num);
num--;
} while (num > 0);
return 0;
}

子程序设计

函数是C语言中的一种子程序。函数可以将程序分解为更小、更易于管理的块,从而使代码更易于理解和调试。

下面是一个例子,展示如何定义和调用一个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int addNumbers(int a, int b);

int main()
{
int num1 = 5, num2 = 7, sum;
sum = addNumbers(num1, num2);
printf("Sum of %d and %d is %d", num1, num2, sum);
return 0;
}

int addNumbers(int a, int b)
{
int result;
result = a + b;
return result;
}

递归

递归是一种函数调用自身的技术。它通常用于解决需要重复执行相同或类似操作的问题。

下面是一个例子,展示如何使用递归来计算阶乘:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>

int factorial(int n);

int main()
{
int num = 5, fact;
fact = factorial(num);
printf("Factorial of %d is %d", num, fact);
return 0;
}

int factorial(int n)
{
if (n == 1)
{
return 1;
}
else
{
return n * factorial(n-1);
}
}

在这个例子中,factorial()函数使用递归调用自身来计算n的阶乘。在每个递归调用中,函数将n乘以factorial(n-1),直到n等于1。最后,递归调用返回到原始函数调用,并将计算的阶乘作为结果返回给主函数。

数组和指针

数组

  • 数组是一种数据结构,可以存储多个相同类型的元素。
  • 在C语言中,数组的元素可以是任何基本数据类型,如整数、浮点数、字符等。
  • 在声明数组时,需要指定数组的大小。例如,下面是一个包含10个整数的数组的声明:
1
int myArray[10];
  • 数组元素的访问是通过下标来实现的,下标从0开始,表示数组中的第一个元素。
    1
    myArray[0] = 1; // 将第一个元素设置为1
  • 数组可以用于存储一系列数据,例如数字、字符串等。

指针

  • 指针是一个变量,它存储了一个内存地址。
  • 指针可以指向任何数据类型,例如整数、字符、数组等。
  • 指针的声明需要指定指针所指向的数据类型。例如,下面是一个指向整数的指针的声明:
    1
    int *myPointer;
  • 指针可以用于访问和修改存储在内存中的变量或数组元素的值。
    1
    2
    3
    int myArray[10] = {1,2,3,4,5,6,7,8,9,10};
    int *myPointer = &myArray[0]; // 指向数组的第一个元素
    *myPointer = 2; // 修改第一个元素的值
  • 指针可以用于动态分配内存,例如使用malloc函数来分配内存
    1
    int *myPointer = (int*)malloc(sizeof(int)*10); // 分配包含10个整数的内存空间

结构体

在C语言中,结构体是一种自定义的数据类型,它允许程序员将不同类型的数据组合在一起,并赋予这个组合一个自定义的名字。结构体由多个变量组成,每个变量被称为结构体的成员。结构体成员可以是各种数据类型,包括整型、浮点型、字符型等等。

下面是一个简单的例子,其中定义了一个名为Person的结构体,它包含了三个成员:name表示人名,age表示年龄,gender表示性别。

1
2
3
4
5
struct Person {
char name[20];
int age;
char gender;
};

定义了这个结构体之后,我们可以使用它来创建一个结构体变量,并访问它的成员。例如,下面的代码创建了一个名为person1Person类型的变量,并初始化了它的成员。

1
struct Person person1 = {"Alice", 25, 'F'};

在这个例子中,我们使用了结构体初始化语法来给person1赋值。也可以通过点号运算符来访问结构体成员,例如:

1
2
3
printf("Name: %s\n", person1.name);
printf("Age: %d\n", person1.age);
printf("Gender: %c\n", person1.gender);

输出为:

1
2
3
Name: Alice
Age: 25
Gender: F

结构体数组

当我们需要定义一组相似结构体变量时,可以使用结构体数组。下面是一个例子,其中定义了一个名为Student的结构体,它包含了两个成员:name表示学生姓名,score表示学生的分数。

1
2
3
4
struct Student {
char name[20];
int score;
};

接下来,我们可以定义一个Student类型的结构体数组,其中包含三个学生的信息。

1
2
3
4
5
struct Student students[3] = {
{"Alice", 90},
{"Bob", 85},
{"Charlie", 95}
};

在这个例子中,我们定义了一个名为students的结构体数组,它包含了三个Student类型的结构体变量,并用结构体初始化语法对它们进行了初始化。

我们可以通过循环遍历结构体数组,并使用点号运算符来访问每个学生的姓名和分数。

1
2
3
for (int i = 0; i < 3; i++) {
printf("Name: %s, Score: %d\n", students[i].name, students[i].score);
}

输出为:

1
2
3
Name: Alice, Score: 90
Name: Bob, Score: 85
Name: Charlie, Score: 95

文件操作

首先创建一个名为data.txt的文本文件,并将一些数据写入该文件。

然后,我们将使用C语言中的文件读写函数来读取该文件的内容并打印出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>

int main() {
// 创建一个文件指针,并打开 data.txt 文件以进行写操作
FILE *fp = fopen("data.txt", "w");

// 写入数据到文件中
fprintf(fp, "Hello, World!\n");
fprintf(fp, "This is a test.\n");

// 关闭文件指针
fclose(fp);

// 重新打开 data.txt 文件以进行读操作
fp = fopen("data.txt", "r");

// 从文件中读取数据,并输出到屏幕上
char buffer[100];
while (fgets(buffer, 100, fp) != NULL) {
printf("%s", buffer);
}

// 关闭文件指针
fclose(fp);

return 0;
}

首先创建了一个名为data.txt的文本文件,并将两行数据写入该文件中。然后,我们使用fopen()函数打开该文件,将文件指针存储在fp变量中,并使用fprintf()函数将数据写入该文件。

在写入完数据之后,我们使用fclose()函数关闭文件指针。

接下来,我们重新打开data.txt文件以进行读操作。使用fopen()函数打开文件时,我们将第二个参数设置为r,表示以只读模式打开文件。然后,我们使用fgets()函数从文件中读取数据,并将读取的数据输出到屏幕上。

最后,我们再次使用fclose()函数关闭文件指针。

需要注意的是,在进行文件读写操作时,我们需要确保正确地打开和关闭文件指针,并对可能发生的错误进行适当的处理。

关于C语言的异常处理

注意,C语言本身不支持像其他一些语言(如Java和Python)那样的异常处理机制,但我们可以使用C语言中的一些技巧来模拟异常处理。

一个常见的做法是在代码中使用返回值来指示错误状态,并在发生错误时采取适当的行动。下面是一个简单的例子,演示了如何使用返回值来模拟异常处理。

在这个例子中,我们将定义一个名为divide()的函数,该函数将两个整数相除,并返回结果。如果第二个参数为零,则表示除法操作不合法,此时我们将返回一个特殊的错误码-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>

int divide(int a, int b, int *result) {
if (b == 0) {
return -1;
}
else {
*result = a / b;
return 0;
}
}

int main() {
int a = 10, b = 0, result;
int status = divide(a, b, &result);
if (status == -1) {
printf("Error: Division by zero.\n");
}
else {
printf("%d / %d = %d\n", a, b, result);
}
return 0;
}

我们定义了一个名为divide()的函数,它将两个整数a和b相除,并将结果存储在指向result变量的指针中。如果b为零,则函数返回-1,表示除法操作不合法。

main()函数中,我们将a设置为10,将b设置为0,并调用divide()函数进行除法操作。如果除法操作不合法,则函数将返回-1,此时我们将打印一条错误消息。否则,我们将打印计算结果。

通过在代码中使用返回值来模拟异常处理,我们可以在发生错误时采取适当的行动,并对用户提供友好的错误消息。当然,这种做法要求我们对可能出现的错误情况进行仔细的考虑,并在代码中进行适当的处理。


C语言入门~又爱又恨
https://liaoweiquan.github.io/2019/03/10/C语言初见/
作者
泉泉
发布于
2019年3月10日
许可协议