简单介绍一下本篇文章的主要目的:
是让新手玩家快速认识并了解C语言基础内容和语法结构,并没有深入去探究,让大家对C语言有个大概且全面的认识。
相信我,这篇文章虽然很长,但不会让你从入门到入土。认真看完本篇打好基础,对后面深入学习C/C++等知识的帮助是非常大的,大家加油,不管学什么都要有恒心!!!
再次强调:本篇只是初始C语言!
那为什么要选择C语言呢?
C生万物,编程之本!C语言是母体语言,是人机交互接近底层的桥梁,学会C/C++,相当于掌握了核心技术并且在TIOBE排行榜中,C/C++长期霸占前三名,没有丝毫撼动,可谓是经典永不过时!
计算机语言的发展:二进制的指令 --> 汇编语言(使用助记符来代替带二进制指令) --> B语言 --> C语言,只有C为高级语言,其他都是低级语言。
C语言的国家标准:C89、C90、C99、C11…
ps:本篇代码块生成都是在Visual Studio 2019环境下。
如果想下载请戳:VS2019的安装教程
接下来请看这么一段代码:
#include
int main()
{
printf("hello everybody!\n");
return 0;
}
执行后在控制台显示如下:
不懂没关系,接下来会对这段代码进行解释:
C语言中一定要有主函数也就是main函数
main函数是程序的入口
一个工程中main函数有且只有一个
//标准的main函数写法如下:
int main()
{
return 0;
}
//return 0;的意思是返回一个整数(整形)0
//主函数前面的int代表的是整形类型,与返回值相对应
//现在可以简单理解为固定程序框架,写代码
//前要有这么个框架,代码写在里面就可以了
//int是整形类型,main是主函数
//后面的小圆括号里为参数,下面一对大括号,写上return 0;
既然了解了主函数,那么怎么在控制台上打印信息呢?
//printf是一个库函数(格式化输出函数)
//专门用来打印数据信息的
//库函数是C语言本身提供给我们的函数,不是我们自己创造的
//既然是别人给你的函数,你使用的话就必须要向别人打个招呼
//那么如何打招呼(包含头文件呢)呢?
#include //包含头文件!
int main()
{
printf("hello everbody!");
//如果是打印纯文字数据的话puts函数也可以并且自动换行
return 0;
}
//如果不包含头文件就使用库函数的话运行会报错
//std - standard - 标准
//i - input - 输入
//o - output - 输出
//标准输入输出函数
vs2019运行代码快捷键:Ctrl + f5 / fn + Ctrl + f5
相信大家认真看完之后就一定会写出自己的第一个C程序了。
在了解数据类型之前,问问大家为啥要写代码?
很简单,是为了解决生活中的问题~
比如说要买一本书,书有书名、价格、编号等一系列信息
这些信息的数据类型在C语言里叫什么呢?例如:
《XXC语言》 – 书名 – 字符串
66.6 – 价格 – 浮点数
300 – 页数 – 整形 – (计算机基础称为定点数)
…
那么C语言里到底有哪些类型呢,如下:
数据类型 | 名称 |
---|---|
char | 字符数据类型 |
short | 短整型 |
int | 整形 |
long | 长整型 |
long long | 更长的整型 |
float | 单精度浮点型 |
double | 双精度浮点型 |
浮点数是小数点可以浮动的数
C语言里有字符串类型吗? 答案是没有
在探究第一问时,我们要先搞清楚每种类型在内存中大小是多少
请看一下代码:
int main()
{
%d指定了打印格式,按照整形打印,\n是换行符
sizeof是C语言的操作符,就是用来计算每个类型占多少内存
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));
return 0;
}
运行结果为:
每行sizeof计算的数字的单位是每个数据类型占内存的几个字节
计算机里的单位:
bit – 比特位
byte – 字节
kb – 千字节
mb – 兆字节
gb – 千兆字节 吉字节
tb – 太字节
注:八个比特位=一个字节,1024字节=1kb,1024kb=1mb…除了比特其余进率为1024。
C语言规定:sizeof(long)>= sizeof(int)就可以了。
根据上图我们可以得知,char占一个字节也就是八个比特位,short是两个字节16个比特位,int占四个字节32个比特位…所以空间越大,所表达的数值的范围也就越大,也就是说当我们需要不同范围的数据的时候,就给不同的类型来表示。
重点来了,因此我们可以回答第一问,从生活角度:存在这么多类型,是为了丰富的表达生活中的各种值。从内存角度:当类型提供的足够丰富,就能让我们使用的更加灵活,让其空间利用率更高,也就是因为它们大小不同,表示范围就不同,那我们就可以在适当的时候,选择适当的类型。
到这里大家一定对数据类型有了比较好的理解了,那么该怎么使用数据类型呢
还是拿上面提到的书的例子:
int main()
{
根据不同的数据,要用不同类型来存储
也就是用类型向内存申请空间来存储数据
//int是四个字节,也就是创建整形变量page
//向内存申请了四个字节的空间把300存进去
//创建变量的本质是向内存申请空间
int page= 300;
//价格为小数,就用flaot类型来存储
float price = 66.6;
//想精度低一点用float,高一点就用double
return 0;
}
生活中有些值是不变的,有些是变化的。
在C语言中,不变的值用常量的概念来表示,变的值用变量来表示。
int main()
{
//创建变量的基本方法
short age = 20;//年龄
int height = 180;//身高
float weight = 70.5;//体重
return 0;
//注意数据类型和变量名之间要有空格
}
int a = 10;
//大括号外部定义的叫全局变量
int main()
{
int b = 20;
//大括号内部定义的叫局部变量
return 0;
}
注意:局部变量名称不可以重复定义!
那么下面这段代码是对的吗?值是多少?
int a = 100;
int main()
{
int a = 10;
printf("a = %d\n", a);
return 0;
}
程序是对的,输出的是10,这说明了:当全局变量和局部变量名字相同的情况下,局部优先,但是不建议将两种名字定义相同。
写段代码,求出两个整数的和
该怎么写?不妨先捋一下思路:
首先要有两个整数,输入数据,然后求和,最后输出
代码:
//scanf -- 输入函数
//printf -- 输出函数
int main()
{
int num1 = 0;//变量在不初始化时里面都是存的随机值
int num2 = 0;//建议在定义每个变量时都要初始化
//输入两个整数
//%d指定的是格式,按照两个整形读取
//读取的数值放在num1和num2里
//注意中间用逗号隔开,scanf需要的是取地址
//要在变量名前加上&(取地址)
scanf("%d %d", &num1, &num2);
//输入数字的也要带上空格,比如10 20
//求和
//把两个数的和放在一个新整形的变量里
int sum = num1 + num2;
//输出
printf("%d\n", sum);
return 0;
}
注意:如果是第一次使用vs2019运行会报错
解决方法如下:添加 #define _CRT_SECURE_NO_WARNINGS 在源文件的一行,一定是第一行。
接下来介绍一下一劳永逸的方法:自动在每个新源文件下添加 。在VS的安装路径下找到有个newc++file.cpp的文件,双击该文件后进入vs添加并保存,这样每次新建源文件都会自带该命令。
- 作用域
看下面这三段代码,注意变量a的位置:
int main()
{
{
int a = 10;
printf("a = %d\n", a);
//此时控制台输出的是10
}
printf("a = %d\n", a);
//加上这句代码程序会报错:“未声明的标识符a”
//所以可以发现变量a的作用域就是
//在它所在的大括号内部,出了作用域消失
return 0;
}
---------------------
int main()
{
int a = 10;
{
printf("a = %d\n", a);
}
printf("a = %d\n", a);
//这段代码则可以正常运行
//说明a的作用域是整个main函数
return 0;
}
---------------------
int a = 10;
void test()//定义test函数
{
printf("a = %d\n", a);//输出a
}
int main()
{
test();//这里是调用test函数
{
printf("a = %d\n", a);
}
printf("a = %d\n", a);
//代码正常运行,最终的结果是打印了三个a
return 0;
}
如果把a定义在别的源文件里呢?
也可以正常运行,只不过需要注意的是声明来自外部的符号需要用到extern关键字。
因此能得出:
1.局部变量的作用域是变量所在的局部范围。
2.全局变量的作用域是整个工程。
- 生命周期
1.局部变量的生命周期是:进入作用域开始,出作用域生命周期结束。
2.全局变量的生命周期是:整个程序的生命周期。
C语言中的常量分为以下几种:
int main()
{
1.字面常量
30;
3.14;
'a';//字符要用单引号
"abc";//字符串要用双引号
//就是直观可以看出来的数据
------------------------------------------
2.const修饰的的长变量
//在C语言中,const修饰的a,本质上变量
//但是不能直接修改,有常量的属性
int a = 10;
a = 20;
printf("%d\n", a);
//此时打印出的a为20,说明a是变量
//那如果不想被该怎么做呢?
const int a = 10;
//只需要在int前面加上const即可
int a = 10;
//此时a具有常量属性不可修改
//运行程序报错:左值指定const对象
}
3.#define定义的标识符常量
#define MAX 100
#define STR "you are a pig"
编译器在预处理程序时,会将程序中所有的MAX用100来替换
与const长变量不同,define 本质是对所定义的内容进行替换
int main()
{
printf("%d\n", MAX);//输出100
int a = MAX;
printf("%d\n", a);//输出100
printf("%s\n", STR);//输出you are a pig
return 0;
}
------------------------------------------
4.枚举常量
//生活中有些值是可以一一列举出来的
//比如说三原色:红色、蓝色和绿色
//那怎么列出来呢?
enum Color //enum关键字
{
//这三个可能的取值就是枚举常量
RED,
BULE,
GREEN
//常量是不会在内存中开辟空间的,只是符号
//只有当你用这个类型创建变量的时候才会开辟空间
};
//性别
enum Sex
{
MALE,
FEMALE,
SECRET
};
int main()
{
//怎么使用枚举常量
enum Color a = RED;
//RED = 10; //err
//MALE = 20; //err
return 0;
}
注意:枚举常量的值是默认从0开始的
常量是不可以改变的!
“hello world.\n”
这种由双引号引起来的就叫字符串。
int main()
{
//abcd$&12汉字等都叫做字符
//C语言有char数据类型,专门放字符类型的
char a = 'A';//字符变量必须要用单引号引起来
//在数据类型那里提到过,没有字符串类型
//那问题来了,既然没有,那该怎么存储字符串呢
//我们可以把它放到一个字符数组里去
char arr[] = "abcde12345";
//arr后面方括号里的值代表可以存放几个字符元素
//当我们不知道后面有几个字符时,里面的值还可以省略
//省略后会自动根据后面的元素个数来确定开辟几个内存空间
return 0;
}
定义好了字符数组,接下来调试代码看下数组里字符的存储是怎样的:
出现了奇怪的字符’\0’,但我们在定义的时候并没有定义这个字符,那为什么会出现这个呢?
跟着我继续看,接下来,重点来了。
!!!
注意:字符串的结束标志是一个\0的转义字符。在计算字符串长度的时候\0是结束标志,不会算作字符串内容。
下面就举例来说明为什么结束标志很重要,请看下段代码:
int main()
{ //定义字符串的两种形式
char arr1[] = "abcde";
char arr2[] = { 'a','b','c','d','e' };
//注意第二种形式,大括号里要依次放上每个字符
//并且用单引号引起来,逗号隔开
return 0;
}
这两种形式会有什么区别?调试代码来观察一下:
第一种形式arr1用字符串来初始化,我们知道字符串的结束标志为’\0’,所以用双引号定义字符串末尾会隐藏一个\0。
而当我们给arr2里主动放上了字符a,b,c,d,e并没有主动放上\0,因此,arr2里便没有\0。
这是形式上的差异,那么使用上会有什么差异呢?
我们可以把这两个字符串打印出来观察差异:
int main()
{
char arr1[] = "abcde";
char arr2[] = { 'a','b','c','d','e' };
//按照整形输出要用到%d
//而格式化输出字符串要用到的是%s
printf("%s\n", arr1);
printf("%s\n", arr2);
return 0;
}
运行结果如下:
输出结果为什么是这个样子?
在给大家解释之前需要先了解数组在内存中的布局情况:
下面给大家解释一下两种形式的打印结果为什么不同:
打印字符串,只要输出出来就完了,那怎么判断打印完没完,根据’\0’,打印到\0就停止打印,不在往后面打印了。
因为字符串的结束标志是\0。所以打印arr1的时候打印完e就停止了,而打印arr2就没有这种效果,因为它后面没有结束标志\0就会继续打印后面的内存数据,直到遇到\0.
arr2怎么能和arr1一样打印到e就停止呢?相信大家都知道了:就是在最后面加上’\0’,就可以了。
int main()
{
char arr1[] = "abcde";
char arr2[] = { 'a','b','c','d','e','\0'};
printf("%s\n", arr1);
printf("%s\n", arr2);
//此时屏幕输出两行abcde
return 0;
}
这个例子证明了字符串结束标志的重要性,接下来再举个例子:
给大家介绍一个C语言里的库函数strlen() 。含义:string length,求字符串长度的函数,比如说strlen(“abc”)/strlen(arr1),只要把字符串传给它,计算出长度后会返回一个整形结果。
还是拿上面的代码:
#include
#include
//注意:strlen是库函数
//所以在使用前须包含头文件string.h
int main()
{
char arr1[] = "abcde";
char arr2[] = { 'a','b','c','d','e','\0'};
//创建整形变量来接收strlen的返回值
//int len = strlen(arr1);
//printf("%d\n",len);//输出5
//也可以不同创建变量直接打印
printf("%d\n", strlen(arr1));//5
printf("%d\n", strlen(arr2));//5
return 0;
}
问一下大家如果,把arr2里的’\0’去掉,输出的还是5吗?
.
.
.
.
.
去掉\0输出结果就变成了随机值,这是为什么?
其实不难看出,根据输出结果以及了解了数组在内存中的布局情况可以得知:strlen在计算字符串长度的时候,也是数到字符串结束标志’\0’就停止(不算\0!)。arr2后面没有\0,strlen就会一直计算数组后面内存的元素个数,直到遇到\0。
这个例子又一次说明了字符串结束标志的重要性:在字符串里,不管是打印还是计算字符串长度,如果遇到\0,那我们就应该停下来。
小练习:
#include
#include <string.h?
int main()
{
char arr[] = "abc\0def";
printf("%s\n",arr);
printf("%d\n",strlen(arr));
//输出结果是什么?
}
含义:转变原来字符的意思,上面遇到的\n、\0等就是转义字符
上代码:
int main()
{
//这两行代码区别就在于在n前面加了反斜杠
printf("abcn");
printf("abc\n");
//输出结果会一样吗?
return 0;
}
在字符前面加上反斜杠\,这就是转义字符,把原来字符的意思转变了。
int main()
{
//上面的小练习
//这两行代码有什么区别呢?
printf("abc0def");
printf("abc\0def");
return 0;
}
那是不是所有字符都可以转义?并不是,C语言给出了一些转义字符如下表:
转义字符 | 释义 |
---|---|
\? | 在书写多个连续问号时使用,防止它们被解析成三字母词 |
\’ | 用于表示字符常量’ |
\" | 用来表示一个字符串内部的双引号 |
\\ | 用来表示一个反斜杠,防止它被解释为一个转义序列符 |
\a | 警告字符,蜂鸣 |
\b | 退格符 |
\f | 进纸符 |
\n | 换行符 |
\r | 回车符 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ddd | ddd表示1~3个八进制数字。如:\130 x |
\xdd | dd表示2个十六进制数字。如:\x30 0 |
接下来在代码里解释下它们各自的的意思:
//一些很少能用到的我就不解释了
//%c - 打印字符
int main()
{
//想打印一个单引号,怎么做?
//单引号是个字符,用%c
//这样对吗?
printf("%c\n", ''');
//其实是错的,因为编译器会把前两个
//单引号看成一对里面括了个字符,后面一个就落单了
//所以该怎么改?
//只需要在一个单引号前加上\
//就成了转义字符
printt("%c\n", '\'');
//这样输出结果就是一个'
------------------------------
//打印一个双引号
//其实方法和打印单引号是一个意思
printf("\"");
//输出:"
------------------------------
//两个反斜杠
//大家都知道\0是字符串结束的标志
printf("abcd\0ef\n");
//此时只会打印abcd
//如果想原封不动地打印怎么做?
printf("abcd\\0ef\n");
//在前面加上一个反斜杠再次转义成了普通字符
//这时打印abcd\0ef
//可以简单理解为:\\=输出一个\
------------------------------
printf("\a");
//电脑会蜂鸣一声
//没什么特殊意义
------------------------------
//换行符
printf("abc\ndef\n");
//输出两行:abc
// def
------------------------------
//水平制表符
//和键盘左上角的tab作用相同
printf("abcd\tef");
//输出:abcd ef
//相当于下面这句代码
printf("abcd ef");
return 0;
}
最后两个转义字符是重点,在解释**\ddd和\xdd这两个转义字符之前需要先了解八进制转十进制和十六进制转十进制**:
按权展开式:
八进制130转十进制 = 1*8² + 3*8¹ + 0*8º = 64 + 22 + 0 = 88
365 = 3*8² + 6*8¹ + 5*8º= 192 + 48 +5 = 245
…
十六进制转十进制同上只不过各位的权值是16的整数次幂(基数是16)
这就不一一举例了,具体去看计算机基础
int main()
{
//'\130'是字符,130代表八进制数
printf("%c\n", '\130');
//输出:X
//注意\130是一个字符
return 0;
}
int main()
{
//'\x60'是字符,60代表十六进制数
printf("%c\n", '\x61');
//输出:a
//注意\x60是一个字符
return 0;
}
这两段代码的意思分别是130这个八进制数转成十进制后的数字和60这个十六进制数转成十进制后作为ASCII值代表的那个字符。
键盘上有各种各样的字符,而计算机只能识别二进制的指令,所以有人想出了把每个字符一一编号存进内存中,这种编号就叫ASCII编码。
ASCII表:
要记住0、大写A和小写a的ascii值
注意:这两个转义字符的值不能超出ASCII表的范围(0~127)。
小练习:
int main()
{
printf("%d\n", strlen("qwer t"));
//笔试题
printf("%d\n", strlen("c:\test\628\test.c"));
return 0;
}
分别输出多少?
.
.
.
.
.
运行结果:
第一个大家应该算对了,主要来看看第二道为什么是14呢?
这里就会有人问了,\628不就是一个转义字符\ddd吗,这样理解就错了。
\ddd后面跟的三个数为八进制数,即0~7,是没有8这个数字的,所以\62为一个字符,最终结果就是14了。这种题还是比较坑的,不光要了解转义字符还要细心地去数。
十六进制数是0~15(1,2,3,4,5,6,7,8,9,A,B,C,D,E,F)。
注解解释的意思,使用方法:在代码前面加上//(上面基本上每段代码都有注释)
- 代码中有不需要的代码可以直接删除,也可以注释掉
- 代码中有些代码比较难懂,可以加一下注释文字
介绍一下两种注释形式(c/c++):
#include
int Add(int x, int y)
{
return x+y;
}
/*C语言风格注释
int Sub(int x, int y)
{
return x-y;
}
*/
int main()
{
//C++注释风格
//int a = 10;
//调用Add函数,完成加法
printf("%d\n", Add(1, 2));
return 0;
}
一定要养成注释的好习惯
1.是可以提高程序的可读性
2.让别人可以更好的读懂并理解你的代码
写注释也是帮助自己,帮助别人
人生处处面临着选择,比如:
如果你好好学习,校招时拿一个好offer,走上人生巅峰。
如果你不学习,毕业等于失业,回家卖红薯。
这就是选择!
代码实现:
int main()
{
int input = 0;
//printf("要好好学习吗?\n");
//上面提到过纯文字信息输出的话puts也可以
//并且可以自动换行,不用\n了
puts("要好好学习吗?(1/0):");
//给用户个输入提示:1学习,0不学
scanf("%d", &input);
//输入1,执行if代码块里的语句
if (input == 1)
{
puts("坚持,你会有好offer");
}
//否则就执行else
else
{
puts("放弃,回家卖红薯");
}
return 0;
还有一种选择结构:switch
因为是初始C语言,本篇不再深入介绍
}
注意写法if语句的写法,后面小括号里写表达式,表达式的结果如果为真,就会执行if下面的语句,为假就执行else下的语句。
表达式的结果只有0或1,0代表假,1代表真。
C语言是如何实现循环呢?
举个例子:打印50份资料
int main()
{
//定义资料变量初始化为1
int num = 1;
//只要不满足条件就一直进入循环
while (num <= 50)
{
printf("这是第%d份资料\n", num);
num += 1;
}
//当满足条件跳出循环
return 0;
其余两种循环结构本篇就不介绍了,到后面在专门展开写
}
同时注意while的写法,后面小括号里写表达式,表达式的结果如果为假,就一直进入循环,直到满足表达式后跳出。
注意:一定要有跳出循环的条件,比如上面变量num每次进入循环执行后会+1,循环一定次数后总会满足跳出条件,否者就会陷入死循环。
小总结:
C语言是【结构化】的程序设计语言
所有生活中的事情都可以抽象成是三种或者三种的组合
我们现在接触过的函数有哪几个呢?
main主函数,程序的入口;strlen函数,求字符串长度的函数,这两个都是C语言提供的函数,但只用库里的函数是远远不够的,所以我们就来简单探究下该怎么自定义函数?
不知道大家还记得上面我们写过的代码:求出两个整数的和。现在我们把它再拿过来改装一下:
main函数是程序的入口!
先去找main函数!
//定义函数格式如下
//把两个操作数num1和2传上来,函数要有能力接收
//所以要在小括号里根据
//下面传上来的值的个数和类型来声明几个变量
//因为是两个整形,所以两个整形变量,逗号隔开
int Add(int x,int y)//x是num1的值
{ //y是num2的值
//这里定义个临时变量z
//把x+y的结果赋值给z
int z=x+y;
//把计算出的z的值返回
return z;
//跟主函数的return一样
//都是返回一个整形
//所以要在Add前面加上一个int
//表示返回的类型是整形
}
int main()
{
int num1 = 0;
int num2 = 0;
//输入
scanf("%d %d", &num1, &num2);
//求和
//int sum = num1 + num2;
这次换个方法
//定义一个自定义函数Add
//函数名尽量定义为有意义的标识/单词
//注意写法
int sum = Add(num1,num2);
//Add后面一个小括号
//里面放进去操作数num1和num2
//让add帮我求一下两数的和
//把计算完成返回的结果放到变量sum里
但是现在没有Add函数
需要去main函数上面去定义
//输出
printf("%d\n", sum);
return 0;
}
运行结果:
理解的话模糊请往下看,下图是更细致的描述:
这样大家对函数就有些基本的了解了吧。
只要封装好了函数,就可以反复调用。
比如说还有两个操作数求和该怎么调用?
int t=1; int t2=2; int tet = Add(t,t2);
函数的特点就是简化代码,代码复用。
要存储1-10的数字,怎么存储?
int a=1; int b=2; ...定义十个变量吗?
这样定义的话写起来既麻烦看起来又冗余,那该怎么写?
这时呢,C语言中给了数组的定义:一组相同类型元素的集合,也就是用数组来定义一组相同的元素类型。
用数组来存储1-10的写法:
int main()
{
//数组格式:
//数组类型、数组名、方括号和元素个数
//数组里的元素要用大括号括起并用逗号隔开
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//数组
}
了解了数组的定义后,还有一个很重要的知识点:数组的下标。画图解释:
这些序号,被称之为数组下标,下标是从0开始的。
下标怎样使用,比如说想访问其中的一个元素:
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//想打印9这个元素怎么做?
//只需要知道它的下标就可以访问了
//不难看出它的下标为8;
printf("%d\n", arr[8]); //输出:9
//注意写法数组名后跟着方括号
//里面为元素下标
}
以上是访问一个元素,现在你想访问数组里的所有元素并打印,你会怎么做?
ps:前面学到的while循环是不是就派上用场了,循环+下标。
(建议先想一想再动手敲一敲)
.
.
.
.
.
现在来梳理一下解决步骤:
数组里十个元素,下标为0~9。所以第一步需要一个产生0~9的变量,第二步是循环条件,十个元素要循环十次,最后一步是变量每循环一次就+1,大于十次就跳出。代码实现:
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//变量初始化为0,从0开始
int i = 0;
//只要i<10,就进入循环
while(i<10)
{
//打印当前下标为i的元素
printf("%d\n", arr[i]);
//最后i+1
i = i + 1;
//回到上面继续判断
}
return 0;
}
到这里大家应该就对数组有些初步的认识了。
简单介绍下各类操作符以及如何使用:
操作符这块内容比较多还比较繁琐,需要多下功夫了。
+ - * / %
注意:C语言中的乘号是*,除号是/,和数学有所区别。%叫做取余(取模)
用法:加减乘的使用就不在叙述了,主要是了解除法和取余。请看代码:
int main()
{
int a = 5 / 2;
printf("%d\n", a);
//结果是2.5吗?
return 0;
}
其实不是,结果为2。
5除以2,是商二余一,得到的是商并不会得到余数部分,所以结果为2。除号两端都是整数,执行的叫整数除法,那要是就想得到2.5这个结果该怎么做?
很简单,只要除号两边有一个是小数,执行的就是小数除法。
int main()
{
float a = 5 / 2.0;
//float a = 5.0 / 2;都可以
//小数要用浮点类型
printf("%f\n", a);
//%lf是按照小数打印
//输出默认小数点后面带6个0
//想输出小数点后1位就写成%.1f
//两位就%.2f...以此类推
return 0;
}
这样输出结果就是2.5了。
以上就是除法需要注意的地方,接下来再看个取余运算符的例子:
int main()
{
int a = 5 % 2;
printf("%d\n", a);
//输出多少?
return 0;
}
取余运算符顾名思义,除号得结果是取商,那取余要的就是余数了。所以上面的例子的结果就是1了,5除以2,是商二余一,取的是余数1。
>> <<
先看例子:
int main()
{
int a = 2;
int b = a << 1;
printf("%d\n", b);
//左右移是按照其二进制序列来进行运算的
return 0;
}
ps:如果进制还有不懂同学的去学一下计算机基础。
所以得出的结果为4;这是左移,那么右移的道理也是一样的。只要记住移动的是二进制序列就可以了。
& ^ |
int main()
{
int a = 3;
int b = 5;
//按(二进制)位与
int c = a & b;
printf("%d\n", c);
return 0;
}
按位与:有0则0,全1为1
int main()
{
int a = 3;
int b = 5;
//按(二进制)位或
int c = a | b;
printf("%d\n", c);
return 0;
}
同样先写出a和b的二进制序列再计算:
(上图字打错了是按位或)
按位或:有1则1,全0为0
int main()
{
int a = 3;
int b = 5;
//按(二进制)位异或
int c = a ^ b;
printf("%d\n", c);
return 0;
}
按位异或:同0异1
注:操作数必须为整数。
= += -= *= /= &= ^= |= >>= <<=
赋值操作符比较简单直接代码解释:
int main()
{
//=就是赋值
int a = 2;
a = a + 5;
//也可以写成
a += 5;
//这就叫做复合赋值
a = a - 3;
a -= 3;
a = a / 4;
a /= 3;
a = a % 5;
a %= 5;
return 0;
}
解释:只有一个操作数
! | 逻辑反操作符 |
---|---|
+ | 正值 |
- | 负值 |
& | 取地址 |
sizeof | 操作数的类型长度(字节为单位) |
~ | 对一个数的二进制按位取反 |
- - | 前置、后置- - |
++ | 前置、后置++ |
* | 间接访问操作符(解引用操作符) |
(类型) | 强制类型转换 |
举例说明:
(&取地址和*解引用最后讲)
int main()
{
!逻辑反操作符
//在C语言中0表示假,非0表示真
int a = 1;
//!的作用就是把假变成真
//把真变成假
printf("%d\n", !a); //0
//这是语法上的解释
//具体如何使用
if(a)
{
//如果a为真执行这块代码
}
if(!a)
{
//如果a为假执行这块代码
}
-----------------------------
正值/负值
int a = 5;
a = -a;//-5
a = +a;//5
-----------------------------
sizeof在数据类型介绍了
//不是函数
//它是一个操作符,用来计算类型和变量的大小
int a = 10;
printf("%d\n", sizeof(a));//4
printf("%d\n", sizeof(int));//4
//用来计算数组大小可行吗
//初始化10个元素0
int arr[10] = { 0 };
printf("%d\n", sizeof(arr));//10*4
//一个整形元素大小是4字节,十个40
printf("%d\n",sizeof(arr[0]));//4
//那我们就可以求数组的元素个数了
int num = sizeof(arr) / sizeof(arr[0]);
//整个数组大小除以数组第一个元素大小
//得到的就是数组元素个数
printf("%d\n",num);
return 0;
}
int main()
{
~按位取反
int a=0;
printf("%d\n",~a);
//打印的是原码
//而在内存中存储的是补码
}
这个操作符涉及到了原码反码补码的知识,来画图解释一下:
那知道了补码是不是就可以推出原码是多少了?
补码减一得反码,符号位不变其余按位取反得原码,所以得出~a是-1
如果还是很模糊的小伙伴,在计算机基础里对于原码反码补码有比较全面的解释,可以去看看。
接下里是前后置++、–是比较重要也用的比较多:
int main()
{
int a = 10;
int b = ++a;
//前置++的计算规则 - 是先++,在使用
printf("%d\n", b);//11
//先++,10+1的值在赋给b
printf("%d\n", a);//11
//++a之后,a自增为11
//所以最后打印出两个11
int a = 10;
int b = a++;
//后置++的计算规则 - 是先使用,在++
printf("%d\n", b);//10
//这里a的值先赋给b,b为10
printf("%d\n", a);//11
//赋给b后,a++,a自增为11
return 0;
}
谨记:前置和后置的使用规则,前置先++或–,再使用。后置先使用,再++或–
强制类型转换,先看下面这段代码:
int main()
{
int a = 3.14;
printf("%d\n", a);
return 0;
}
此时编译此代码会报警告:
因为类型不匹配,那怎么能避免警告呢?
强制类型转换,3.14是个double类型,而a是int类型,所以只需要在3.14前面加上(int)。
int main()
{
int a = (int)3.14;
//注意写法
printf("%d\n", a);
//此时便不会在报警告,打印3
return 0;
}
> | 大于 |
---|---|
>= | 大于等于 |
< | 小于 |
<= | 小于等于 |
== | 等于 用于测试相等 |
!= | 不等于 用于测试不相等 |
这些操作符只需要注意写法,==是等于,和数学有些不同,注意区分。
int main()
{
//1代表张三和李四去了
int a = 1;
int b = 1;
//表达式为1代表真,0代表假
//&&必须要两边的表达式都为真,否则为假
int c = a && b;
printf("%d\n", c);
//此时c的值为1
//只要a和b有一个为0则表达式为0
return 0;
}
int main()
{
int a = 1;
int b = 0;
//||两边表达式有一个为真即为真
//全0才为假
int c = a || b;
printf("%d\n", c);
//此时c的值为1
return 0;
}
注意和按位与和按位或区分。
写法:表达式1 ?表达式2 :表达式3
意思是:表达式1为真,表达式2计算,整个表达式的结果是表达式2.
表达式1为假,表达式3计算,整个表达式的结果是表达式3.
举个例子,求出两个数的最大值:
int main()
{
int a = 5;
int b = 3;
int max = 0;
//如果没了解到三目操作符
//大家可能会这么写
if (a > b)
{
max = a;
}
else
{
max = b;
}
//利用三目操作符求
max = a > b ? a : b;
printf("%d\n", max);//5
return 0;
}
含义:用逗号隔开的一串表达式
注意:逗号表达式的特点是,从左到右依次计算,整个表达式的结果是最后一个表达式的结果!
简单举个例子:
int main()
{
//这就是一串逗号表达式
//只是没什么影响
(2, 4 + 5, 9);
//看看下面这段
int a = 0;
int b = 3;
int c = 5;
int d = (a = b + 2, c = a - 4, b = c + 2);
printf("d = %d\n", d);
//d是多少?
//先算第一个表达式:
//a=3+2=5
//第二个
//c=5-4=1
//最后一个
//d=1+2=3
//输出:d = 3
return 0;
}
下标引用操作符:
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", arr[0]);
//访问数组元素就要用到下标引用操作符
return 0;
}
int main()
{
//调用函数的时候
//函数后面的()就是函数调用操作符
//函数调用操作符可以传一个或多个参数
printf("能看到这的应该都是王者了吧\n");
printf("%d\n", 100);
return 0;
}
到这里一共还有四个操作符没讲到:& * . ->,具体后面会讲到。
大家有没有注意到我们写代码时用到的int、char、const、if、while、return等等这些我们能直接拿来用的,这就是C语言提供的关键字。
C语言提供了丰富的关键字,这些关键字都是语言本身预先设定好的,用户自己是不能创造关键字的,也不能把关键字做变量名。
常见关键字如上:
简单介绍下在前面的学习中没见的关键字。
auto:
int main()
{
{
//auto是用来修饰局部变量
//auto int a = 10;
int a = 10;
//局部变量都是自动创建,自动销毁
//所以也叫作自动变量
//通常auto都是省略掉的
}
return 0;
}
swtich、break、case、continue和default(后面分支和循环语句会详细介绍)。
exteren:用来声明外部符号的。
register:寄存器关键字
int main()
{
register int num = 100;
//建议把num的的值放在寄存器中
//只是建议,最终决定放不放是编译器说了算
return 0;
}
那为什么要放到寄存器里面去?来给大家解释一下:计算机的数据可以存放到网盘、硬盘、内存、高速缓存和寄存器上(CPU访问速度从低到高),寄存器的速度是最快也是效率最高,所以大量/频繁被使用到的数据,想放到寄存器里面来提升效率(现在即使不写,编译器也会根据需要自动放到寄存器里)。
static:静态的
//这是把前面的知识串起来
//还是先去找main函数
///定义test函数
void test()
{
int a = 1;
a++;
printf("%d ", a);
}
int main()
{
int i = 0;
while (i < 10)
{
//调用test函数
test();
i++;
}
return 0;
//这段代码最后输出是?
}
此时改一下函数:
void test()
{
//在前面加上static
static int a = 1;
a++;
printf("%d ", a);
//现在这段代码最后输出是?
return 0;
}
因此我们可以总结出:
static修饰局部变量,改变了局部变量的生命周期(本质上是改变了变量的存储类型)。
如果我们希望一个变量出了它的范围不销毁,下次继续使用,这时就应该使用static来修饰它。
大家都知道全局变量的作用域是整个工程:
程序正常运行,接下来用static来修饰一下全局变量会怎样:
程序会报错,static修饰全局变量,使得这个全局变量只能在自己的源文件.内部可以使用,其他源文件不能使用!
原因是:
全局变量,在其他源文件内部可以被使用,是因为全局变量具有外部链接属性,但是被static修饰之后,就变成了内部链接属性,其他源文件就不能链接到这个静态的全局变量了!
注意函数声明:只需要声明函数返回类型、函数名和函数参数
当用static修饰函数时,程序会报和修饰全局变量相同的错误。
原因是:
static修饰函数,使得函数只能在自己所在的源文件内部使用,不能在其他源文件内部使用。
本质上: static是将函数的外部链接属性变成了内部链接属性!(和static修饰全局变量一样!)
struct:结构体typedef:类型定义(类型重定义)
//typedef类型重命名
typedef unsigned int u_int;
int main()
{
//定义无符号整形
//这样写太长了不方便
//那能不能把这个类型简化一下
unsigned int a = 100;
这时就提出了在主函数外把类型重新定义
//typedef unsigned int u_int;
//作用是把unsigned int重新取了个名字
//叫做u_int
u_int b = 100;
//这条代码上面是等价的
//这里的u_int就是unsigned int的别名
return 0;
}
signed(有符号的):10、-20,数字有正有负,也就是描述的数字是有符号位的。与unsigned(无符号的)相对反。
union:联合体(共用体)
void:无(空)类型
volatile:体现你段位的一个关键字,暂时不讲,别问我为什么不讲,因为老师没讲我也不会(doge)
注意这两个不是关键字,而是预处理指令:
define
include
define是个预处理指令
#define MAX 100
int main()
{
printf("%d\n",MAX)//100
//具体在常量那个章节讲过
//这里不在详细介绍了
return 0;
}
//宏名Add,参数X,Y 把两个参数加起来
#define Add(X,Y) X+Y
int main()
{
printf("%d\n", Add(2,3));
//输出5
//那么先看一下下面这句
printf("%d\n", 4*Add(2,3));
//是20还是11?
return 0;
}
答案为11,因为编译器执行的是:4*2+3。所以我们不应该把参数X和Y当成一个普通的变量,它有可能是表达式。
#define Add(X,Y) ((X)+(Y))
因此把参数分别括起来,再把整个宏体括起来,这样的结果就是20。
指针 - C语言的灵魂!非常重要的一块内容也比较难,但是本篇只是初始指针
在学习指针之前就必须要先了解内存:
内存是电脑上特别重要的存储器,计算机中所有程序的运行都是在内存中进行的。所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。
这里就出现了一个重要的问题:
内存是怎么编号的?
我们经常会谈到说是32位机器或64位机器,这里的32位和64位是指32或者64根(物理的)地址线,通电后正电是1负电是0,所有地址线通电后会产生不同的电信号,把电信号转换成数字信号会组成由0和1组成的二进制序列,32位机器所有的二进制序列有2³²个,这些二进制序列就可以作为内存编号,当这个二进制序列成为一个内存的编号的时候,我们把这个编号称为这个内存单元的地址。
只有了解了内存知识,才能更好的认识和学习指针概念。
上代码解释:
int main()
{
int a = 10;
//a是整形要在内存分配4个字节的空间
}
需要用到&取地址操作符,取出该数据在内存中的地址,单目操作符。
当拿到地址之后想把地址打印出来做怎么做?
int main()
{
int a = 10;
//%p是打印地址的
printf("%p\n",&a);//注意要带&
//00EFFB78
return 0;
}
&a虽然是地址但也是个值,把这个地址值存起来也需要一块空间:
在C语言中,把存放地址的变量就叫做指针变量。
int main()
{
int a = 10;
//pa是用来存放地址的
//就叫做指针变量
int* pa = &a;//取出a的地址
//* 说明pa是指针变量
//int 说明pa指向的对象是int类型
> 注意指针变量写法:指针类型*。
return 0;
}
小练习:定义一个char类型的指针
int main()
{
char a = 'W';
char* pa = &a;
return 0;
}
那么定义指针变量就仅仅是为了把某个数据的地址存起来吗?
并不是!
还是拿上面那段代码:
int main()
{
int a = 10;
//我们希望能够通过pa的存的值来找到a
int* pa = &a;
//那么该怎么找a呢?
//注意写法!
//因为pa里面存的是a的地址
//*pa就是通过pa里面的地址找到a
*pa = 20;
//这颗星*叫做解引用操作
//也可以说*pa = a;
printf("%d\n",a);
return 0;
}
*解引用操作符也是单目操作符
指针 就是 地址,pa叫指针变量,里面存的是a的地址,所以使用pa这个变量就是使用a的地址,它俩是一回事。
指针变量的大小:
int main()
{
//用sizeof计算指针变量大小
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*));
return 0;
}
运行结果:
为什么指针大小都是相同的呢?
其实不难理解:指针是存放地址的,指针需要多大空间取决于地址的存储需要多大空间,地址的存储在32位机器需要32个比特位也就是4个字节,而64位则是64个比特位也就是8个字节。不管什么类型的地址在内存都是二进制序列,既然都是二进制序列那就只根据32还是64位平台来决定指针大小了。
**结论:**指针大小在32位平台是4个字节,64位平台是8个字节。
在总结一下指针:指针变量是用来存放地址的,指针有类型,通过解引用操作符找到它所指向的对象并操作该对象,这就是指针最基本最核心的点。只要理解了,后面对于指针所有的操作都是在这个基础上变化的。
> 结构体是C语言中特别重要的知识点,结构体使得C语言有能力描述复杂类型。
比如描述学生,学生包含: 名字+年龄+分数 这几项信息。
这里只能使用结构体来描述了。
例如:
//创建一个学生的类型
struct Stu
{
//成员变量
char name[20];//名字
int age; //年龄
double score;//分数
};
//创建一个书的类型
struct Book
{
//成员变量
char name[20];//书名
float price;//价格
char id[20];//书号
};
int main()
{
//我们类型创建出来了怎么用?
//创建一个结构体变量 学生s
//一个学生是由几个数据组成的
//这些数据用大括号括起来
struct Stu s = {"张三",20,95.5};
//结构体的创建和初始化
//当拿到这些数据的时候想打印出来
//怎么做
printf("姓名:%s 年龄:%d 分数:%lf\n", s.name, s.age, s.score);
//结构体变量.成员变量
//这里用到了一个 . 操作符
//找到结构体成员并访问它用的是.
return 0;
}
. 操作符,找到结构体成员并访问它
这就是结构体的基本访问。
下面来简单介绍一下用结构体指针访问,还是拿上面的例子:
>利用指针访问成员变量并打印
struct Stu
{
//成员变量
char name[20];//名字
int age; //年龄
double score;//分数
};
int main()
{
struct Stu s = {"张三",20,95.5};
//结构体变量也是变量
//是变量就可以取出它的地址
//利用指针对这个它进行操作
//结构体指针
struct Stu* ps = &s;
printf("姓名:%s 年龄:%d 分数:%lf\n", (*ps).name, (*ps).age, (*ps).score);
//ps指向s的地址,*解引用一下就是s
//和第一种打印就没有区别
return 0;
}
打印结果:
把一个结构体地址交给指针的时候也可以打印出来相应的内容。
但其实这种方法是比较费劲的,下面还有一种更优解的方法,现在还剩下一个操作符->,利用它来实现:
struct Stu
{
//成员变量
char name[20];//名字
int age; //年龄
double score;//分数
};
int main()
{
struct Stu s = {"张三",20,95.5};
struct Stu* ps = &s;
//ps既然是指针
//它指向对象的名字
printf("姓名:%s 年龄:%d 分数:%lf\n", ps->name, ps->age, ps->score);
//这种写法就等价于上面例子中的写法
//相比之下这种比较直观
return 0;
}
通过ps先找到它的对象,然后在->找到它里面的成员变量。
运行结果:
->操作符:结构体指针->成员变量名。
以上就是所有初始C语言的内容,本文仅仅简单介绍了C语言的整体框架和语法内容。对于某些代码还是要自己动手敲敲加深印象,尽量做到举一反三。本篇主要针对刚入门C语言的新手,希望初学的伙伴们看完本篇内容能对C语言有个整体而全面的认识和了解,而对于熟悉的C的小伙伴也可以重刷一遍巩固基础。
四天爆肝三万多字,码字不易,看到这的小伙伴记得给个一键三连哦,我们下期再见~
ps:由于本人是博客新手,文章有误排版不美观在所难免,我也很想知道错误,希望小伙伴们发现错误能在评论区指出,日后加倍努力争取写出高质量博客!