如标题所言,以下内容都是简单介绍一下,在以后的新文章中将会进行深入说明。这篇文章会让你对C语言在大体上有一个初步的认识
如果大家有兴趣的话可以去搜一下TIOBE,这是一个编程语言排行的榜单,我下面的两张图片就来自于这个榜单
上面是2023年十月份的榜单,C语言在该榜单中排行第二,同样可以看到第二列2022年排行中C语言也是位列第二,足以看到C语言的强大。有些同学看到这里就会问了,哎,那我看这Python还是第一呢,怎么不见你说Python呢。有些同学对于先学Python还是先学C语言二者之间很难抉择,我的建议是先学C语言,因为C语言中有个极其难理解的东西叫做指针,一旦搞懂了指针,你就会“哦噢原来是这样,C语言也没那么难嘛”,此时再去学习Python就跟洒洒水一样。但是,如果你先学Python,也会发现Python不难,但是之后再学C语言就会让你极其痛苦。再来看一个图片:
这是二十年来,编程语言的折线图,蓝紫色表示的是C语言,不难看出C语言一直位列前三,甚至可以说前二。在成百上千种编程语言中常年占据前三,足以说明一些问题。
C 语言是一种通用的高级语言,最初是由丹尼斯·里奇在贝尔实验室为开发 UNIX 操作系统而设计的。C 语言最开始是于 1972 年在 DEC PDP-11 计算机上被首次实现。在 1978 年,布莱恩·柯林汉(Brian Kernighan)和丹尼斯·里奇(Dennis Ritchie)制作了 C 的第一个公开可用的描述,现在被称为 K&R 标准。UNIX 操作系统,C编译器,和几乎所有的 UNIX 应用程序都是用 C 语言编写的。由于各种原因,C 语言现在已经成为一种广泛使用的专业语言。
这本书就是这两人写的,但是我建议初学者看《C Primer Plus》或者《C语言程序设计现代方法》
就简单说到这里,下面开始进行C语言正式的讲解,这篇文章也只是对C语言的整体有个初步的认识,每个知识都浅谈一下,在以后的新文章中将会进行深入的说明。
这里我直接从变量开始说起,就不对数据类型这些非常简单的东西作以介绍了。
变量变量,顾名思义就是可以变化的量,如你的年龄会随着时间的变化而增加,你的年龄就是一个变量。
变量命名,也非常容易理解,就是给一个变量起一个名字。可以是a,b,c,d,又或者是age,time等等。
对于变量命名这里面有几个规则,硬性规则是变量命名只能由数字、字母、下划线组成,并且不能以数字开头,很好理解吧。举几个例子
int age = 18; //纯字母组成
int age_ = 18; //字母和下划线组成
int _age = 18; //下划线在前,字母在后也是可以的
int age_5 = 18; //字母,下划线和数字组成,也是可以的
int 5_age = 18; //也是数字、字母和下划线组成,但是不能将数字放在开头,这是一个错误示范
其次,建议同学们再给变量命名的时候,尽量让变量名有意义,比如我上面的命名,我可以写age,也可以写a,b这样没有意义的名字,在语法上没有任何问题,但是可读性就差了不少,同学们想一想,以后去到公司,那肯定是大量的代码啊,你给变量起个a这样的名字,谁能知道你这表示的是什么意思,使得代码的维护更加艰难,但是你起个age,人一看就明白这表示的是年龄,起一个有意义的名字是一个很好的编程习惯。
还有就是变量名不能冲突,也就是不要起相同的名字,当全局变量和局部变量冲突时,局部变量优先。举个例子
#include
int a = 5; //这里是全局变量
int main()
{
int a = 10; //这里是局部变量
printf("%d\n", a);
return 0;
}
这里的结果会是10,这就是局部变量优先。
简单说,这个变量在哪段代码当中起作用,代码范围就是变量的额作用域。
局部变量的作用域就是局部变量所在的局部范围。
全局变量的作用域是整个工程。
人的生命周期就是从出生到死亡,变量的生命周期就是从变量的创建到销毁。
局部变量的生命周期是从进作用域开始,到出作用域结束。
全局变量的生命周期就是整个程序的生命周期。
常量相信同学们也都理解,就是不变的量,比如你的身份证号,一般称为字面常量。这里还有三个重点介绍的。
第一个是const修饰的常变量。有同学就问了,常量就常量,变量就是变量,常变量是个什么东西啊?简单说就是具有常属性的变量。举个例子
#include
int main()
{
int age = 18;
age = 20;
printf("%d\n", age);
//const int score = 90;
//score = 100; //这里的score是不能直接修改的
//printf("%d\n", score);
return 0;
}
上述代码age就是正儿八经的变量,先定义了age为18,后面再改变age为20,运行结果就是20;如果你把注释的代码添加进去,你会发现编译不过,就是因为使用const修饰的score具有了常属性,编译器认为他是个常数,就不能改变了,所以后续将它改为100,编译器通过不了,但是它本质上还是变量,只是从语法层面上让该变量不能被改变。打个不恰当的比方,就像一个俊美的男生穿了长裙,在别人看来他是一个女生,但实际上他还是个男生,const就相当于是长裙。
直接上代码
#include
#define PI 3.14 //这里就是定义的标识符常量
int main()
{
printf("%f\n", PI);
return 0;
}
用#define定义的标识符常量有什么优势呢?就拿π来举例,如果代码长达几百上千行,在程序中你可能会使用几次、十几次甚至几十次的π,这时候的π是3.14,但是有一天你发现3.14这个精度低了,想要将所有的π都改成3.14159,难不成要在程序中将十几个π,一个一个找出来改成3.14159?这不现实,那怎么弄?用#define定义,后续想要改动,只需要将#define后面的3.14改成3.14159,在这个程序下的所有π的值都会改为3.14159,是不是省事很多呢
直接上代码
#include
//枚举
enum DAY
{
MORNING = 1,
AFTERNOON = 2,
EVENING = 3,
}day;
int main()
{
enum DAY day;
day = MORNING;
printf("%d\n", day);
return 0;
}
括号里的MORNING,AFTERNOON,EVENING就是枚举常量,如果不赋值,枚举常量默认从0开始,向下依次递增1。这里就是简单看一下就可以了,后面会深入介绍的
字符串有一个很重要的点,那就是结束的标志是 \0,只有遇到了 \0 才会结束。举个例子
#include
#include //头文件,包含了strlen函数
int main()
{
char ch[] = { 'a', 'b', 'c'}; //字符
char ch2[] = { "abc" }; //字符串
printf("%d\n", strlen(ch));
printf("%d\n", strlen(ch2));
//strlen函数是求字符串长度的,从给定的地址向后数字符,直到遇见\0结束,\0不统计在内
return 0;
}
我们来看一下运行结果
这不对劲啊,这同样都是abc三个字符啊,我能理解下面的长度是3,为什么上面的一行的输出结果是19呢?上面说过,\0 是字符串结束的标志,在双引号里的abc属于字符串,隐含着\0,可以理解为在双引号的后引号前隐藏了一个\0,只是没有显示出来,而在第一行中的a,b,c并不是正经的字符串,而是三个字符,想要计算第一行的长度需要手动输入 \0
char ch[] = {'a', 'b', 'c', '\0'}
此时再运行结果就会是3
看图,对于第二行来说,长度会从1号箭头指的a的地址往后数,到\0就是2号箭头所指的地址,此时是三个字符,返回3。对于第一行来说编译器会从1号箭头指的a所在的地址往后数,直到数到\0才会返回一个值,而\0可能会在3号箭头所指的位置,也可能会在2号箭头所指地址后面的任意一个位置,这时候返回值一定是一个大于等于3的随机值,在我的电脑上显示19,在你的电脑上可能就是8或者40或者16等随机值。
这里介绍几个常见的转义字符和两个较难的转义字符
让单引号不被解析为一对单引号中的单引号
让双引号不被解析为一对双引号中的双引号
比如你想要打印一个双引号,你打算这样写代码
#include
int main()
{
printf(""");
return 0;
}
你会发现编译器报错,这个时候你给要打印的双引号前加一个\,再次运行就会打印出 "
双斜杠是让后面的斜杠不要被解析为转义字符里面的斜杠,多在表示路径时使用。
比如路径D:\Code\c_language在打印是不能直接写成这样
printf("D:\Code\c_language");
你会发现打印结果中斜杠没有打印出来,这个时候就需要使用双斜杠
printf("D:\\Code\\c_language");
这个时候再打印你会发现完整的路径被打印出来了。
这是一个警告字符,你可以试着打印一个\a,运行程序,你会听到你的电脑响了一下。
\b叫做退格符,\b出现的时候会往前退一格。
printf("123\b456");
运行一下你会发现3没了,这就是退格符\b的作用,会让4往前退一格,将3覆盖掉了,所以结果没有3。
\n应该是同学们最常用的一个转义字符了,功能是换行,相当于键盘上的回车键,只不过回车键是输入设备的换行,\n则是在输出设备上让打印结果换行。
\t叫做水平制表符,可以尝试动手写一下,一般是左对齐总共时8个算一个,但是可以设置。
\ddd和\xdd是最难理解的两个转义字符了,先来说说\ddd。
\ddd 中的d d d 表示1到3个八进制的数字。也就是1到3个0~7的数字,比如
printf("\101");
你发现输出结果是A,这是为什么呢?这是因为101是八进制数,它会先转换为十进制数,再打印出该十进制数所对应的ASCII码值。
\xdd 中的d d 表示两个十六进制数,也就是2个09、AF,其余和\ddd无异。
注释,也就是给代码做一个简要的文字解释,相当于中学语文课本中文言文和古诗文下面的注解一样,为了让文章更加容易被理解,注释也是让代码更容易被读懂。在C语言中有两种风格的注释。
第一种风格,也被称为C++风格,只能注释单行
//你要注释的内容
第二种风格,是C语言风格,可以注释多行
/* 你要注释
的内容 */
C语言风格的注释有一个缺点,那就是不能嵌套注释。
C语言中的三大基本结构,有顺序结构,选择结构(也叫分支结构),循环结构。选择语句就是选择结构,主要有if…else和switch两种,后面的新文章将会详细介绍,这里举一个简单的if语句:
#include
int main()
{
int score = 0;
printf("请输入你的分数(0-100):");
scanf("%d", &score);
if (score >= 80)
printf("优秀\n");
else
printf("继续加油\n");
return 0;
}
顾名思义就是重复循环的,一直做,就像我们同学要日复一日的学习。主要有三种,while,for和do…while,也将在后面的新文章中详细介绍。这里简单说一下while语句。
#include
int main()
{
int book_num = 0;
while (book_num <= 5)
{
book_num++;
printf("请继续阅读\n");
}
if (book_num > 5)
printf("知识渊博\n");
return 0;
}
一个函数能完成一个独立的功能,简化代码
比如我要实现一个加法的程序,不用函数是这样
#include
int main()
{
int a = 0;
int b = 0;
int sum = 0;
scanf("%d%d", &a, &b);
sum = a + b;
printf("%d\n", sum);
return 0;
}
如果使用函数,代码则是这样
#include
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 0;
int b = 0;
int sum = 0;
scanf("%d%d", &a, &b);
sum = Add(a, b);
printf("%d\n", sum);
return 0;
}
有些人说了,这看着也没见简化啥,这是因为这段非常简单,只实现了一个加法的功能。如果一个比较复杂的功能需要上百行代码,这时使用函数是不是就会简化代码。
什么叫数组?那就是一组数呗。官方定义:一组相同类型元素的集合。
数组可以通过下标来访问,比如我定义有5个整型元素的数组:
int arr[5] = {1,2,3,4,5}; // 定义一个放五个元素的整型数组
我这个数组有5个元素,那么下标的范围就是0~4
int arr[5] | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
下标 | 0 | 1 | 2 | 3 | 4 |
#include
int main()
{
int i = 0;
int arr[5] = { 1,2,3,4,5 };
printf("%d\n", arr[1]);
return 0;
}
因为数组的下标从0开始,这个结果就是第二个元素2的值。
这里仍只是认识一下,后面详细介绍。C语言的灵活性就体现在操作符上,可以直接操作到二进制位。
+ - * / %
移动的是二进制位
>> <<
操作的也是二进制位
& ^ |
= += -= *= /= |= >>= <<=
单目的意思是只有一个操作数
! 逻辑取反,在C语言中,0为假,非0为真
- 负值
& 取地址
sizeof 操作数的类型长度
~ 对一个数的二进制按位取反
-- 有前置和后置之分,前置--为先-后使用;后置--为先使用后-
++ 有前置和后置之分,与--相同
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
//sizeof的用法
#include
int main()
{
printf("%d\n", sizeof(int));
printf("%d\n", sizeof(char));
printf("%d\n", sizeof(float));
printf("%d\n", sizeof(double));
printf("%d\n", sizeof(long long));
return 0;
}
这就是这五个类型所占内存空间的大小,单位是字节
> >= < <= != ==
&& 逻辑与,表示并且
|| 逻辑或,表示或者
也叫三目操作符,是C语言中唯一的三目操作符
语句1 ? 语句2 : 语句3
意思为当
语句1为真是则执行语句2,语句3不执行;语句1为假时。语句2不执行,执行语句3
#include
int main()
{
int a = 5;
int n = (a = 5) ? 1 : 0;
printf("%d\n", n);
return 0;
}
结果为1
语句1, 语句2, ,语句3
从左往右依次计算,结果是最后一个表达式的结果
#include
int main()
{
int a = 1;
int b = 2;
int c = 3;
int d = (a+b, a+b, a+b+c*5);
printf("%d\n", d);
return 0;
}
虽然这里的结果是18,和前面的a+b没有关系,好像可以直接由最后一个式子算出来,其实并不是这样,要从左往右依次计算,最后的结果是最后一个表达式的结果。
#include
int main()
{
int a = 0;
int b = 0;
int c = 0;
a = (c=10, b=c%8, b+1);
printf("%d\n", a);
return 0;
}
这段代码的结果运行的结果是3,从左往右依次计算c=10,b=2,b+1=3,最后返回3.如果直接算b+1,那答案是1,显然是错误的。
[] () . ->
[] 数组下标的引用,如arr[1]
() 函数的调用,如Add(a,b)
后面两个可以用来访问结构体中的结构成员,这里不详细介绍
C语言中总共有32个关键字,这是C语言设定好的,用户不能创造。在这里介绍两个关键字typedef和static
typedef的作用是对类型的重命名,如无符号长整型是unsigned long long ,在多次使用时比较繁琐,看着也比较冗余,可以这样让写代码更加方便。
#include
typedef unsigned long long ull;
int main()
{
ull a = 20;
ull b = 30;
ull c = 50;
printf("%d\t%d\t%d", a, b, c);
return 0;
}
在上述代码中,将unsigned long long重命名为ull,在后续代码的编写中更加方便
看如下代码
#include
void A()
{
int i = 0;
i++;
printf("%d\n", i);
}
int main()
{
int i = 0;
for (i = 0; i < 5; i++)
{
A();
}
return 0;
}
结果为5个1,这是因为主函数运行到调用函数A时,进入函数A后,创建一个变量i=0,然后i++,使得i变为1,再打印i,结果就是1,然后出了函数A后变量i会被销毁,然后等到循环第二次再次运行到调用函数A,进入函数A后,创建一个变量i=0,然后i++,使得i变为1,再打印i,结果还是1,第三次,第四次,第五次也是这样,所以最终的结果为5个1。
再看使用static修饰过局部变量的程序
#include
void A()
{
static int i = 0; //static修饰过的局部变量
i++;
printf("%d\n", i);
}
int main()
{
int i = 0;
for (i = 0; i < 5; i++)
{
A();
}
return 0;
}
这段代码的结果是1 2 3 4 5 ,这是因为static修饰的局部变量的生命周期被延长了,让该局部变量出了作用域后不被销毁,依然存在,一直到程序结束生命周期才结束。所以在出函数A时,已经创建的变量i不会被销毁,所以在下一次循环调用函数A时会在上一次的基础上进行++操作。
这里就要说到内存了,内存大概可以划分为三个区域,分别为栈区、堆区和静态区。如图
这里所说的变量销毁并不是真正的摧毁毁灭,而是还给操作系统。相当于你租房,不住了退房一样。
static修饰的局部变量就不存在栈区了,而是被存放到静态区,具有静态变量的特点
全局变量本身具有外部链接属性,可以在其他源文件内部使用,但被static修饰后,全局变量的链接属性被改变了,由外部变成了内部,只能在自己所在的源文件内部使用,相当于作用域变小了。
我们可以看到这时候是可以打印出外部变量的值的。
这时候我用static修饰以后,发现报错了,说int year是无法解析的外部命令,说明12.cpp中使用不了11.cpp中的外部变量,可以看出外部全局变量被static修饰后失去了本身具有的外部链接属性。
static修饰函数与全局变量是类似的
前面已经说过#define定义标识符常量了,这里说一下宏定义
#include
#define Add(x, y) (x+y) //宏定义,从左往右 #define 名称(参数) 实现体,有点类似于函数
int main()
{
int sum = Add(1, 1);
printf("%d\n", sum);
sum = 2 * Add(1, 1);
printf("%d\n", sum);
return 0;
}
前面说到数组是一组相同类型元素的集合,要么全是整型,要么全是字符。结构体可以描述复杂的类型。比如说来描述学生,学生信息有姓名,性别,年龄等等信息,无法使用相同类型描述,所以这里就只能使用结构体来描述了。
#include
struct Stu
{
char name[20]; //名字是字符串,一个汉字是两个字符
int age;
char sex[5];
};
int main()
{
struct Stu a = { "小刘", 19, "男" };
printf("%s %d %s", a.name, a.age, a.sex); //.访问结构成员
return 0;
}
这里的.就是前面说到的访问结构成员的操作符