【前言】本系列(初阶)适用于初学者课前预习或者课后复习资料,包含了大部分基础知识点梳理与代码例方便看官理解,有问题请指出。本人邮箱地址:p772307283@outlook.com
本章概要:初识c语言,带各位看官认识c语言中的重点内容,做一个大概的认识,所有下文提到的知识点都会在后续进行一个详细的讲述。
目录
1.什么是c语言?
简单的来说,c语言其实就是一门计算机编程语言,是人与计算机交流的语言。其广泛的应用于底层开发。底层包括操作系统和电脑硬件等,就像linux就是有c语言参与开发的系统,可谓是经久不衰的一门语言,在历史的长河中屹立不倒。所以学好c语言对编程者而言是非常重要的。简单来讲就是这么多,对C语言想要更多深入了解的看官可以自行百度或阅读相关书籍。
2.第一个c语言程序
相信大家在课堂上见识到的第一个代码应该都是hello world了吧,可谓是经典中的经典,也正是这么简单的一个代码里却也充满了小细节。
#include<stdio.h>
int main(){
printf("hello,world!");
return 0;
}
从第一行开始来看,第一行的意思是包含stdio.h这个头文件。
stdio.h在c中代表标准输入输出头文件,也就是你想使用如printf这样的函数就必须要引用这个头文件。在后面我们还会见到各种不同的头文件,来辅助我们完成各种不一样的功能。
往下的第二行则是一个主函数main,这是非常重要的一个知识点,敲重点!!!
main函数是整个程序的入口,这就意味着不论你的main函数在你写的代码哪个位置,运行时都会作为入口进入。
同时在一个工程中,main函数只有一个,就像在vs中,源文件里可以有多个.c文件, 但是在其中只可以有一个.c文件具有main函数。
3.数据类型
下面是在学习中经常会见到的一些数据类型
char 字符型数据类型 例子:hello,world
short 短整型 //整型就是整数
int 整型
long 长整型
long long 更长的整型
float 单精度浮点数 //小数
double 双精度浮点数
那么问题来了,为什么会出现这么多的类型呢?
我们先来看另一个问题,每种类型的大小是多少呢?
这就要用到sizeof这个操作符了,作用是计算变量的大小。
#include<stdio.h>
int main() {
printf("%d\n", sizeof(char));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof(short));
printf("%d\n", sizeof(long));
printf("%d\n", sizeof(long long));
printf("%d\n", sizeof(float));
printf("%d\n", sizeof(double));
printf("%d\n", sizeof(long double));
return 0;
}
其结果为 1 4 2 4 8 4 8 8
单位为字节,再来科普一下计算机的常用单位吧。
bit——比特
1 byte=8bit
byte——字节
KB
1kb=1024byte
MB
1mb=1024gb
GB
TB
一个比特位可以存储一个数字,那么学过计组的看官应该很快就能知道short的大小为2字节,其范围表示为-32768到32767.那么使用这样一个数据类型来表示年龄就已足以,如果 使用int来为年龄申请一个空间不就有些浪费了吗?(浪费很少,一般还是使用int)
所以存在这么多的类型,其实就是为了更加丰富的表达生活中的各种值。
类型使用
char ch='q';
int age =12;
这样的意思就是为字符q申请了一个名叫ch的空间来存放,为12申请了一个叫age的空间来存放,在之后的调用中直接使用空间名即可。
tips: 注意必须用' '才可以表示是一个字符
4.常量和变量
在生活中我们经常会见到一些不变的值,就像是数学课上学习的圆周率,或者每个人只有一个的身份证号码。
还有一些可以变的值,就像是你熬夜或者吃夜宵会导致身体发胖,体重直线上升(doge)。
这就是变量和常量的区别。
同时在上面的类型使用我们就已经见识过变量定义的方法了。但同时重点又来了!!!
变量的命名是有很多细小的规则的哦,再次敲黑板!!!!
1.只能以字母数字与下划线(_)组成。
2.不能以数字开头。
3.长度不可以超过63个字符。
4.变量中区别大小写。
5.变量不可以取关键字。
变量也是有局部和全局之分的。
简单的来说就是覆盖范围有所区别。
#include<stdio.h>
int age = 100; //全局变量
int main() {
int num = 9; //局部变量
int age = 95;
printf("%d", age);
return 0;
}
代码运行结果显示为95,说明当局部变量和全局变量重名时,局部变量优先使用。
变量又该如何使用呢?
#include<stdio.h>
int main() {
int num1 = 0;
int num2 = 0;
int sum = 0;
printf("输入两个操作数\n");
scanf("%d %d", &num1, &num2);
sum = num1 + num2;
printf("sum=%d\n", sum);
return 0;
}
其中的scanf则是一个库函数,作用是从键盘接收数据。
我们申请了三片空间,分别叫num1,num2,sum,并通过scanf函数为num1与num2分别赋予了两个整型数值,然后通过加法计算将总和值赋给了sum这个空间,到这里,三个变量的值都从先前定义的0有了人为赋予的值,这就是简单的变量使用。
变量还有作用域和生命周期的知识点。
作用域是程序设计概念,在一段代码中所用到的名字不总会是有效的,限定这个名字的可用性的范围就是名字的作用域。
{
int a=100;
}
printf("%d",a);
这样的代码会显示错误,因为a只能在大括号中去使用,而printf函数已经显然超出了{}的范围。
局部变量的作用域是变量所在的局部范围。
全局变量的作用域则是整个工程。
变量的生命周期则是指变量的创建到销毁之间的一个时间段。
局部变量的生命周期是从进入作用域生命周期开始,出作用域生命周期结束。
全局变量的生命周期则是整个程序的生命周期。
讲完变量再来看常量。在c语言中常量可以分为以下的几种。
1.字面常量
2.const修饰的常变量。
3.define标识符定义的标识符常量。
4.枚举常量。
#include<stdio.h>
int main() {
//字面常量
3;
4433;
//const修饰的常量
const float pai = 3.14;
//注意,这样的pai是无法被修改的。
//define定义的标识符常量
#define m 100
return 0;
}
需要注意的是,上述代码段的pai被称为const修饰的常变量,只是在语法层面限制其无法被直接改变。但是pai在本质上仍然是一个变量,所以才会被称为常变量。
这一点可以用数组的知识点来佐证。
const int n = 10;
#define m 10
int arr[n] = { 0 };
int arr[m] = { 0 };
arr[n]里面的n必须要是一个常量,因为n在这里是一个常变量,本质上仍然是变量,所以会报错。
而define所定义的就不会有问题。
还需要提出的一点注意是创建变量的时候尽可能为其赋给一个初值,这样会让程序更加可控,也是一个好习惯。
还有枚举常量。
enum sex {
male,
female,
secret
};
其中的male,female,还有secret则是枚举常量,作为枚举类型sex的可能取值。
注意,枚举常量默认是以0开始,向下递增1的。
printf("%d\n", male);
printf("%d\n", female);
printf("%d\n", secret);
这点可以写出代码佐证,其值分别为0,1,2
5.字符串与转义字符
"hello,world"
这样的由双引号括起来的一串字符被称为字符串。
在介绍下面的内容时先引入一个函数strlen,他的作用是计算字符串的长度。
同时,使用strlen函数需要引入头文件
#include<string.h>
#include<stdio.h>
#include<string.h>
int main() {
char arr1[] = "hello,world";
char arr2[] = { 'a','b','c' };
printf("%s\n", arr1);
printf("%s\n", arr2);
}
运行代码会得到类似这样的奇怪结果。
在输出arr2时出现了乱码。这是怎么一回事呢?敲重点咯。
字符串的结束标志是一个\0的转义字符,但是在计算字符串长度时\0不被计入总长度。
%s匹配的是字符串,所以在执行打印arr1时,在遇到arr1这个字符串末尾被隐藏的\0时就会终止打印。而arr2在内存空间中却不存在结束标志,所以会在'c'被打印后继续在arr2后面的空间里寻找\0这个标志。这也就是出现了乱码的原因。
那么我们该如何解决乱码问题呢?
char arr3[]={'a','b','c','\0'};
这样便可以实现打印出abc了。在末尾添加一个结束标志手动结束他。
计算长度则是之前提到的strlen函数。
#include<stdio.h>
#include<string.h>
int main() {
char arr1[] = "hello,world";
char arr2[] = { 'a','b','c' };
char arr3[] = { 'a','b','c','\0' };
printf("%s\n", arr1);
printf("%s\n", arr2);
printf("%s\n", arr3);
int len1 = strlen(arr1);
int len2 = strlen(arr2);
int len3 = strlen(arr3);
printf("%d\n", len1);
printf("%d\n", len2);
printf("%d\n", len3);
}
感兴趣的看官可以自行运行代码查看。
那么接下来就是转义字符了。
问题:如果你想要打出类似这样的内容:c:\code\test.c
#include<stdio.h>
int main() {
printf("c:\code\test.c\n");
return 0;
}
如果只是按部就班的往printf里搬的话会得到不合期望的结果。
这是因为\t被当作了转移字符,其作用是水平制表符。
转义字符表如下
不需要特别去记忆,多用就熟练了。
其中需要讲讲的是最后两个。
#include<stdio.h>
int main() {
printf("c:\code\test.c\n");
printf("%d\n", '\073');
printf("%c\n", '\073');
return 0;
}
注意,\073是作为一个转义字符出场,需要被当作一个字符来处理,所以需要用' '来括好,代表其是个字符。
其运行结果是59与;
为什么073会带来两个不同的数据呢?首先需要了解一下asc码表。
从0到127,每一个十进制都代表了一种字符或者转义字符。'\073' 在这里是八进制,转换为十进制为59,所以在匹配%d时显示的是作为十进制的59,当匹配%c时,则匹配了59所对应的;
\xhh则是同理。
需要注意的是十进制0与字符'0'还有'\0'的区别,查表可以知道,字符0的本质是十进制的48,'\0'的本质则是十进制的0了。千万不要混淆。要理解asc表的定义,同样不需要记忆,多用就熟练了。
6.选择语句和循环语句
什么是选择呢?好好学习每天勤奋码代码还是当躺平青年?
勤奋学习说不定就会收到大厂offer,而躺平则可能只能去卖红薯。
#include<stdio.h>
int main() {
int choice=0;
printf("勤奋码代码还是躺平?(1或者0)");
scanf("%d", &choice);
if (choice == 1) {
printf("offer get");
}
else
{
printf("卖红薯");
}
return 0;
}
选择语句的核心就是if和else的应用。循环也是同理,在后面再进行一个详细的讲解。
#include<stdio.h>
int main() {
printf("study hard");
int code = 0;
while (code < 100) {
code++;
printf("work harder\n ");
}
if (code >= 100) {
printf("amazing!");
}
return 0;
}
循环的核心则是while的使用。
7.函数
在数学中,我们所认识的函数是y=f(x)的形式,而在c中他的特点则是简化代码,代码复用。
函数可以用来实现某一功能,就像库中本来就内置的输出函数printf或者输入函数scanf。如果每次输出输入都要自己去写详细的过程,整个编程过程会变得非常繁琐。当你完成一个函数的创建后,在下次要使用时直接调用就很方便,极大的简化了你的操作。
#include<stdio.h>
int main() {
int num1 = 0;
int num2 = 0;
int sum = 0;
scanf("%d %d", &num1, &num2);
sum =num1 + num2;
printf("sum=%d\n", sum);
return 0;
}
上面是一个简单的输入两值然后相加输出的过程,那么如何使用函数来书写呢?
int add(int x, int y) { //创建函数名为add,同时有两个输入
int z = x + y; //让变量z等于输入两数和
return z; //返回z的值
}
int main() {
int num1 = 0;
int num2 = 0;
int sum = 0;
scanf("%d %d", &num1, &num2);
sum = add(num1,num2);
printf("%d", sum);
return 0;
}
这就是简单的函数运用,在第三节我会详细梳理函数在c语言中的使用。
9.数组
如果我们要一次存储1-10的数字,该怎么去存储呢?
c语言给出了解决方法,使用数组。数组就是相同类型的元素集合。
数组该如何去定义呢?
int arr[10]={1,2,3,4,5,6,7,8,9,10};
char ch[5]={'a','b','c','d','e'};
可以不用将数组的空间放满,不放满的话剩下的空间便用0来填充,这叫不完全初始化。
在c语言中规定,数组的每个元素都有一个下标,而且下标从0开始。
举个例子
int arr[10]={0};
数组空间 0,0 ,0 ,0 ,0 ,0 ,0 , 0, 0, 0,
下标 0,1, 2, 3, 4, 5 ,6 , 7 ,8, 9
下面来介绍简单的数组使用。
实现一个简单数组的遍历。
#include<stdio.h>
int main() {
int i = 0;
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
for (i = 0; i < 10; i++) {
printf("arr[i]=%d\n", arr[i]);
}
return 0;
}
9.操作符
操作符在这里做一个简单的介绍。眼熟即可。掌握几个常用操作符。
算术操作符
+ - * / 传统的加减乘除
% 取余操作
这里主要讲一下除和取余操作。
#include<stdio.h>
int main() {
int a = 17/4;
int b = 17 % 4;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
得到的答案是4和1,除操作很好理解,17除以4,因为a是整型变量,只留整数,所以得到4
取余则是17除以4的余数。
那么如果想得到小数呢?是否将a从int变换为float就行了呢?答案是否定的。
float a=17/4得到的结果也只会是4.00000
敲个小重点
/的两端为整型,则得到的仍然是整数。/两端至少要有一个为浮点型才会让结果得到预期的小数。
但取余操作两端必须要为整数,否则会报错。
移位操作符
>> <<
位操作符
& ^ | 与二进制有关,之后详细解释
赋值操作符
= += -= *= /=
挑一个+=来解释吧,后面三个原理相同。还有涉及到进制的>>=等也是之后解释。
a+=5的意思其实就是a=a+5
单目操作符
! 逻辑反操作
在c语言中规定0为假,非0则为真 ,常见于while循环
while(!a){
printf("a is false");}
当a为0时,!a等于真,则会进入循环,输出相应的语句。
+ 正值
- 负值
& 取地址
sizeof 操作数类型长度(字节为单位)
~ 二进制按位取反
(类型) 强制类型转换
强制转换用法:int a=(int) 3.14;
下面则是重点
-- 前置或后置
++ 前置或后置
看代码示例
#include<stdio.h>
int main() {
int a = 100;
int b = a++;
printf("a=%d,b=%d\n", a, b);
b = ++a;
printf("a=%d,b=%d", a, b);
return 0;
}
输出结果为
a=101,b=100
a=102,b=102
这是因为前置+的规则为先自加后用,++a的意思就是先将a的值加1再使用,
a++则是反过来,先将a的值赋予了b,再对a进行自增操作。即先用后自加。
关系操作符
>
<
>=
<=
!=
== 最后两个用于判断是否相等
用法:如if(a==5)或者if(a!=5)
逻辑操作符
&& 逻辑与 (二者同时为真则整体为真)
|| 逻辑或 (二者同时为假则整体为假)
条件操作符
exp1?exp2:exp3
举例
#include<stdio.h>
int main() {
int a = 20;
int b = 15;
int c = (a > b ? a : b);
printf("%d", c);
return 0;
}
exp1是判断条件,就像代码这里我们判断a是否大于b
如果exp1为真,则返回a,如果exp1为假,则返回b。
因为a确实大于b,所以我们返回了a的值并赋给了c,这段代码本质上是在选出较大的那个数。
基本常用操作符就是以上的这些。
10.关键字
在c语言中提供了丰富的关键字,这些关键字都是语言本身就预设好的,我们用户不可以去创建关键字。接下来介绍几个常看到的关键字,更多的在后续文章进行讲解。
register----寄存器
计算机中的数据通常存储在硬盘,内存,高速缓存,寄存器中,四者按顺序运行速度增长,造价也更高,但相应的存储空间也更小。
register int a = 10;
作用是建议编译器将a=10放在寄存器中,若是程序需要频繁调取a则会使调取速率更快,效率也更高。
关键字typedef
typedef在这里可以理解为类型的重命名。
#include<stdio.h>
typedef unsigned int uint;
int main() {
uint a = 10;
return 0;
}
也就是将自己取得名字赋给原有的数据类型,这里就将uint代替了unsigned int。起到简化代码的作用。
关键字static
static用来修饰变量与函数。
1.修饰局部变量称为静态局部变量。
#include<stdio.h>
void test() {
int q=0;
q++;
printf("%d\n", q);
}
int main() {
int i = 0;
for (i = 0; i < 10; i++) {
test();
}
return 0;
}
这串代码实现输出了10个1,因为上文提到过,q在这里是一个局部变量,他只能在所属于的那个{}中起作用,所以当运行出了{}后q就被销毁,下一次在运行时q重新被赋值为0;
但是当我们改动一个小地方
static int q=0;
会发现程序编译后输出的是1-10,这是因为static修饰局部变量改变了其生命周期,但不改变其作用域。 static让q这个局部变量即使出了作用域仍然可以起作用,直到整个程序结束。
修饰全局变量与函数
不管是static修饰全局变量还是函数,都会使得其只能在本源文件中使用,跨源文件使用时会出现连接性错误,系统会报错。感兴趣可以自己试试运行代码会出现什么情况。
总之,全局变量本身具有外部链接属性,也就是说,在A文件中定义的一个变量,可以在B文件中通过链接使用。但是如果被static修饰后,就会丧失外部链接属性,反而变成了内部链接属性,使其只能在A文件中使用。函数也是如此。被static进行了限制,也就是通俗的讲,因为带有static,所以被“隔离”了。
11.#define定义常量与宏
#include<stdio.h>
#define A 10 //定义标识符常量
#define sum(x,y) ((x)+(y)) //定义宏
int main() {
int a = sum(1, 2);
printf("%d", a);
return 0;
}
宏其实与函数类似,只不过大多数情况下宏的处理比函数更加简单。
12.指针
终于来到了c语言重点中的重点,灵魂中的灵魂——指针。指针成就了c语言可以说毫不为过,指针奠定了c语言长盛不衰的根基。所以指针一定要重点掌握。
要了解指针先要理解内存。
内存作为电脑中非常重要的存储器,基本所有的程序都需要在内存中运行。那计算机该如何合理的利用分配利用内存呢?答案就是将内存分割成一个个小的内存单元,其大小为1个内存1单元1个字节。并且为了有效的去访问他,计算机为内存单元进行了编号,编号就被称为内存单元的地址。
严谨的讲,编号(地址)其实就是指针,只是口头上我们常将指针变量叫做指针。
编号和内存空间的关系其实就像是门牌号和房间的关系一样,编号只是为了寻找到内存空间的一个目标而已。就像我们去寻找一个人,总要先问清他的详细地址在哪里。这是一个道理。 有了地址,才知道往哪里去。
那么有个问题,如果去访问一个内存单元,那么他内存单元的地址是如何产生的?
假设我们使用的电脑是32位(有32根数据线),通电产生信号0,1,这样就会有2的32次方个二进制序列,就可以去作为2的32次方个地址。学习过计算机组成原理的看官应该会很清楚吧,感兴趣的同样可以自行度娘。
变量的创建是在内存中分配空间的,比如我们上文讲过的
int a=109;
这样就给一个名叫a的空间分配了4个字节的内存空间啦。忘了的老铁回到之前的sizeof那里可以温习。每个内存单元都存在地址,那么变量也是有地址的,该如何去取出变量的地址呢?
#include<stdio.h>
int main() {
int a = 10;
&a;//用取址符取出a的地址
printf("%p", &a); //%p是以地址形式打印
return 0;
}
这里的a是int类型,分配了四个字节的空间,虽然每个字节都有地址,但取出的是第一个字节的地址。
打印出来的地址是以16进制显示的,但不管是什么进制来显示,都不过只是一种表现形式罢了。其只是地址的不同表现形式。
不仅有整形指针,也可以推广到其他的类型去。比如字符型指针。
#include<stdio.h>
int main() {
char ch = 'q';
printf("%c\n", ch);
char* p = &ch;
*p = 'a';
printf("%c\n", ch);
return 0;
运行的结果是q与a,说明这个指针变量的创建是成功的。*p的作用在这里就等于是找到了ch的家门口。
指针变量也具有大小的说法。
结论如下
指针变量大小取决于地址的大小
32位平台下地址为32个bit位
64位平台下地址为64个bit位
#include<stdio.h>
int main() {
printf("%d\n", sizeof(char*));
printf("%d\n", sizeof(int*));
printf("%d\n", sizeof(short*));
printf("%d\n", sizeof(double*));
return 0;
我的电脑是64位,所以输出的结果都是8(sizeof的单位是byte)
13.结构体
结构体的使用使得c语言具有了描述复杂类型的能力。
就像是我们需要一个学生管理系统,需要记录每一个学生的姓名,性别等基本信息,那么就只有使用结构体来进行描述了。
struct stu {
char name[20];
int age;
char gender[5];
char id[15];
};
这样便定义了一个stu类型(名字可以自己取,但要方便辨认,但struct是固定格式)。
这个类型包括了名字,年纪,性别,学号四种学生属性。
结构体的初始化:
struct stu s = { "taylor swift",30,"female","1989" };
这样就创建了一个类型为stu且名叫s的变量,并且对其进行了赋值。
那么如何打印呢?
printf("%s %d %s %s", s.name,s.age,s.gender,s.id);
还可以通过结构体指针来进行访问。
struct stu* p=&s;
printf("%s %d %s %s\n", p->name, p->age, p->gender, p->id);
这里创建的指针p的类型是struct stu
【结束语】那么以上就是c语言的主要脉络了,从下一章开始我们将着重介绍c语言的各种细节。包括分支循环语句,函数,数组等。