1. 数据类型:
在这个部分,我们只需要记住我们常用的数据类型的大小以及范围就够了,等期末复习篇更新完我会出一篇更加详细的数据存储的文章
1.整形家族:
字符型,短整型,长整型,整型,他们又分为有符号和无符号,一共排列组合八种。
1.1注意:在C语言中并没有char类型的常量,其实是用int表示char,字符型与整型相比只是更加节省内存。(在所有数据类型中,char类型占用的内存空间最少)
在我们主观的理解中,长整型听上去应该比短整型所能表示的值都要大,这是错误的。长整型和短整型规定的只是他们的长度大小,并没有规定他们的数值大小,比如说我们的 long long int a=1;int b=2;这个位置a是小于b的,但是我们的a所占内存的长度是要大于b的。
1.2.1缺省
缺省类型一般是int,缺省就是默认的意思。比如a=10;那么就默认是int a=10;也并不一定非要是int。
1.2.2新问题
那么这个缺省的int是short int 还是long int?
通常选择的缺省值是这种机器最为高效的位数(计算最快的方式)
补充(可以不看):
1.其实也并没有规定int ,long int ,short int必须不一样,如果某种机器的字长是32位,而且没有什么有效的指令处理更有效地处理更短的整型值,那么就可以把这三个都设置为32位
2.优秀的程序一般都有良好的可移植性,其实就是我们这里的char。我们必须保证我们的字符集中的字符都是位于可表示范围内,一般是将char变量的值限制在singed char和unsigned char的交集内。
字面值也可以叫做常量
常量可以联想到不可以改变,所以字面值是不可以改变的,其实字面值也分非常多种,有字符串型(c++),整型…但是我们期末考试知道字面值不可以改变就可以了
2.浮点型
float能保证6位,double能保证15位。但是默认float和double都只能显示6位。如果需要更多位的输出可以这样:
浮点型的缺省类型是double
(1/2)因为可能当成0,和1.0/2算出来不一样,1/2永远是0,1.0/2才是0.5。
3.指针类型:
指针就是地址,比如说你的名字是小明,你的家是212寝室,那么根据212就可以找到小明,这里的212就是指针
3.1字符串常量:
说到这个位置就不得不说一个叫字符串常量的东西,什么是字符串常量呢?
首先声名,c语言中是没有字符串类型的,但是c语言提供了字符串常量。
什么是字符串常量?
字符串常量是一串以NUL字节结尾的 零个或者多个 字符。
注意:我们这里说的是以NUL结尾,所以那么我们就可以知道在字符串常量中间是不可以出现NUL的。
举个例子:
这里为了方便输出我是用c++,我们说的是NUL(0)字节,并不是数字0,我们的数字0对应的ascii码是48。
字符串常量的大小
这一篇文章讲的非常详细各种坑都给避开了:点击传送门进行传送
回到文章,为什么我们的字符串常量要在这指针这一节将?
从上面字符串常量的赋值也可以看出是赋值给一个char类型的指针的。
因为编译器将字符串第一个字符的地址 赋值给了p1,假如这个位置"hello",就是将h的地址赋值给了p1。
2.基本的声明
1.声明的基本形式
说明符(一个或者多个) 声明表达式列表
说明符包含了一些关键字,用于描述被声明标识符的基本类型。说明符也可以用于改变标志符的缺省存储类型和作用域,这些都将会讲到
普通声明:
int i;
char j,k,l;
加了其他关键字的声明或者省略关键字
short int i;
long int j;
unsigned short k;
long long q;
上诉情况都是没有问题的,我们看看下面这种情况:
a=10;
这个情况就是错误的了,但是不是上面说的int可以省略吗?
答案:在变量声明的时候我们 至少有一个其他的说明符那么关键字int才可以被省略
浮点型不一样
浮点型不能使用short ,signed,unsigned
为什么?
1.不用short是因为精度会丢失。
2.浮点数是不能用 unsigned来规范的。unsigned 的意思就是把内存中的数据第一位也用来表示数据,而不用于表示符号位。而浮点数规定内存中数据的第一位必须是符号位。因此两者之间是互相矛盾的,这也就是为什么浮点数不会有unsigned类型。
3.初始化
1.数组的声明注意事项:
初始化数组的时候如果后面显示了各位,那么数组元素的个数可以省略。
例子:int a[ ]={1,2,3,5,7,8};
2指针的声明
注意一点:
int *p=&a;
这里的a只能是int型的变量,不能是float型的变量。
举个错误例子:
可以看出char 型的指针不能指向int的变量!
说到这里就顺便把typedef以及define的区别说了。
############################开始####################################
int *a,b,c;
int* a,b,c;
两句话有什么区别?
没有区别,唯一的区别就是*这个符号离a的距离近还是int这个符号近。但是我们通常使用第一种方式。因为第二种情况容易让初学者理解为定义了三个a,b,c三个int *类型的变量,其实是定义了一个int *变量和两个int变量。
如果我们要定义两个指针变量就必须这样 int *a,*b;
这个地方我们就可以引出typedef以及define的区别:
语法
define :#define 宏名 字符串
typedef : typedef 自己定义的名字 类型;
1.define语句后面不能够加;但是typedef语句结束后,后面有这个符号
2.define进行的是简单的替换,而我们的typedef进行的是一个类型的定义,可以理解为取别名。什么是简单的替换什么是取别名呢
define使用的实例
#define sign1 int*
sgin1 a,b;
typedef使用实例
typedef sing2 int*;
sign2 a,b;
那么就要说说什么是简单的替换了,这个位置看似一样的语句其实本质上并不一样
define例子中等价于 int * a ; int b;
而第二个例子等价于 int * a ; int * b ;
所以define进行的只是简单的替换而typedef的作用是取别名。
##############################结束####################################
2 字符串常量的声明:
上面说过:
所以char *p=“love”,是将l的地址赋值给p这个指针。
隐式声明:单纯的声明变量中不建议使用,容易错误,会警告,旧的编译器不会报错,但是隐式声明在c++中还是有用处
字面意思
a[10]编译器默认为int a[10]
a=10,被认为是int a=10;
现在编译器一般都会报错,不过上面提到过一点
当至少有其他一个关键字在的时候可以省去int。
4.常量
字面意思,不能修改
建议看我的这篇文章:传送门
可以看看,个人觉得写得比较详细。
5.作用域
当一个变量在程序的某个部分被声明的时候,它只有在程序的一定区域才能被访问到。
可以分为四种不同类型的作用域——文件作用域,函数作用域,代码块作用域,原型作用域。
标识符声明的位置决定了它的作用域。
1.代码块作用域;
看一幅图,包括了所有情况
代码块的作用域是比较简单的
注意点1:
我们10处的i变量可能和第9处的某个变量可能处于同一个位置,因为他们的作用域并没有重叠之处,所以两处的代码在时间上没办法同时存在,也就是说,他们可能有在不同的时间内占用同一块物理内存,这并不冲突,应为当运行完9那个模块的时候,9中的临时变量一定会被释放,空出来的区域可能被10中的变量占用。
注意点2:
int a;
int fun( int q)
{
int a;
}
如果代码块内部定义了与代码块外部相同名字的变量,那么我们的编译器就会隐藏我们的外部那个变量,以内部变量为主,近水楼台先得月。这种情况当然能够理解。
更加特殊的情况来了
int fun( int a)//第一处
{
int a;//第二处
}
这种情况怎么办?
还是会以第二处的a为主,当然这么写是错误的
这里发生了“重定义”的错误。
2.文件作用域:
在所有代码块之外声明的标识符都具有文件作用域 ,他表示这些标识符从他们的声明之处到他所在源文件的结尾处都是可以访问它的,但是同名的时候还是会被隐藏那个全局变量。
在文件中定义的函数名也具有文件作用域(如同声明4),因为函数名本身不属于任何代码块,如果我们将这个当作头文件通过#include指令包括到其他文件中,就像他们直接卸载那些文件中一样,他们的作用域并不局限于头文件的文件尾部
3.原型作用域
只适用于函数原型中声明的参数名
int fun(int i);
这里的i就是原型作用域
4.函数作用域
- 调用函数时创建函数作用域,函数执行完毕后,函数作用域销毁
- 每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的
- 在函数作用域中可以访问到全局的作用域的变量,反之不可。当在函数作用域中操作一个变量时,它会先在自身作用域中寻找关键字,如果有就直接使用,如果没有,就向上一级作用域中寻找,直到找到全局作用域如果全局作用域没有,则会报错。从内部向外面一步一步
问题1. 先看了结论,我们再看看为什么?
c语言中函数的本质就是一段代码,但是给这段代码起了一个名字,这个名字就是他的的这段代码的开始地址 这也是函数名的本质,并且每一次调用我们都是将函数压入栈中,然后再出栈,函数调用完成后,函数里的所有局部变量将会被释放。所以我们函数的作用域就只是函数调用时产生。
(补充)问题2. 但是又为什么会返回局部变量却没有问题?局部变量不是已经被释放了吗?
局部变量作为返回值时,一般是系统先申请一个临时对象存储局部变量,也就是函数返回值的复制品,用来展示存储局部变量。但是你要是返回局部变量的地址就出现问题了!!!因为已经被释放了,返回的是一个不知道里面是什么的
6.链接属性(选看)
第一次接触到这个词语的时候是在上一篇文章,链接器,那么这个就肯定会和链接器有点联系
1.什么是链接属性?
当组成一个程序的各个源文件分别被编译后,所有的目标文件以及那些从一个或多个函数库中引用的函数链接在一起,形成可执行程序.
标识符的链接属性决定如何处理在不同文件中出现的标识符.标识符作用域与它的链接属性有关,但这两个属性并不相同.
2.链接属性分为三种:
(1)external(外部):external链接属性的标识符不管声明几次,存在于多少个源文件,都是属于一个个体.
(2)internal(内部):internal链接属性的标识符在同一个源文件内所有声明都是同一个个体,但在其他源文件中多个声明分属不同个体
(3)none(无):没有链接属性的标识符(none)总是当成单独个体,也就是多次声明被当做多个不同的实体.
3.缺省情况下,函数和代码块之外的标识符链接属性为external,代码块之内的标识符链接属性为none.下面的程序骨架所示.
1 int a;
2 int b(int c)
3 {
4 int e;
5 int f(int g);
6 }
在缺省情况下,a,b,f为external链接属性.其余标识符为none链接属性.
所以如果当其他源文件声明了标识符a,实际上是访问的这个源文件中所定义的实体.f的链接属性之所以是external,是因为他是函数名,调用f函数实际上将链接到其他源文件所定义的函数,甚至这个函数的定义 是在某个函数库中.
4.用于在声明中修改标识符链接属性的关键字:extern和static
static:
(1)对于缺省情况下为external链接属性的声明,在声明前加static,会使这个标识符变成这个源文件私有,这样可以防止它被其他源文件调用.
(2)对于缺省情况下为none链接属性的声明,在声明前加static,不会改变这个标识符的链接属性,也不会改变它的作用域,但会改变它的存储类型(生命周期),让它在程序执行的整个过程中一直存在.
extern:
(1)在声明前加extern,会为这个标识符指定external链接属性.这样就可以在其他源文件声明的外部定义这个实体.比如下面k声明为extern时,函数就可以访问其他源文件声明的外部变量了.
1 int fun()
2 {
3 int i;
4 extern int k;
5 }
如果标识符在第一次声明中已经指定了链接属性,那么extern在后面的第二次或以后的声明中,不会更改第一次声明所指定的链接属性.如下所示,i在第一次声明中已经指定了internal链接属性,在函数内的第二次声明并不会将链接属性internal变为external.
1 static int i;
2 int fun()
3 {
4 extern int i;
5 }
7.存储类型
变量的存储类型(storage class)是指存储变量值的内存类型。变量的存储类型决定变量何对创建、何时销毁以及它的值将保持多久。
有3个地方可以用于存储变量1.普通内存2.运行时堆栈3.硬件寄存器。在这3个地方存储的变量具有不同的特性。
变量的缺省存储类型取决于它的声明位置凡是在任何代码块之外声明的变量总是存储于静态内存中,也就是不属于堆栈的内存,这类变量称为静态(static)变量。对于这类变量,无法为它们指定其他存储类型。 静态变量在程序运行之前创建,在程序的整个执行期间始终存在。它始终保持原先的值,除非给它赋一个不同的值或者程序结束。
在代码块内部声明的变量的缺省存储类型是自动的(automatic),也就是说它存储于堆栈中,称为自动(auto)变量。有一个关键字 auto就是用于修饰这种存储类型的,但它极少使用,因为代码块中的变量在缺省情况下就是自动变量。在程序执行到声明自动变量的代码块时,自动变量才被创建,当程序的执行流离开该代码块时,这些自动变量便自行销毁。 如果该代码块被数次执行,例如一个函数被反复调用,则这些自动变量每次都将重新创建。在代码块再次执行时,这些自动变量在堆栈中所占据的内存位置有可能和原先的位置相同,也可能不同。即使它们所占据的位置相同,也不能保证这块内存同时不会有其他的用途。
对于在代码块内部声明的变量,如果给它加上关键字static,可以便它的存储类型从自动变为静态。具有静态存储类型的变量在整个程序执行过程中一直存在,而不仅仅在声明它的代码块的执行时存在。
注意:
1.修改变量的存储类型并不修改变量的作用域,它仍然只能在该代码地内部访
2.函数的形式参数不能声明为静态、因为实参总是在堆栈中传递给函数,用于支持递归。
最后,关键字register可以用于自动变量的声明,提示它们应该存储于机器的硬件寄存器而不是内存中,这类变量称为寄存器变量。通常,寄存器变量比存储于内存的变量访问起来效率更高。但是,编译器并不一定要理睬register 关键字,如果有太多的变量被声明为register,它只选取前几个实际存储于寄存器中,其余的就按普通自动变量处理。如果一个编译器自己具有一套寄存器优化方案,一般我们将我们使用最高频率的变量加上这个关键字。
在许多机器的硬件实现中,并不为寄存器指定地址。同样,由于寄存器值的保存和恢复,某个特定的寄存器在不同的时刻所保存的值不一定相同。基于这些理由,机器并不提供寄存器变量的地址。
参考文献:《C和指针》