小韩是一个学习比较刻苦认真的学生,虽然老师上课进度刚讲到输入输出,但是小韩已经自学到C语言指针部分的内容了。但是进度太快的弊端就是有些东西很难消化吸收,这不就遇到了问题,来请教小刘:“学长,你说这个指针到底是地址还是变量啊,我怎么越看头越晕呢?”小刘笑道:“指针的全名叫指针变量,从表面来看他其实就是个变量,但是从深层来看因为它存储的数据有些特殊,是内存中的地址,所以我们有时才会说‘指针就是地址’,逻辑上讲指针变量存储的是地址变量,那不谈变量指针不就是地址了吗?”小韩恍然大悟:“原来是这个意思啊!”。
小刘又说:“其实很多人学C学到最后搞不懂指针是什么,地址是什么,其实是对内存的理解不到位。当你明白了数据是怎么在内存中存放的,CPU又是通过什么在内存中获取数据的时候(其实就是通过数据的地址),你就大概明白为什么编程入门要学C,学C是能学到很多计算机底层的东西的。”
小刘感慨道:“现在市面上很多教C语言的教程都把指针和内存放到最后面讲,无疑给人带来一种很难、不好学的心里压力,我认为没有必要,C语言很多地方都涉及到内存、地址的知识,我觉得在有一些基础后循序渐进的引出内存和地址才是正确的做法,而不是把它积压到最后,一股脑的灌输给学生。你想,本来前面学的还蛮轻松愉快的,到最后“当头一棒”这谁能不懵?”
“所以你们不用担心,我会以最简单的通俗易懂的方式循序渐进的给你们讲C语言,不用担心哪里会学不懂,其实只要找对了方法学什么都是轻松愉快的。接下来我会先给你们讲讲C语言的变量和数据类型然后在由此引申出一些内存的知识。”
小刘又来到了熟悉的教室,说:“同学们,C语言基本的输入输出我们已经学过了,今天来学习新的内容——变量和数据类型。其实,变量和数据类型我们早就接触过了,只不过没有给大家做正式的介绍。我们来这段代码:[code_1]”
#include<stdio.h>
int main()
{int a,b,c;a = 1;b = 1;c = a+b;printf("%d",c);return 0;
}
输出
“这段代码非常简单,有哪位同学能口头描述一下这个代码的过程?”
小韩跃跃欲试:“我来!”
“好,那你就来给大家分析分析。”
“首先我们定义了三个整数,然后给a,b赋值为1,c赋值为a+b,最后输出c的结果为2。”
“非常好,就是这个意思。那么其实你们已经知道了,我们定义的a,b,c都是整数,而在C语言中整数就是一种数据类型。数据类型其实就是一组具有相同属性的数据的集合。比如说1,2,3……这些都是属于整数类型的,在C语言中定义整数类型的值用 int 表示。那么加上小数点1.0,2.0……9.8,10.999后这些表示的就都是小数,在C语言中我们更习惯称为浮点数。而浮点数又分为单精度浮点数和双精度浮点数。二者的区别从名字就可以看出来,后者比前者的精确度更高,表示的范围更大,所以占的内存空间也就更大。单精度浮点型定义用 float ,双精度浮点型定义用 double 。示例代码:[code_2]”
#include<stdio.h>
int main()
{float a;double b;a = 1.123;b = 1.123;printf("%f\n",a);printf("%lf\n",b);return 0;
}
输出
“从这个输出结果我们能看出,浮点型的数通常会默认保留小数点的后六位。且输出时float的格式符是%f,double的格式符为%lf。意义就是 %lf 比 %f 表示的范围要更大。”
“除了整数和浮点数外,还有一个最常见的数据类型就是char。这个数据类型在我们前面将字符和字符串的输入输出的时候也是经常用到。如果想表示单个字符就定义一个字符变量,如果想表示一个字符串就定义一个字符数组。这个就不做过多介绍了。[code_3]”
#include<stdio.h>
int main()
{char a = 'a';char array[20] = "Hello World!";printf("%c\n",a);printf("%s\n",array);return 0;
}
输出
我们常用到的除了int、char、float、double以外,还有void(空类型)也是我们常用的,但是我们通常不会用它来定义变量,而是定义一个没有返回值的函数的时候才会用到。
“这些就是我们常用的五个基本数据类型。下面说说bool(布尔)类型和wchar_t(宽字符)类型。”
“bool类型非常简单,因为这个类型的变量只有两个值,一个是true(真),一个是false(假)。通常,我们会在做一些逻辑判断的时候用到。[code_4]”
#include<stdio.h>
int main()
{bool a;if(a){printf("ture\n");}else{printf("false\n");}return 0;
}
输出
“默认情况下,布尔变量值为false,也可以赋值为true。[code_5]”
#include<stdio.h>
int main()
{bool a;a=true;if(a){printf("ture\n");}else{printf("false\n");}return 0;
}
输出
“在C语言中也可以用0,1表示false和true。例如:[code_6]”
#include<stdio.h>
int main()
{bool a;a = 1;if(a){printf("ture\n");}else{printf("false\n");}return 0;
}
输出
“宽字符类型顾名思义就是比char类型的字符所占的内存空间更大,我们用代码更好解释一下它和char的区别。[code_7]”
#include<stdio.h>int main()
{char c='c';wchar_t wc='w';printf("char c = %c,byte:%d\n",c,sizeof(c));printf("char wc = %c,byte:%d\n",wc,sizeof(wc));char cc[20]="Hello World!";wchar_t wcc[20]=L"Hello World!";printf("cc=%s\n",cc);printf("wcc=%ls\n",wcc);return 0;
}
输出
“由上面的代码和输出可以看出,char和wchar_t在处理单个字符时,定义与赋值和输出都是一样的,但不同的是用wchar_t定义的字符占了两个字节单位大小,char定义的字符占了一个字节单位大小。在处理字符串时,两种数据类型展现了明显的区别,wchar_t类型的字符串在定义时要在字符串前面加’L’,告知编译器,我的类型是‘宽字型’,否则编译器会把字符串当单字符处理只能输出首个字符。而在输出字符串时,宽字符型的格式要用’%ls’而不是‘%s’,这点也要和窄字符char类型区分开。”
“C语言中除了有以上这些基本数据类型外,还有一些类型的修饰符,在基本数据类型前加上这些修饰符能改变类型的空间大小、符号或取值范围:”
官方文档
“这些类型的大小我们可以通过指令sizeof()得到:[code_8]”
#include<stdio.h>
int main()
{short s;long l;int i;char c;float f;double d;printf("short 长度: %d byte\n",sizeof(s));printf("long 长度: %d byte\n",sizeof(l));printf("int 长度: %d byte\n",sizeof(i));printf("char 长度: %d byte\n",sizeof(c));printf("float 长度: %d byte\n",sizeof(f));printf("double 长度: %d byte\n",sizeof(d));return 0;}
输出
“对signed、unsigned、short、long的一些说明:在int类型前面加signed修饰符表示数据是带符号的可以有正有负(默认情况下数据都是带符号的)。在int类型前面加unsigned修饰符表示数据是无符号的只能表示正数。加short表示缩短字节长度,加long则是增大字节长度。”
“那么我想给大家提个小问题,在用unsigned做修饰符时,只能表示正数,那么原来的负数去哪了呢?在内存中表示负数的那些位数难道是浪费掉了吗?”
“当然不是,在用unsigned修饰整数型变量时,原本内存中表示负数的位数全都用来表示正数了,也就是说,当我们用unsigned修饰变量时,数据的最大值会提升一倍,而最小值变成了0,就是因为负数已经不存在了,简单来说就是牺牲负数表示正数,正数最大值扩大一倍。例如:[code_9]”
#include<stdio.h>
#include<stdlib.h>
int main()
{int a = INT_MAX; unsigned int a1 = 2*22147483647;printf("INT_MAX=%d\n",a);printf("UNSIGNED INT_MAX=%d\n",a1);return 0;
}
输出
“注意:上述在使用INT_MAX查看int最大值时,必须调用头文件stdlib.h。”
int类型最大值的计算方法:
其他的数据类型大小范围请参考:基本数据类型大小和范围
“前面我们讲了很多数据类型的知识,下面该讲讲变量了。什么是变量呢?[code_10]”
#include<stdio.h>
int main()
{int a;a = 10;printf("%d\n",a); return 0;}
输出
“如上述代码中的a,就是一个变量。而int是用来定义a的数据类型。a=10是对a的赋值操作,printf(“%d\n”,a)则是对a的调用。从代码角度看,一个变量其实就这些动作:定义、赋值、调用。(有时赋值和调用也可能没有。)我们在深入点理解,其实定义就是在内存空间中开辟出一块空间大小用来存储变量,赋值就是在这块新开辟的空间中存入一个数据值,而调用就是把这块空间的数据拿到别的地方进行处理(运算或者赋值)。”
“说的再简单点,此时我们想像内存就是一个大仓库,变量就是在这个仓库中存放的一个箱子。这个箱子的颜色、大小都由定义箱子的数据类型决定。而箱子里放不放东西,由是否对变量进行赋值决定。”
如图,变量-(具象)->箱子:
“上面这个图我们就可以理解为,在内存中开辟了一块int类型大小的空间(4byte),并给这个空间取名字为“a”,a里面的值是‘123’。代码表示:[code_11]”
#include<stdio.h>
int main()
{int a;a = 123;return 0;}
“那么据此,其他的数据类型的变量也都同理。假设占用空间的大小作为箱子的尺寸,那么变量在内存的定义可以用下图表示:”
“把不同数据类型的变量比作不同颜色、尺寸的箱子,把内存比作是存放箱子的仓库,我认为这是目前最通俗易懂的理解方法。但这样我就有个问题了,这些箱子在仓库中是随便放的,还是按要求摆在不同区域呢?大家可以思考一下这个问题,答案会在下次课中揭晓。”