本文章皆来自于本人自己对C语言上课内容的理解,如果有错误恳请帮忙留言私信纠正,感谢各位!
目录
1.第一个C语言程序
1.1 在屏幕上打印hello world!
#include <stdio.h>
int main()
{
printf("hello world!");
return 0;
}
注意:在最新的C语言标准中,main函数前的类型为int而不是void
1.2 C语言的基本结构
简单来说,一个C语言程序就是由数个头文件和函数组成的
#include <stdio.h>
//stdio.h是标准输入输出standard(标准)、input(输入)、output(输出)的缩写
//当程序中需要使用scanf、printf等库函数时需要加入<stdio.h>这个头文件
int main()
//int为一种数据类型,表示定义一个整型。
//main表示主函数,是程序的入口,一个程序有且只能有一个main函数
//C语言的代码都是从main函数的第一行开始执行的
{
//在双引号中间输入hello world!(即在双引号之间输入需要打印的东西)
printf("hello world!");
//printf 是一个库函数 - C语言的标准库中提供的一个的现成的函数,直接就能使用
//功能;在屏幕上打印信息
//printf是输出函数,打印函数,使用前一定要包含头文件 stdio.h
return 0;
//return是函数的返回值,根据函数类型的不同,返回的值也是不同的
}
1.3 常见的数据类型
C语言中有众多数据类型,不同的数据类型可以表示日常生活中的各种值
char //字符数据类型
short //短整型
int //整形
long //长整型
long long //更长的整形
flaot //单精度浮点数
double //双精度浮点数
浮点数据是指带小数的数字.
生活中有很多信息适合使用浮点型数据来表示,比如:人的体重(单位:公斤)、商品价格、圆周率等等。
而每个不同的数据类型能储存的字符数也不同,可以根据以下程序得出每种数据类型对应的字节数
#include <stdio.h>
int main()
{
printf("%d\n", sizeof(char));
printf("%d\n", sizeof(short));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof(long));
printf("%d\n", sizeof(long long));
printf("%d\n", sizeof(float));
printf("%d\n", sizeof(double));
//%d是打印10进制的整型
//sizeof是运算符也叫做操作符
// \n是转义字符中的换行字符
return 0;
}
当代码运行起来后我们可以得出每种数据类型对应的字节数(大小)依次为1,2,4,4,8,4,8
在图中我们可以看见整形int和长整型long的字节数一样,其实这是由于C语言中规定sizeof(long)>=sizeof(int)就行了
1.4 C语言中的储存单位
bit 1byte=8bit
byte--字节 1KB=1024byte
KB 1MB=1024KB
MB 1GB=1024MB
GB 1TB=1024GB
TB ...
...
注:在C语言中一个汉字占2个字节
1.5 C语言中的程序解释——注释
适当的注释在团队编程中有利于提高团队的工作效率,而注释是给程序员看的,不是给电脑看的,注释过的函数在程序中不运行。
在C语言注释中方法一般有两种:
多行注释: /* 注释内容 */
单行注释: //注释一行
1.6 标识符
C语言规定,标识符可以是字母(A~Z,a~z)
、数字(0~9)
、下划线_
组成的字符串,并且第一个字符必须是字母或下划线。在使用标识符时还有注意以下几点:
1.标识符的长度最好不要超过8位,因为在某些版本的C中规定标识符前8位有效,当两个标识符前8位相同时,则被认为是同一个标识符
2.标识符是严格区分大小写的。例如Age和age是两个不同的标识符。
1.7 格式化输出语句
格式化输出语句,也可以说是占位输出,是将各种类型的数据按照格式化后的类型及指定的位置从计算机上显示。
其格式为:printf("输出格式符",输出项)
;
在C语言中打印不同类型的数据需要用不同的格式符,且格式符的个数要与变量、常量或者表达式的个数一一对应。
例如:
int a=10;
float b=5.32f;
char ch='w';
printf("%d %f %c,a,b,ch");
//整数:%d 小数:%f 字符:%c
//在C语言中小数位数可以根据在%和f添加数字表示精确到第几位
2.变量、常量
2.1 定义变量的方法
变量的定义方法一般是:数据类型+变量名;变量可根据需求赋值
int high=170
float weight=50.0f
//在C语言中float定义的变量如果在小数后边不加f程序会自动认为该小数为double类型
char vo='a'
//C语言中定义字符,字符必须在‘’中引起
2.2 变量的分类
根据变量位置的不同一般可以将变量分为全局变量和局部变量:
那么什么是全局变量和局部变量?
局部变量:一般将定义在函数中的变量称为局部变量,其只能在函数内部使用。
全局变量:定义在全局作用域中的变量,即函数外的变量,称之为全局变量。
那么当全局变量和局部变量同时出现时谁的优先级更高呢?
#include <stdio.h>
int a = 10 ; // 全局变量
int main ()
{
int a= 20 ; // 局部变量
printf ( "%d\n" , a );
return 0 ;
}
在这段代码中我们可以清楚的看见我们定义了两个a出现,且一个在{ }内部一个在{ }外部;那么当代码运行起来会不会报错呢?
由图可见当代码运行起来时并不会报错,并且输出值为20;这是因为在C语言中,当全局变量与局部变量冲突时,局部变量的优先级高于全局变量。
注意:虽然全局变量使用起来方便,但为了防止冲突和安全性,尽量避免定义全局变量。
2.3 变量的生命周期和作用域
生命周期:变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段
局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束。
全局变量的生命周期是:整个程序的生命周期。
作用域:作用域是程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用
的而限定这个名字的可用性的代码范围就是这个名字的作用域。
局部变量的作用域:是局部变量所在的局部范围内
就例如在本段代码中,b只在自己定义的大括号内的局部范围内可以调试,出了它本身定义的大括号范围后再想打印b的数值就会报错,因为此时b出了自己的作用域就相当于消失了。
全局变量的作用域:全局变量的作用域是整个工程
而在这段代码中我们可以看出a的值在两个范围内都可以使用。
2.4 常量
C 语言中的常量和变量的定义的形式有所差异。C 语言中的常量分为以下以下几种:
字面常量,const 修饰的常变量,#define 定义的标识符常量,枚举常量
(1)字面常量
100——整型字面值
'w'——字符字面值
3.14——浮点型的字面值
"abcdef"——字符串常量
(2)const 修饰的常变量
const 是常属性的
const int num = 10;但是num本质上是个变量,虽然具有了常属性,但不能被修改。
如果想证明被const修饰的变量本质仍然是变量,可以根据数组的一个特性来判断
在数组中[]之间只能为常量,如果误输入变量就会报错。n虽然被const修饰,具有了常属性,不能被修改,但是本质上还是变量,所以不能用来指定数组的大小
而根据数组的这个特性我们可以设计以下代码得出const修饰的num的本质是变量的结论
(3)#define 定义的标识符常量
在C语言中,可以用一个标识符来表示一个常量,称之为符号常量。符号常量在使用之前必须先定义,其一般形式为:
#define BIG 1000
int main()
{
int arr[BIG];
#define SMALL 0
printf("%d\n", BIG);
BIG = 2000;
printf("%d\n", SMALL);
return 0;
}
(4)枚举常量
枚举 - enum
//声明的一个表示性别的枚举类型
enum Sex
{
//下面三个符号是Sex这个枚举类型的未来的可能取值
//枚举常量
MALE=5, //0
FEMALE,//1
SECRET //2
};
注:枚举常量的默认是从0开始的,依次向下递增1的。
3.字符串、转义字符
3.1 字符串
什么是字符串?
"hello world!\n"
这种由双引号引起来的一串字符称为字符串字面值,简称为字符串。
注:字符串的结束标志是一个\0的转义字符。在计算字符串长度的时候\0是结束标志,不算做字符串内容
例如字符串“abc"里面的内容是“a,b,c和\0”只不过在字符串的末尾\0被隐藏起来了而已。
想要求知此字符串的具体内容,我们可以按F10,开始调试,在开始调试后我们可以打开监视窗口
我们可以根据以下代码在经过打开监视窗口后得到字符串“abc”内的具体内容
int main()
{
char arr1[] = "abc";
char arr2[] = {'a', 'b', 'c'};
return 0;
}
由此图片我们可以清楚的看到字符串“abc”中是隐藏了一个\0的,而arr2中就是三个字符{‘a','b','c'}所以其监视出来的内容也就只有‘a','b','c'
但是我们证明\0只是字符串结束的标志而不算作字符串内容呢?
#include <string.h>
//string是调用库函数strlen所必须的头文件
#include <stdio.h>
int main()
{
char arr1[] = { "abc" };
int len = strlen(arr1);
//strlen 是一个库函数,是专门用来求字符串长度的
//strlen再求字符串长度的时候,统计的是\0之前出现的字符的个数
printf("%d\n", len);
return 0;
}
我们可以根据上面的代码得出字符串“abc"的长度,得出的结果如下图
我们可以清楚的看到此字符串的长度为3
那么\0的重要性到底是什么呢?
我们可以通过以下代码的结果来认识一下
#include <string.h>
#include <stdio.h>
int main()
{
char arr1[] = "abc";
char arr2[] = { 'a', 'b', 'c' };
int len = strlen(arr1);
printf("%d\n", len);
len = strlen(arr2);
printf("%d\n", len);
return 0;
}
当代码运行后我们可以看见,aar1的长度就是3,而arr2的长度为33
这是因为在arr2中的三个字符中没有\0,而strlen统计的是\0之前出现的字符的个数,所以当没有识别到\0时其会一直往下读,而到最后求出来的长度33也只不过是个随机值,在不同运行情况下也可能为25,44,48之类的。
而想要解决这种情况只需要在arr2中主动加上’\0’即可,此时arr2的长度就变成正常的3了,如图,
3.2 转义字符
转义字符是指在ASCII和Unicode等字符集中的无法被键盘录入的字符、被当作特殊用途而需要转换回它原来的意义的字符。而转义字符的转义时指字符已经被转换了意义。
例如我们想要在屏幕上打印abcd\nefd
我们会发现此时在屏幕上打印的东西略有变化,这是因为在C语言中\n此时被转换了意义,它变成了换行字符。如果我们想就是只打印abcd\nef就需要在\之前再添加一个\防止\n被转义。
在C语言中我们常用的转义字符就如下表:
其中,\n和\t时最常用的两个转义字符:
\n用来换行,让文本从下一行的开头输出;
\t用来占位,一般相当于四个空格,或者tab键的功能。
4.函数
在C语言中的函数是一段可以重复使用的代码,用来独立地完成某个功能,它可以接收用户传递的参数,也可以不接收;将代码段封装成函数的过程叫做函数定义。
例如我们写一个简单的加法代码
#include <stdio.h>
int main()
{
int m = 0;
int n = 0;
//输入
scanf("%d %d", &m, &n);
//求和
int sum = m + n;
//输入
printf("%d\n", sum);
return 0;
}
但如果我们想要多次使用这个加法程序就需要写很多次这个代码,在实际操作中较为麻烦,因此我们可以写一个关于加法的函数,方便调用加法代码。
#include <stdio.h>
int Add(int x, int y)
//在()里面返回的类型要与返回值的类型都一样,在此处都为int
{
int z = x + y;
return z;
}
int main()
{
int m = 0;
int n = 0;
//此处的m,n=0都是初始化,并不是赋值为0
//输入
scanf("%d %d", &m, &n);
//求和
int sum = Add(m, n);//(m,n)在操作符中就是函数调用操作符
//()里的操作数是:Add,m,n.
//输出
printf("%d\n", sum);
return 0;
}
在此时也许会有人发问同样的功能写这么多代码不是复杂化了吗?但是在以后的学习中我们会发现当我们在一个程序中多次使用同一个代码的时候我们调用函数会更加为方便。
5.数组
数组:一组相同类型元素的集合就叫做数组
5.1 数组定义
int arr[10]={1,2,3,4,5,6,7,8,9,10}//定义一个整型数组,里面最多可以放10个元素
5.2 数组的下标
C语言规定:数组的每个元素都有一个下标,下标是从0开始的。
数组是可以通过下标来访问的。
例如:
6.操作符
在C语言中操作符是说明特定操作的符号 ,它是构造C语言表达式的工具 。
附操作符表:
优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 | 说明 |
1 | [] | 数组下标 | 数组名[常量表达式] | 左到右 | -- |
() | 圆括号 | (表达式)/函数名(形参表) | -- | ||
. | 成员选择(对象) | 对象.成员名 | -- | ||
-> | 成员选择(指针) | 对象指针->成员名 | -- | ||
2 | - | 负号运算符 | -表达式 | 右到左 | 单目运算符 |
~ | 按位取反运算符 | ~表达式 | |||
++ | 自增运算符 | ++变量名/变量名++ | |||
-- | 自减运算符 | --变量名/变量名-- | |||
* | 取值运算符 | *指针变量 | |||
& | 取地址运算符 | &变量名 | |||
! | 逻辑非运算符 | !表达式 | |||
(类型) | 强制类型转换 | (数据类型)表达式 | -- | ||
sizeof | 长度运算符 | sizeof(表达式) | -- | ||
3 | / | 除 | 表达式/表达式 | 左到右 | 双目运算符 |
* | 乘 | 表达式*表达式 | |||
% | 余数(取模) | 整型表达式%整型表达式 | |||
4 | + | 加 | 表达式+表达式 | 左到右 | 双目运算符 |
- | 减 | 表达式-表达式 | |||
5 | << | 左移 | 变量<<表达式 | 左到右 | 双目运算符 |
>> | 右移 | 变量>>表达式 | |||
6 | > | 大于 | 表达式>表达式 | 左到右 | 双目运算符 |
>= | 大于等于 | 表达式>=表达式 | |||
< | 小于 | 表达式<表达式 | |||
<= | 小于等于 | 表达式<=表达式 | |||
7 | == | 等于 | 表达式==表达式 | 左到右 | 双目运算符 |
!= | 不等于 | 表达式!= 表达式 | |||
8 | & | 按位与 | 表达式&表达式 | 左到右 | 双目运算符 |
9 | ^ | 按位异或 | 表达式^表达式 | 左到右 | 双目运算符 |
10 | | | 按位或 | 表达式|表达式 | 左到右 | 双目运算符 |
11 | && | 逻辑与 | 表达式&&表达式 | 左到右 | 双目运算符 |
12 | || | 逻辑或 | 表达式||表达式 | 左到右 | 双目运算符 |
13 | ?: | 表达式1? 表达式2: 表达式3 | 右到左 | 三目运算符 | |
14 | = | 赋值运算符 | 变量=表达式 | 右到左 | -- |
/= | 除后赋值 | 变量/=表达式 | -- | ||
*= | 乘后赋值 | 变量*=表达式 | -- | ||
%= | 取模后赋值 | 变量%=表达式 | -- | ||
+= | 加后赋值 | 变量+=表达式 | -- | ||
-= | 减后赋值 | 变量-=表达式 | -- | ||
<<= | 左移后赋值 | 变量<<=表达式 | -- | ||
>>= | 右移后赋值 | 变量>>=表达式 | -- | ||
&= | 按位与后赋值 | 变量&=表达式 | -- | ||
^= | 按位异或后赋值 | 变量^=表达式 | -- | ||
|= | 按位或后赋值 | 变量|=表达式 | -- | ||
15 | , | 表达式,表达式,… | 左到右 | -- |
注意:在操作符中
除法操作符的两端如果都是整数,执行的是整数除法
只要有一个操作数是浮点数,执行的就是浮点型的除法
取模 - 得到的是余数, % 这个操作符只能作用于整数
7.常见关键字
C语言提供了丰富的关键字,这些关键字都是语言本身预先设定好的,用户自己是不能创造关键字的。
例如以下操作符:
按照其种类分类我们可以将其分为以下几类
下面我带大家简单了解(具体的内容较为困难,以后会慢慢逐渐展开讲解)几个常用的关键字的作用:
7.1 typedef----类型重命名(别名)
typedef unsigned int uint
int mian()
{
unsigned int a=100;
//uint a=100;
}
在此段代码中我们发现如果想用unsigned int定义较为麻烦,此时我们就可以用typedef给这个类型重新起个名字uint,此时uint的作用效果和unsigned int的效果一样,更为方便。
7.2 static----静态的
想要了解static,我们可以先了解其作用。
在C语言中static的作用主要有以下三种:
1. 修饰局部变量 2.修饰全局变量 3. 修饰函数
1.修饰局部变量
首先我们先分析一下这段代码的执行效果:
#include <stdio.h>
void test() //4.进入test函数
{
int a = 1; //5.a=1
a++; //6.a+1
printf("%d ", a); //7.打印a
} //8.a值销毁
int main() //1.所有的代码从主函数开始进入并执行
{
int i = 0;
while (i < 10) //2.while循环,循环到i>10
{
test(); //3.调用test函数
i++; //9.i+1
} //10.判断i是否小于10,若小于返回第一步重复执行直到i>10
return 0;
}
当代码执行完我们可以得到此段代码的效果是打印10个2,如图:
但当我们在int前加上static后此段代码的执行效果又是什么样呢?
#include <stdio.h>
void test()
{
static int a = 1;
a++;
printf("%d ", a);
}
int main()
{
int i = 0;
while (i < 10)
{
test();
i++;
}
return 0;
}
我们可以看到此时输出的值变成了2-11,那为什么会变成这样呢?
这是因为在int前加上static后代码执行的时候到了上次运行的第八步时a的值不会再销毁并且保存下来等到下一次代码经过的时候使用。(我们可以通过之前所教的监视过程一步一步看怎么执行的)
其被static修饰后值不会销毁的原因主要有以下两个
1.一个普通的局部变量是放在栈区的,而被static的修饰的局部变量,是放在内存的静态区的
2.存储位置发生了变化,使得局部变量出了作用域不会销毁,影响了变量的生命周期
拓展:在此段代码中a是放在内存中什么位置的呢?
由此我们可以看到作为局部变量的a是放在内存中的栈区的。
2.修饰全局变量
当我们在同一个源文件下创建了两个项目时如果在一个项目中定义了一个全局变量,那么在另一个项目中只需要用extern声明就可以在另一个项目中使用。
因为全局变量是具有外部链接属性的,在整个工程中都可以使用,在其他源文件内部,只要适当的声明就可以使用。
但如果在全局变量定义的源文件中的int前加上static后,全局变量就不能在其他源文件中使用了,这是因为static在修饰全局变量时全局变量的外部链接属性就变成了内部链接属性,只能在自己所在的.c文件内部使用,因为static影响了变量的作用域。
3.修饰函数
static修饰函数和修饰全局变量效果基本相同,在函数的int前加上static后函数也就不能在其他源文件内部使用了。
8. #define定义常量和宏
8.1 #define定义常量
#include <stdio.h>
#define G 100
#define EDG "WE ARE THE CHAMPION"
int main()
{
printf("%d\n", G);
printf("%s\n", EDG);//因为定义的EDG是一串字符,所以打印用%s
}
我么可以通过#define+想要定义的名称+定义的内容来定义一个常量,字符串或者代码。
8.2 #define定义宏
宏定义就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。
#include <stdio.h>
#define MAX(x, y) ((x)>(y)?(x):(y))
int main()
{
int a = 10;
int b = 20;
int m = MAX(a+3, b);
//int m = (a > b ? a : b);
printf("%d\n", m);
return 0;
}
注:()里x,y前是没有类型的是因为#define定义的宏是不在乎类型的。
9. 指针
9.1 内存
内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的。
所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。
我们可以通过此图并联系到日常生活中的门牌号之类的来理解指针
int main()
{
int a=10; //此段代码的真正意义是向内存申请4个字节的空间以存放10
int* pa=&a //把pa叫指针变量 <pa是存放地址(指针)的一个变量,所以把pa叫做指针变量>
//a实际占用4个字节的空间,每个字节都有地址,但是&a拿到的是第一个字节的地址(也就是小的地址)
//int*是pa的类型 *说明pa是指针变量 int实在说明pa指向的对象是int类型的
return 0;
}
因为每个内存单元都有一个地址,所以a申请了4个内存单元就有四个内存地址
那么pa到底有什么用处呢?我们可以运行以下代码来了解一下:
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a; //把pa叫指针变量, & - 取地址操作符
printf("%d\n", a);
*pa = 20; //* 解引用操作符
printf("%d\n", a);
return 0;
}
我们可以发现屏幕上打印出来了10和20,而20是在*pa重新定义后打印出来的,这是因为*pa在此时就相当于a,它是用地址的方式来表达的,改动*pa的值就相当于改动a的值。
其在内存空间中的占用情况大概就如图
9.2 指针变量的大小
我们知道,指针变量是用来存放地址的,地址是32个0或1组成的2进制序列需要32bit位的空间存储,指针变量就需要4个字节存储(32位机器)
<64位机器是64个0或1组成的2进制序列,需要64bit位的空间存储指针变量就需要8个字节存储>
我们可以通过以下代码计算出各种指针变量的大小
#include <stdio.h>
int main()
{
char* p1;
int* p2;
double* p3;
printf("%zd\n", sizeof(p1));
printf("%zd\n", sizeof(char*));
printf("%zd\n", sizeof(p2));
printf("%zd\n", sizeof(int*));
printf("%zd\n", sizeof(p3));
printf("%zd\n", sizeof(double*));
//%zd是因为在VS2022中sizeof返回的类型是size_t,而%d返回的类型是int,所以使用%zd更为合适
return 0;
}
因为在左上角的解决方案平台上我选择的是x64(即64位操作系统)所以我的运行结果都是8
10. 结构体
结构体是C语言中特别重要的知识点,结构体使得C语言有能力描述复杂类型
比如描述一个学生,学生包含:名字+年龄+性别+学号这几项信息
在C语言中就只能用结构体来描述了。例如:
struct Student
{
char name[20];//名字
int age;//年龄
char id[15];//学号
float score;//成绩
};
通过本段代码我们就能清楚的了解一个学生的基础信息。
本次对C语言的基础认识到这里就暂时结束了,后面会继续更新更为进阶详细的C语言知识,希望大家多多支持。
注:如果需要转载引用本文章的内容请注明出处,谢谢。