程序设计的一个重要内容,就是合理的储存数据,其中,变量能够帮助我们完成数据的储存,而变量的类型则规定了变量储存内容的解析方式。
使用变量储存数据
在C语言中,想要使用变量,就必须先声明变量,这一语法的基本形式如下:
type var_name;
对于变量的命名而言,有一些重要的规则需要遵循:
- 变量名需要以英文或下划线开头
- 不能使用 C语言 中的关键字
学习这些规则的最好办法,就是编写程序,因此,我们编写了一些示范的小程序。
/*
var.c
Show the usage of var
BeginnerC
*/
#include <stdio.h>
int main()
{
// Define a var named number, int type
int number = 100;
// Define a var named PI, double type
double PI = 3.14;
// Define a var named letter, char type
char letter = 'A';
printf("%d %lf %c\n", number, PI, letter);
return 0;
}
在这个简单的小程序中,我们声明并定义三个变量,选择不同的数据类型来储存不同的数据,并在最后使用 printf 函数输出了他们。
其中涉及到的知识点如下
- C 语言中的数据类型
C 语言提供了数种内置类型进行数据的储存,但从大的分类来看,主要是整数、小数、字符 - printf 格式输出
printf 函数不是一个单纯输出字符串的函数,它可以根据格式指示符(%d %lf %c 这一类)进行对应的数据输出
我们将 C语言 中的内置数据类型罗列如下表
| 数据类型 | 作用说明 | 具体类型指示符 |
|---|---|---|
| 字符 | 用于表示可以阅读的符号(比如字母) | char |
| 小数 | 表示小数 | double float |
| 整数 | 表示整数 | int |
在当下的程序设计中,我们使用 char double int 三种类型对 字符、小数与整数进行储存。
同时,C语言也提供了一些变量限定符,用于更加规范地使用变量。
| 变量限定符 | 作用 | 举例 |
|---|---|---|
| long | 使得变量占用的储存空间尽可能大 | long double |
| short | 使得变量占用的储存空间尽可能小 | short int |
| unsigned | 使得变量按照无符号方式进行解析 | unsigned int |
| signed | 使得变量按照有符号方式进行解析 | signed int |
| const | 使得变量不可以被修订 | const double PI = 3.1415926; |
当然,在现实情况中的程序设计中,情况往往更加复杂,这种状况下,我们就需要灵活选择(用实践作为支撑)
而对于 printf 函数的格式输出,我们也准备了一张对应的表格(描述常用的格式控制符号)
| 指示符号 | 具体含义 | 示例代码 |
|---|---|---|
| %c | 输出字符 | printf(“%c”, ‘c’); |
| %s | 输出字符串 | printf(“%s”, “Hello World”); |
| %d %ld %u | 输出整数(以 10 进制的形式) | printf(“%d”, 123); |
| %f %lf | 输出小数 | printf(“%lf”, 3.14); |
| %x %o | 输出整数(以 16 进制 或 8 进制的形式) | printf(“%x”, 0x0001); |
学习 printf 函数的最好方式,就是动手实践,比起拘泥于一些名词与表格,我们更倾向于鼓励你写出一些验证的小程序进行测试。
对数据进行基本处理
程序的本质就是处理数据的工具,在完成数据的储存之后,我们就要开始对我们储存的数据进行处理。
在 C语言 中,这被称为运算。
学习数据处理的最好办法,就是动手实践,因此,我们同样会给出一组示例的程序,作为参考。
/*
calc_circle_area.c
Calc the circle area
BeginnerC
*/
#include <stdio.h>
int main()
{
double pi = 3.14;
float r = 2;
printf("The area of circle is :%f\n", pi * r * r);
return 0;
}
这个程序负责计算圆的面积,可以看出,程序非常顺利地达到了我们的目标。
这种包含多个变量运算的式子,被称为表达式,他们是我们数据处理的基石。
C 语言提供了许多种有用的变量运算,我们也如之前一般,整理如下表
加减乘除
| 符号 | 作用 | 举例 |
|---|---|---|
| + | 使两个变量相加 | a + b |
| - | 使两个变量相减 | a - b |
| * | 使两个变量相乘 | a * b |
| / | 使两个变量相除 | a / b |
学习一种运算,最好的办法,就是动手实践。
/*
calc_base.c
Show the usage of the calc
BeginnerC
*/
#include <stdio.h>
int main()
{
int a = 1, b = 2;
printf("%d + %d = %d\n", a, b, a + b);
printf("%d - %d = %d\n", a, b, a - b);
printf("%d * %d = %d\n", a, b, a * b);
printf("%d / %d = %d\n", a, b, a / b);
printf("%d / %d = %f\n", a, b, a / (double)b);
return 0;
}
可以发现,程序非常顺利地完成了计算任务,唯一的差别在于最后的除法上。
在这里,我们有必要引入表达式类型与类型转化的概念。
-
表达式的类型
首先,我们明确,在 C语言表达式 的最后结果上,有且只有一种数据类型
而当参与运算的变量类型不一致时,就需要涉及到类型转换-
两种类型转换
类型转换分为自动类型转换与强制类型转换- 自动类型转换
自动转换用“相对更大的储存空间"保证“不损失信息",它往往取决于参与运算的数据类型中,哪一者的数据类型“占用空间最多" - 强制类型转换
强制类型转换由程序设计者们主动写入,语法为
(type)var
- 自动类型转换
-
掌握一项程序设计知识点的最好办法,就是动手实践,在此,我们继续编写有关的案例。
/*
calc_base_second.c
Show the usage of the calc
BeginnerC
*/
#include <stdio.h>
int main()
{
printf("%d\n", 3 / 5);
printf("%f\n", 3 / 5);
printf("%f\n", 3 / (double)5);
printf("%f\n", 3 / 5.0);
return 0;
}
可以发现,同样是 3 与 5 相除,四条式子的运算结果却不能一致,这是因为
- 整数除法的结果往往采用舍尾法,即完全忽略小数部分因此 3 / 5 = 0.6 时,后面的小数部分被完全舍去,只剩下整数部分,也就是 0
- printf 函数只负责按照指定的格式输出数据,对于数据的类型(储存方式、占用空间),printf 函数没有决定权
所以,我们尽管在第二个式子中让 printf 函数以小数形式输出数据,也只能得到 0 的结果 - 在第三个式子中,我们使用强制类型转换,让 5 的类型直接提升到小数型
所以 0.6 被正确的计算了出来 - 在第四个式子中,我们将 5 变成小数类型,从而让自动类型转化发生,顺利地实现了 0.6 的计算结果
同时,对于字符与整数之间的互相转换,我们也提供一个示例
/*
char_int.c
Show the relation of the char & int
BeginnerC
*/
#include <stdio.h>
int main()
{
printf("%d = %c\n", 'A', 'A');
return 0;
}
在这个案例之中,我们很明显的发现,字符就是数字的另一种表现形式
而这种“字符-数字"的对应关系,被称为字符集,C语言一般使用 Ascil 字符集
% 取余运算
取余运算只针对于整数,它负责计算一次除法以后的余数
/*
mod.c
Show the usage of mod
BeginnerC
*/
#include <stdio.h>
int main()
{
int a = 7, b = 3;
printf("%d\n", a % b);
return 0;
}
逻辑运算
在 C语言 中,逻辑运算是一种对于数据的比较,它的运算结果遵循一个法则:非0为真
我们将所有的逻辑运算整合为一张表格,罗列如下
| 符号 | 含义 | 举例 |
|---|---|---|
| >= | 判断前者的数据是否大于等于后者 | 5 >= 3 |
| 判断前者的数据是否大于后者 | 1 > 2 | |
| <= | 判断前者的数据是否小于等于后者 | 6 <= 9 |
| < | 判断前者的数据是否小于后者 | 0 < 1 |
| == | 判断两个数据是否相等 | 1 == 1 |
| != | 判断两个数据是否不相等 | 0 != 1 |
| ! | 将逻辑运算的结果取反 | !(1 != 1) |
学习一项程序设计知识的最好办法就是动手实践,因此,我们亦给出与之相关的程序
/*
logical.c
Show the usage of logical calc
BeginnerC
*/
#include <stdio.h>
int main()
{
int result = 0;
result = 2 >= 1;
printf("%d\n", result);
result = 0 > 1;
printf("%d\n", result);
return 0;
}
在这个案例中,我们展现了其中两种逻辑运算,可以很清晰的发现,逻辑运算的结果就是一个整数,判断为真的时候,为1,判断为假的时候,就是0
我们鼓励你更多的用自己编写程序的方式,验证我们上述的观点。
同时,我们指出,这些最基本的逻辑运算,还可以通过组合的方式,构成更加复杂的逻辑关系式。
我们将这些组合的符号,也罗列如下表
| 符号名称 | 含义 | 举例 | ||||
|---|---|---|---|---|---|---|
| && | 与 | (1 != 2) && (3 == 4) | ||||
| 或 | (1 != 2) | (3 == 4) |
他们的区别,我们也用一个程序展示
/*
or_and_compare.c
Show the difference of && and ||
BeginnerC
*/
#include <stdio.h>
int main()
{
int result;
result = (1 != 2) && (3 == 4);
printf("%d\n", result);
result = (1 != 2) || (3 == 4);
printf("%d\n", result);
return 0;
}
如您所见,&&(与运算)需要参与表达式同时为真(不为0)时,才可以判断为真;而||(或运算))则只需要其中一个为真即可。
学习,最重要的还是实践,因此,我们鼓励你写出与之相关的程序,验证自己的想法。
条件表达式
条件表达式根据逻辑运算的结果决定所执行的代码,如您所见
/*
condition_statement.c
Show the usage of condition statement
BeginnerC
*/
#include <stdio.h>
int main()
{
1 > 0 ? puts("1 > 0"), puts("That's true") : puts("1 <= 0"), puts("That's false");
return 0;
}
可以发现,这个程序既顺利,又不顺利的完成了任务。
顺利的在于,条件表达式正确判断出了 1 > 0 的结果,输出了 1 > 0
而不顺利的在于,后面本应该不被输出的 That's false 也被输出了
这是因为,我们使用了 逗号表达式,它的作用在于分割语句为一个个独立的模块。
而在这里,实际上我们的语句被按照如下的方式进行“理解"
1 > 0 ? puts("1 > 0"), puts("That's true") : puts("1 <= 0") 和 puts("That's false");
换而言之,不论如何,后面的 That's false 都要被输出。
解决的办法,在于用括号进行包裹。
/*
condition_statement_final.c
Show the usage of condition statement
BeginnerC
*/
#include <stdio.h>
int main()
{
1 > 0 ? puts("1 > 0"), puts("That's true") : (puts("1 <= 0"), puts("That's false"));
return 0;
}
事实上,这种办法非常容易引发不必要的麻烦,也降低了代码的可读性,因此,我们更推荐你使用 if 语句 进行替代。
condition ? statement_true : statement_false;
// Equal to
if (condition)
{
statement_true;
}
else
{
statement_false;
}
而 if语句 具有更好的可理解性与可阅读性
位运算
在计算机中,一切的数据实际上都是二进制数据,而位运算,就是专注于二进制数字的运算。
我们也将他们罗列如下表
| 运算符 | 含义 | 举例 | |
|---|---|---|---|
| >> | 右移运算,将二进制位向右移动一位,高位补0 | 1 >> 2 | |
| << | 左移运算,将二进制位向左移动一位,低位补0 | 1 << 2 | |
| & | 按二进制比特位进行与运算(非逻辑运算) | 8 ^ 9 | |
| 按二进制比特位进行或运算(非逻辑运算) | 8 & 9 | ||
| 按二进制比特位进行异或运算(非逻辑运算) | 8 ^ 9 |
学习一项知识点的最好办法,就是动手实践,因此,我们也将给出程序,进行验证
/*
bit_calc.c
Show the usage of bit calc
BeginnerC
*/
#include <stdio.h>
#include <string.h>
/*
PrintNumber
Print the number in binary form
Argument
number
The number want to print
Return Value
No value will return
*/
void PrintNumber(int number)
{
char buffer[1024] = {};
char bit[2] = {};
if (0 == number)
{
printf("0");
}
while (number)
{
sprintf(bit, "%d", number % 2);
strcat(buffer, bit);
number /= 2;
}
for (int i = strlen(buffer) - 1;i >= 0;i--)
{
printf("%c", buffer[i]);
}
puts("");
}
int main()
{
/* Show the usage of >> & << */
PrintNumber(8);
PrintNumber(8 >> 1);
PrintNumber(8 << 1);
/* Show the usage of & and ^ and | */
PrintNumber(4);
PrintNumber(4 & 8);
PrintNumber(4 ^ 8);
PrintNumber(4 | 8);
return 0;
}
这个程序涉及到了许多超前的知识点,比如循环语句,比如函数,在这里我们只需要清楚一点
PrintNumber 函数会打印出一个十进制数字的二进制数字形式。
而如我们所见,8 的 二进制形式 是 1000,4 的 二进制形式 是 100
先解析前2条语句。
- 8 >> 1,右移一位,所以变成了 0100,也就是图中输出的 100
- 8 << 1,左移一位,所以变成了 10000,也就是图中输出的 10000
这就是左移与右移运算的基本理解,实际情况下(比如遇到有符号与无符号数字)时会更加复杂,在这里,我们更鼓励你通过实践去掌握他们。
再看后面的三条语句。
-
4 & 8
也就是 0100 & 1000,因为与运算的准则就是:同时为1才是1,所以这一运算的结果为 0000,也就是最后输出的二进制 0 -
4 ^ 8
也就是 0100 ^ 1000,因为异或运算的准则是:参与对比的两个比特位不一样时,为1,所以这一运算的结果为 1100 -
4 | 8
也就是 0100 | 1000,因为与运算的准则是:两个位中的一位为1,结果就是1,所以这一运算的结果为 1100
取地址运算
&var 可以提取出一个变量的内存地址,这一点对于指针而言颇为重要。
自增运算与自减运算
var++ 将当前变量的数值加一,var--,将当前变量的数值减一
var++与 ++var,var--与 --var的区别在于最终表达式的结果(是原变量数值,还是自增/自减后的结果),这一点,我们鼓励你写出明确的程序于以测试。
运算的优先级问题
每一种运算都有先后次序之分,比如,1 + 2 * 3 的次序就是先计算 2 * 3,再计算 两者 的和。
我们认为,单纯背诵优先级表没有多大的意义,最好的办法就是:
- 使每个表达式都尽可能简单
- 对于需要优先运算的表达式,用括号进行包裹
- 动手实践,自己测试
计算表达式的占用空间
很多时候,许多复杂的表达式难以估算其类型的占用空间,对此,C语言提供了 sizeof 运算符帮助我们进行评估。
/*
sizeof_try.c
Use the sizeof to solve the problem
BeginnerC
*/
#include <stdio.h>
int main()
{
char c = 'A';
printf("%d\n", sizeof(3.1415926));
printf("%d\n", sizeof(3.14));
printf("%d\n", sizeof('A'));
printf("%d\n", sizeof('A' + 'B'));
printf("%d\n", sizeof(c));
printf("%d\n", sizeof(5 + 1));
return 0;
}
可以发现,很多时候,表达式所占用的字节空间,是出乎我们的意料的,因此,实践动手才是解决问题的唯一方案。
register 寄存器变量
寄存器变量相对于一般的变量而言,具有更高的性能。
因为寄存器变量在寄存器中进行读写,而一般的自动变量在内存中进行读写。
/*
register.c
Use the register value
BeginnerC
*/
#include <stdio.h>
#include <time.h>
int main()
{
int start = 0, end = 0;
register int count_1 = 0;
int count_2 = 0;
start = clock();
for (int i = 0;i < 1000000;i++)
count_1++;
end = clock();
printf("%f s\n", (double)(end - start) / CLOCKS_PER_SEC);
start = clock();
for (int i = 0;i < 1000000;i++)
count_2++;
end = clock();
printf("%f s\n",(double)(end - start) / CLOCKS_PER_SEC);
return 0;
}
值得注意的是,寄存器变量不能使用取地址运算。
数据的读取
在能够设计出合理的数据方案之后,我们也要关注数据的读取。
C语言标准函数库 stdio.h 提供了许多有用的函数,帮助我们读取数据。
学习一门技术的最好方式,就是动手实践,下面,我们集中展示他们的使用
/*
read.c
Read the data to the value
BeginnerC
*/
#include <stdio.h>
int main()
{
int number = 0;
double number_2 = 0;
char c = '\0';
scanf("%d %lf %c", &number, &number_2, &c);
printf("%d %f %c\n", number, number_2, c);
return 0;
}
在这一案例中,我们使用标准库函数 scanf 函数为我们读入 整数 小数 字符 三种类型的数据
而作为一种格式输入函数,scanf 函数也提供了许多其它的用法
/*
read_2.c
Read the data to the value
BeginnerC
*/
#include <stdio.h>
int main()
{
int ip_first = 0;
int ip_second = 0;
int ip_third = 0;
int ip_fourth = 0;
scanf("%d.%d.%d.%d", &ip_first, &ip_second, &ip_third, &ip_fourth);
printf("%d %d %d %d\n", ip_first, ip_second, ip_third, ip_fourth);
return 0;
}
在这一案例之中,我们用’.‘与’%d’格式字符串进行组合,最后让我们能够顺利地实现 IP地址(IPV4)的输入
对于 scanf 函数的输入,我们整理如下表
| 符号 | 含义 |
|---|---|
| %% | 百分号 |
| %lf | 小数 |
| %d %u %i | 整数 |
| %s %c | 字符串/字符 |
| %x %o | 十六进制数字或八进制数字 |
值得注意的是,字符串实际上就是多个相连的字符
/*
read_3.c
Use the scanf to read the string
BeginnerC
*/
#include <stdio.h>
int main()
{
char string[256] = {};
scanf("%s", string);
printf(string);
return 0;
}
在这个程序之中,我们使用了数组来保存字符串,可以发现,这个数组的最大容量是 256 个字符。
在我们使用 %s 读取字符串,可以发现,在这个案例之中,我们并没有使用取地址运算,这是因为,数组名本身就是数组元素的起始地址
/*
read_4.c
Show the usage of getchar/putchar
BeginnerC
*/
#include <stdio.h>
int main()
{
char c = '\0';
c = getchar();
putchar(c);
return 0;
}
在这个程序中,我们使用 getchar 函数读取单一的字符,并用 putchar 输出他们