1.1、基本数据类型
数据类型分两类:基本数据类型 + 复合类型
基本类型:char 、short、int 、long 、float 、double
复合类型:数组、结构体、共用体(C语言没有类,C++有)
1.2、内存占用与sizeof运算符
数据类型就像一个一个的模子,这个模子实例化出C语言的变量。变量存储在内存中,需要占用一定的内存空间。一个变量占用多少空间是由变量的数据类型决定的。每种数据类型,在不同的机器平台上占用内存是不同的。我们一般讲的时候都是以32位CPU为默认硬件平台来描述:
char 1字节
short 2字节
int 4字节
long 4字节
float 4字节
double 8字节
int main(void)
{
printf("sizeof(char) = %d\n", (int)sizeof(char));
return 0;
}
1.3、有符号数和无符号数
对于char、 short 、int 、long 等整形类型的数,都分有符号数和无符号数。对于float、 double这种浮点型数来说,只有有符号数,没有无符号数。
对于C语言来说,数(也就是变量)是存储在内存中的一个一个的格子中的。存储的时候是以二进制方式存储的。对于有符号数和无符号数来说,存储方式不同的。比如对于int来说,unsigned int 无符号数,32位(4字节)全部用来存储数的内容,所以表示数的范围是0 ~ 4294967295(2^32 - 1)。signed int 有符号数,32位中的最高位用来存符号(0表示正数,1表示负数),剩余的31位用来存数据。所以可以表示的数的范围是-2147483648 ~ 2147483647(2^31)。
对于float和double这种浮点类型的数,他在内存中的存储方式和整形术不一样。所以int与float相比,虽然都是4字节,但是在内存中的存储方式完全不同。所以同一个4字节的内容,如果存储时是按照int存放的,取的时候一定要按照int型方式去取。如果存的时候和取的时候理解的方式不同,那数据就完全错了。
总结:存取方式上主要有两种,一种是整形,一种是浮点型,这两种存取方式完全不同,没有任何关联,所以是绝对不能随意改变一个变量的存储方式。在整形和浮点型之内,比如说4种整形char short int long只是范围大小不同而以,存储方式一模一样的。float和double存储原理是相同的,方式上有差异,导致了能表示的浮点型的范围和精度不同。
1.4、空类型
C语言中的void类型,代表任意类型,而不是空的意思。任意的意思是说不是想变成谁就变成谁,而是说他的类型是未知的,是还没指定的。
在函数的参数列表和返回值中,void代表的含义是:一个函数形参列表为void,表示这个函数调用时不需要给他传参。返回值类型是void,表示这个函数不会返回一个有意义的返回值。所以调用者也不要想着去使用改返回值。
void * 是void类型的指针。void的类型的指针的含义是:这是一个指针变量,该指针指向一个void的类型的数。void类型的数就是说这个数有可能是int,也有可能是float,也有可能是一个结构体,那种类型都有可能,只是我当前不知道。void型指针作用就是,程序不知道那个变量的类型,但是程序员自己知道。程序员如何知道?当时给这个变量赋值的时候是什么类型,现在取的时候还是什么类型。这些类型对不对,能否兼容,完全有程序员自己负责。编译器看到void就没办法帮你做类型检查了。
int main(void)
{
int a = 1983;
void *pVoid;
pVoid = &a;
printf("*pvoid = %d\n",*(int *)pVoid); //强制类型转换
return 0;
}
注意:void *类型的指针,可以指向任意类型的数。但是程序员使用时心里必须清楚,存放时和取时数的类型必须相同,如果不同,编译器是没法发现的,结果都要程序员自己负责。
1.5、数据类型转换
C语言中有各种数据类型,写程序是需要定义各种类型的变量。这些变量需要参与运算。
一个基本要求就是:不同类型的变量是不能直接运算的。
也就说,int和float类型的变量不能直接加减等运算。要运算,必须先把两种类型转换成相同的类型才可以。
2.1、隐式转换
就是自动转换,是C语言默认会进行的,不用程序员干涉。
C语言的理念:隐式类型转换默认向精度更高、范围更大的类型转换。
int main(void)
{
int a = 3;
float b = 3.5;
int c;
c = a + b;
printf("c = %d\n",c);
return 0;
}
结果:6
分析:
1.编译器发现a和b类型不同,就进行隐式转换,将a转换成float类型的临时变量f1
2.f1 + b,得到一个临时变量f2,值为float类型的6.500000
3.c = f2,编译器发现c和f2的类型不同,但赋值时必须以 = 左边的类型为准,于是编译器隐式转换,将f2转换成临时变量int型的i,值为6
4.printf打印时发现%d,右边的变量c类型为int,编译器检查发现类型匹配,直接打印。
2.2、强制转换
C语言默认不会这么做,但程序员想这么做,所以就强制这么做了。
2.3、C语言与bool类型
C语言中原生类型没有bool,C++有。在C语言中如果需要使用bool类型,可以用int来代替。
很多代码体系中,用宏定义来代表真和假。
#define TRUE 1
#define FALSE 0
2.1、变量
指的是在程序运行过程中,可以通过代码使它的值改变的量。
2.2、局部变量
指的是定义在函数中的变量。
2.3、常量
指的是从在运行过程中不会改变的量。常量的值在程序运行之前初始化的时候给定一次,以后就不会改变了。
2.3.1、#define 定义的常量
#define N 20 //符号常量
int a[N];
2.3.2、const 关键字
const int i = 1983; //i不能再赋值
难点:const和指针结合,共有4种形式:
const int *p; //p是一个指针,指针指向一个int型整数。p所指向的是常量
int const *p; //p是一个指针,指针指向一个int型整数。p所指向的是常量
int *const p; //p是一个指针,指针指向一个int型整数。p本身是常量,p所指向的是个变量
const int *const *p; //p是一个指针,指针指向一个int型整数。p本身是常量,所指向的也是常量
结论和记忆:
1.const在号前面,表示const作用于p所指向的量。所以这时p所指向的是个常量。
2.const在号后面,表示p本身是常量,但是p指向的不一定是常量。
const 型指针有什么作用?
char *strcpy(char *dst,const char *src);
字符串处理函数strcpy,它的函数功能是把src指向的字符串,拷贝到dst中。
3.1、枚举常量
是宏定义的一种替代品,在某些情况下比宏定义好用。
3.2、全局变量
指的是定义在函数外面的变量。
全局变量的定义和初始化是在main函数运行之前发生的。
4.1、局部变量和全局变量的区别
4.1.1、定义同时没有初始化,则局部变量的值是随机的,而全局变量的值默认是0。
4.1.2、使用范围上,全局变量具有文件作用域,而局部变量只具有代码块作用域。
4.1.3、在生命周期上,全局变量是在程序开始运行之前的初始化阶段就诞生了,到整个程序结束的时候才死亡。而局部变量在进 入局部变量代码块时诞生的,在该代码块退出的时候死亡。
4.1.4、在变量分配位置中,全局变量分配在数据段上,而局部变量分配在栈上。
基本概念:
作用域:起作用的区域,也就是可以工作的范围。
代码块:所谓代码块,就是用{}括起来的代码。
数据段:数据段存的是数,像全局变量就是存在数据段的。
代码段:存的是程序代码,一般是只读的。
栈:先进后出。C语言中局部变量就是分配在栈中。
4.2、普通局部变量(auto)
int main(void)
{
fun();
fun();
fun();
return 0;
}
void fun(void)
{
int i = 1;
i++;
printf("i = %d\n",i);
}
结果:2
2
2
局部变量i的解析:
在连续三次调用fun()中,每次调用时,在进入函数fun()后都会创造一个新的变量i,并且给它赋初值1,然后i++是加到2,然后输出时为2,然后fun()本次调用结束,同时杀死本次创造的i,这就是局部变量i的整个生命周期。下次再调用fun()函数时,又会重新创造一个i,经过整个程序运行,最终在函数运行完退出时再次杀死i。
4.3、静态局部变量(static)
静态局部变量和普通局部变量不同。静态局部变量是定义在函数内部的,静态局部变量定义时前面加static关键字来标识,静态局部变量所在的函数在多次被调用时,只有第一次才经历变量定义和初始化,以后多次调用时不再定义和初始化,而是维持上一次调用时执行后这个变量的值,本次接着来使用。
静态局部变量在第一次函数被调用时创造并初始化,但在函数退出时它不再死亡,而是保持其值等待函数下一次被调用。下次调用时不再重新创造和初始化该变量,而是直接用上一次留下的值为基础来进行操作。
int main(void)
{
fun();
fun();
fun();
return 0;
}
void fun(void)
{
static int i = 1;
i++;
printf("i = %d\n",i);
}
结果:2
3
4
4.4、静态局部变量的这种特性,和全局变量非常相似。相同点是都创造和初始化一次,以后调用时保持上次的值不变。不同点在于作用域不同,静态局部变量的作用域是在被定义的那个函数中。
4.5.register关键字
register(寄存器),C语言的一个关键字
register int i = 9;
总结:register类型的局部变量表现上和auto是一样的,没啥用。
register被称为C语言中最快的变量。C语言的运行时的环境承诺,会尽量将register类型的变量放到寄存器中去运行(普通的变量是在内存中),所以此类型的变量访问速度会快很多。但是它是有限制的,首先是寄存器数目有限,所以register类型的变量不能太多;其次是在数据类型上有限制,比如说不能用来定义double类型的register变量。一般只在内核或者启动代码中,需要反复使用同一个变量这种情况下才会使用。
5.1、跨文件的变量引用
(1)在a.c中定义的全局变量,在a.c中可以使用,在b.c中不可以直接使用,编译器报错。
(2)想在b.c中使用a.c中定义的全局变量,然后在b.c中先声明函数,再使用函数。即可达到在b.c中间接引用a.c中的函数的目的。
(3)想在b.c中直接引用a.c中的全局变量,则必须在b.c中引用前先声明。
extern int a;
这是一个全局变量a的声明,这句话告诉编译器,我在外部(程序中不是本文件的另一个文件)某个地方定义了另一个全局变量int a,而我现在要在这里引用它,告诉编译器一声,不用报错了。
5.2、普通全局变量
就是平时用的,定以前不加任何修饰词。可以在各个文件中使用,可以在项目内别的.c文件中被看到,所以要确保不重名。
5.3、静态全局变量
就是用来解决重名问题的,定义时在定义前加static关键字,告诉编译器这个变量只在当前文件内使用,在别的文件中绝对不会使用。这样就不用担心重名的问题了。所以静态的全局变量就用在我定义的这个全局变量并不是为了给别的文件使用,本来就是给我这个文件自己使用。
6.1、为什么需要头文件
函数的声明是很重要的。当我们在一个庞大的项目中,有很多源文件,每一个源文件中都有很多函数,并且需要在各个文件中相互穿插引用函数。
#include包含头文件时,用<>和""的区别
<>用来包含系统自带的头文件,系统自带指的是不是你写的,是编译器或库函数或操作系统提供的头文件。
""用来包含项目目录中的头文件,这些一般是我们自己写的。
防止重复包含头文件:
#ifndef _A_H
#define _A_H
#endif
写程序时,最好不要在头文件里定义变量,因为这时候该头文件被多个源文件包含时,就会出现重复定义的问题。全局变量的定义就应该放在某文件中,然后在别的原文件中使用前用extern声明。
内容说明:对朱有鹏老师课堂笔记的整理!
来源:CSDN
作者:葫芦僧的秘密
链接:https://blog.csdn.net/weixin_44739916/article/details/104308136