手机网站开发 c,正规的网店平台有哪些,app研发费用一般多少钱,为什么很多公司做网站建设欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab#xff0c;机器人运动控制、多机器人协作#xff0c;智能优化算法#xff0c;滤波估计、多传感器信息融合#xff0c;机器学习#xff0c;人工智能等相关领域的知识和技术。 … 欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab机器人运动控制、多机器人协作智能优化算法滤波估计、多传感器信息融合机器学习人工智能等相关领域的知识和技术。 C语言标准定义的32个关键字 1. 数据类型关键字12个(1) 声明和定义的区别(2) 数据类型关键字 2. 控制语句关键字12个3. 存储类关键字5个4. 其他关键字3个 专栏《精通C语言》 1. 数据类型关键字12个
C语言中的数据类型主要有下面几种。实际上数据类型可以理解为固定大小内存块的别名给变量指定类型就是告诉编译器给该变量分配多大的内存空间而变量相当于是内存块的门牌号。 (1) 声明和定义的区别
定义可以看作是声明的一个特例并非所有的声明都是定义。可以通过是否分配内存来区分定义和声明定义会建立存储空间而声名不会建立存储空间。
int function()
{//定义int val; //定义一个变量val此时会给val分配内存由数据类型int决定分配多大内存int为4字节。val 10; //可以为val赋值。//声明extern int val_2; //声明变量val_2不会建立内存。//val_2 10; //error: 声明不会建立内存没有内存空间所以无法赋值。return 0;
}定义定义是指创建一个对象并为这个对象分配一块内存同时将变量名和这个内存块进行绑定。但是同一个变量在同一作用域只能定义一次如果多次定义的话编译器会提示重定义错误。声明 告诉编译器某个名称已经被预定了其他对象/内存块不能再使用这个名称。告诉编译器某个名称已经绑定好内存块了该对象是在其他位置定义的这里用到本名称时不要报错。
(2) 数据类型关键字
char声明字符型变量。
char类型用于存储一个单一字符即1字节存储单元。在给char类型变量赋值时需要把值用英文半角单引号’引起来存储时并非真正把该字符放到存储空间而是把该字符对应的ASCII码存放到存储单元中。也可以把char类型看作是1字节整形。
ASCII对照表如下
ASCII值控制字符ASCII值字符ASCII值字符ASCII值字符0NUT32(space)6496、1SOH33!65A97a2STX3466B98b3ETX35#67C99c4EOT36$68D100d5ENQ37%69E101e6ACK3870F102f7BEL39,71G103g8BS40(72H104h9HT41)73I105i10LF42*74J106j11VT4375K107k12FF44,76L108l13CR45-77M109m14SO46.78N110n15SI47/79O111o16DLE48080P112p17DCI49181Q113q18DC250282R114r19DC351383S115s20DC452484T116t21NAK53585U117u22SYN54686V118v23TB55787W119w24CAN56888X120x25EM57989Y121y26SUB58:90Z122z27ESC59;91[123{28FS6092/124|29GS6193]125}30RS6294^12631US63?95_127DEL
在上面的ASCII码表中ASCII值0-31表示非打印控制字符用于控制打印机等外围设备32-126为打印字符这些字符在键盘上都可以找到127表示del命令。
转义字符
转义字符含义ASCII码值十进制\a警报007\b退格(BS) 将当前位置移到前一列008\f换页(FF)将当前位置移到下页开头012\n换行(LF) 将当前位置移到下一行开头010\r回车(CR) 将当前位置移到本行开头013\t水平制表(HT) 跳到下一个TAB位置009\v垂直制表(VT)011\代表一个反斜线字符092’代表一个单引号撇号字符039代表一个双引号字符034?代表一个问号063\0数字0000\ddd8进制转义字符d范围0~73位8进制\xhh16进制转义字符h范围09afA~F3位16进制
int声明整型变量。
在C语言标准中并没有明确规定整型数据的长度整型数据在内存中所占的字节数与操作系统有关系。一般为4字节
打印格式含义%d输出一个有符号的10进制int类型%o输出8进制的int类型%x输出16进制的int类型字母以小写输出%X输出16进制的int类型字母以大写写输出%u输出一个10进制的无符号数
short声明短整型变量。
长度一般不长于int型数据。一般为2字节
long声明长整型变量。
长度一般不短于int型数据。Windows为4字节Linux为4字节(32位)8字节(64位)。
打印格式含义%hd输出short类型%d输出int类型%l输出long类型%ll输出long long类型%hu输出unsigned short类型%u输出unsigned int类型%lu输出unsigned long类型%llu输出unsigned long long类型
float声明单精度浮点型变量。
浮点型变量也叫做实型变量用于存储小数数值。float单精度浮点型一般占用4字节存储空间7位有效数字。
double声明双精度浮点型变量。
double双精度浮点型精度高于float单精度浮点型占用8字节存储空间15-16位有效数字。
浮点型变量存储的是小数并且浮点型变量的存储单元是有限的这就导致一个小数有效位以外的数字将被舍去这样便会出现一些误差。尤其是float单精度浮点型有时候将一个小数赋值给一个float型变量然后打印该浮点型变量都会出现和原小数不一致这样的情况。一般使用double双精度可以提升精度并且在C语言中一个小数后面不加f则被认为是双精度double类型只有小数后面加f才表示float类型比如3.14f。
signed声明有符号类型变量。
缺省时编译器默认为signed有符号类型。在计算机中所有的数据都是以01的二进制形式来存储的对于有符号数来说如何表示一个数值的正负是一个问题因此便有了原码、反码和补码。
原码二进制数据的最高位用来作为符号位1表示负数0表示正数剩余位来表示这个数据的数值大小绝对值也就是说负数的原码是在其绝对值原码的基础上将最高位变成0。原码的表示简单易懂正负数区分方便且易于转换但是在实际用于计算时却不太方便当两个正数做减法运算或者两个异号的数相加时必须先比较两个数的绝对值大小才能进行减法运算以便于决定最终结果是正号还是负号。所以原码表示数据时不便于做加减运算。反码正数的反码与原码相同负数的反码是在负数的原码基础上符号位不变其它位全部取反。反码的存在一般是为了方便计算补码。补码对正数来说原码、反码、补码是完全一致的对负数来说补码是在其反码的基础上将整个数加1。在计算机系统中所有的数值一律用补码的形式来存储。补码的存在主要有这几种意义 统一0的编码。不管是用原码还是反码来表示0都会有两种表示方式即正0和负0但是我们知道0不区分正负。这就导致同一个数值0出现两种表示方式而使用补码表示时对于正0原码为00000000反码为00000000补码为00000000对于负0原码为10000000反码为11111111补码为11111111100000000其中最高位第九位数字1被舍弃。这样正0和负0的补码就一样了。便于运算。使用补码进行运算时可以将减法转化为加法对于任何数的加减运算都直接使用补码进行加法运算即可并且可以将符号位和其他位统一处理当两个用补码表示的数相加时如果最高位符号位有进位则进位直接舍弃。
unsigned声明无符号类型变量。
数据类型占用空间取值范围short2字节-32768 到 32767 (-2^15 ~ 2^15-1)int4字节-2147483648 到 2147483647 (-2^31 ~ 2^31-1)long4字节-2147483648 到 2147483647 (-2^31 ~ 2^31-1)unsigned short2字节0 到 65535 (0 ~ 2^16-1)unsigned int4字节0 到 4294967295 (0 ~ 2^32-1)unsigned long4字节0 到 4294967295 (0 ~ 2^32-1)
struct声明结构体变量。
数组是相同类型数据的集合而结构体可以把不同的数据组合成一个整体。通过结构体我们可以把大量的不同类型数据甚至是函数和其他复合类型数据打包为一个整体。在使用struct关键字时应区分开结构体类型和结构体变量的区别声明结构体类型并不会分配内存只有在定义结构体类型的时候才会分配内存。通常struct关键字会和typedef关键字一块使用通过别名的方式可以在定义结构体变量时不需要再写struct关键字。
struct st
{int a;char b;
}; //声明结构体类型
struct st s_val {1, a}; //定义结构体变量分配内存//定义结构体变量时不能省略struct关键字typedef struct st
{int a;char b;
}_st; //给结构体类型取别名为_st
_st val {1, a}; //可以不写struct结构体变量所占的存储空间大小是所有结构体成员所占存储空间大小的总和并且需要考虑内存对齐方式。而且空结构体没有任何成员也是占存储空间的空结构体占1字节存储空间。
在结构体中可以包含一种称为柔性数组的成员柔性数组是一个未知大小的数组它必须是结构体的最后一个成员并且柔性数组成员的前面必须有一个其他成员。
struct st
{int val;int arr[0]; //int arr[];
};这个0长度的数组成员arr是不占存储空间的这个结构体的大小为4字节。有了这个0长度数组我们便可以方便的扩展这个结构体的大小了
struct st *p_st (struct st *)malloc(sizeof(struct st) 10 * sizeof(int));如上我们使用包含0长度数组的结构体类型定义一个结构体指针并通过malloc在堆上为其分配一块内存这块内存的大小为44字节而结构体类型大小只有4字节但是我们却可以像访问普通数组一样通过p_st[i]来访问这块内存。也就是说柔性数组并不是结构体类型的成员但是通过结构体成员却可以访问我们自定义的柔性数组存储空间。
最后在C中struct结构体和class类的区别struct成员默认是public属性而class的成员默认是private属性。
同样在C语言中也可以实现C面向对象的效果使用struct结构可以实现封装而结构体做结构体成员又可以实现C中的继承并且函数指针做结构体成员可是模仿C类中的方法。
union声明联合数据类型。
联合union是一个能在同一个存储空间存储不同类型数据的类型也就是说union的所有成员共享同一块存储空间同一存储空间段可以用来存放几种不同类型的成员但每一时刻只有一种起作用。联合体所占的存储空间长度为占用存储空间最大的成员的长度所以也叫做共用体。共用体变量中起作用的成员是最后一次存放的成员在存入一个新的成员后原有的成员的值会被覆盖。并且共用体变量的地址和它的各成员的地址都是同一地址。
一个union变量只分配一个足够大的存储空间能够存储最大长度的成员而不会给每一个成员都分配内存这是union与struct最大的区别。union主要用来达到节省空间的目的和struct一样在C中union的成员默认属性为public。
看下面的例子
typedef union
{int data;char buf[2];
}u_t;int main()
{u_t* p, u;memset(u, 0, sizeof(u));p u;p-buf[0] 0x12;p-buf[1] 0x34;printf(%x\n, p-data);return 0;
}对union成员的访问也需要考虑大端存储模式和小端存储模式。
enum声明枚举类型。
通过enum枚举类型可以定义枚举变量该枚举变量的值只能是枚举类型中列举出来的那些值。
enum 枚举名
{枚举值表
};枚举值表中的所有可用值是枚举变量可以使用的值也成为枚举元素。枚举值是常量在程序中枚举值不能作为左值不能给枚举值使用赋值语句赋值。另外枚举元素本身由系统定义了一个表示序号的数值从0开始顺序定义为012 …依次递增我们也可以显示的给枚举元素赋值。
enum day
{mom 1,tue, //2wed, //3fri 5,sat, //6sun //7
};我们知道使用宏定义#define也可以定义常量但是宏定义常量和枚举常量是有区别的#define 宏常量是在预编译阶段进行简单替换而枚举常量则是在编译的时候确定其值。
void声明空类型指针void类型指针可以接受任何类型指针的赋值无需类型转换声明函数无返回值或无参数等。
void主要的用途是限制函数的返回值或者函数参数。在C语言中如果一个函数不加返回值类型限定那么编译器会默认该函数返回整型值所以当一个函数没有返回值的时候一定要声明为void类型。当函数没有参数时也应该声明为void。实际上在C中函数参数为void表示该函数不接受任何参数如果调用该函数时添加了参数那么会报错而C语言中参数为void的函数可以接受任何类型的参数。为了统一无论C还是C只要函数没有参数都要显式指明参数为void。
void类型指针可以指向任何类型的内存块但是使用void类型指针的时候要格外注意。在ANSI标准中不允许对void类型指针进行加减操作这是因为指针的步长是由指针的类型决定的。比如
int *p 0xaa;
p; //指针类型为int每次加一移动4字节这里int类型的指针每次自加一会移动4字节因为int类型的对象占据的存储空间就是4字节。而void类型的指针在移动时你并不知道它指向的存储空间的大小。但是在GNU标准中是允许对void类型指针进行加减操作的。为了统一我们可以在对void类型指针进行加减操作时强制类型转换以此来说明指针移动步长。
void *p;
(int *)p;对于函数来说如果函数的参数可以是任意类型指针那么可以将函数参数声明为void*类型比如典型的C语言内存操作函数memset和memcpy函数内存操作函数所操作的对象是一块内存本身本就不应该关心这块内存是什么类型只要我们通过函数参数告诉编译器我们要操作的这块内存的大小就行了这也是C语言内存操作函数的精髓所在并且也体现了作为一个内存操作API的统一性。比如
int buf[20];
memset(buf, 0, 20 * (sizeof(int)));这句代码的意思是把buf这个数组清0我们只要把buf这块内存的首地址传给memset函数并将要清0的这块内存的大小通过参数传入就可以了。
最后void是一种抽象可以参考C中的抽象类来理解。抽象类不能实例化同样我们也不能去定义一个void类型的变量因为在定义变量时编译器要为变量分配内存而void类型本身就是一种抽象编译器不知道分配多大内存给这个变量。通常void类型用于定义一个可以指向任何类型内存块的指针。
2. 控制语句关键字12个
if条件语句。
else条件语句中的否定分支在if后使用或作为else if分支。
switch开关语句。
case开关语句分支。
case后面的值只能是整型或字符型的常量或者常量表达式。当有较多的case选项时应该尽量把出现概率更大的case选项放在前面以提升程序的执行效率。
default开关语句中的其他分支。
for循环语句。
do循环语句中的循环体。
while循环语句中的条件。
break跳出循环。
continue跳出本次循环进入下一次循环。
goto无条件跳转。
return返回语句可带参数。
return用来终止一个函数并将return后面的值返回给函数的返回值。在函数内部当执行到return语句的时候就会终止这个函数并返回值return语句后面的程序将不会再被执行。
return返回的值不能是存储在栈上的值局部变量因为局部变量在这个函数结束的时候被自动销毁它的生命周期仅限于这个函数内部所以不能作为return语句的返回值。
3. 存储类关键字5个
auto声明自动变量缺省时编译器默认为auto。
默认情况下缺省时所有变量都是auto的。
extern声明外部变量。
extern表示外部的通过extern声明的变量或函数表示该变量或者函数是在外部文件定义的告诉编译器在本文件中遇到该变量或者函数时去其他文件中寻找变量或函数的定义。
register声明寄存器变量。
定义寄存器变量提高效率。register是建议型的指令而不是命令型的指令如果CPU有空闲寄存器那么register就生效如果没有空闲寄存器那么register无效。该关键字请求编译器尽量的将变量存放在CPU内部寄存器中这样在访问变量时不需要再通过内存寻址的方式访问而是直接在寄存器中访问大大提升了访问速度。但是CPU内部寄存器是有限的所以register关键字只能是尽可能的请求编译器把变量存放在寄存器而不是一定存放在寄存器。因为register关键字用于请求将数据存放在寄存器所以使用register修饰符来修饰的变量必须是能被CPU寄存器所接受的类型即register修饰的变量必须是长度小于或等于整形长度的值。同时因为register修饰的变量可能会存放在寄存器中也可能存放在内存中所以不能对register修饰的变量进行取址操作即不能通过取址操作符来获取register修饰变量的地址。
static声明静态变量。 修饰变量 static关键字可以修饰全局变量和局部变量并且他们都会被存放在内存的静态区。 静态全局变量限定变量的作用域为当前文件即从变量定义之处开始一直到当前文件末尾当前文件中该变量定义之前也无法使用除非加extern声明其他文件中即便是使用extern声明也无法使用。静态局部变量定义在函数体内部并且作用域仅限于当前函数当前文件该函数体外部无法使用。因为static修饰的静态变量存放在内存的静态区所以函数运行结束这个静态变量也不会被销毁函数下次被调用时这个变量的值依然存在也就是我们说的静态局部变量只能被初始化一次并且有记忆功能下次调用函数时可以使用上次函数调用结束时静态局部变量的值。需要注意的是普通的局部变量存放在栈区函数调用结束变量就会被析构也就是说普通局部变量的声明周期为定义该变量的函数体内。而静态局部变量存放在静态区它的生命周期是整个程序执行期间也就是说定义该静态局部变量的函数执行完毕并不会析构静态局部变量而是在当前程序执行完毕才会析构。 修饰函数 使用static关键字修饰函数可以将函数变为静态函数也成为内部函数静态函数的作用域为当前文件在该文件之外无法访问。使用静态函数的好处是可以避免不同文件中函数同名引起的错误但是会导致该文件之外无法调用的问题。
const声明只读变量C和C区别。
在C语言中const定义的并不是真正的常量而是具有只读属性的变量其本质还是变量只不过不可修改实际上在C语言中是可以通过指针等其他方式间接修改的而在C中const定义的是真正的常量C中是通过符号表一一对应的方式实现的。通过下面的例子也可以证明
const int NUM 10;
char buf[NUM];上面代码在C语言中编译不通过但是在C中编译通过。我们知道定义数组时要指定数组大小以便于编译器分配内存。在C语言中编译不通过也就证明了const定义的依然是变量而不是常量。
编译器通常不会为const只读变量分配存储空间而是将它们保存在符号表中这使得它们成为一个编译期间的值没有读写内存的操作大大提高了效率。另外需要注意const与宏#define的区别
#define NUM 1 //宏定义一个常量
const int VAL 2; //还没有将VAL放入内存中
int a VAL; //此时为VAL分配内存后面不再分配内存
int b NUM; //预编译期间进行宏替换分配内存
int c VAL; //不会分配内存
int d NUM; //宏替换还会分配内存从汇编的角度来看const定义的只读变量只是给出了内存地址而#define给出的是立即数。所以在程序运行过程中const定义的只读变量只有一份拷贝全局只读变量存放在静态区而不是堆栈而#define定义的常量在内存中有多份拷贝。#define在预编译的时候进行宏替换而const只读变量是在编译时确定它的值。另外#define定义的常量没有类型而const修饰的只读变量是有类型的。const 修饰的只读变量不能用来作为定义数组的维数 也不能放在case 关键字后面。
最后当const修饰指针时放在不同位置所代表的含义也不同。
const int *p; //const修饰指针指向的内存//指针本身可变指针指向的内存不可修改
int const *p; //const修饰指针指向的内存//指针本身可变指针指向的内存不可修改int * const p; //const修饰指针本身//指针指向不可修改指针指向的内存可以修改const int const *p; //指针本身和指针指向的内存都不可修改4. 其他关键字3个
sizeof计算一个对象所占的字节数。
sizeof在使用时虽然会加括号但是他并不是函数而是一个关键字。实际上通过sizeof计算一个变量所占的内存大小时可以省略括号sizeof(val)和sizeof val都可以但是在计算数据类型的大小时必须加括号sizeof(int)否则的话会和类型扩展混淆比如unsigned int就是扩展为无符号整型变量。因为sizeof不是函数所以在使用时不需要包含任何头文件但是sizeof是有返回值的范围值类型为size_t在32位操作系统下是unsigned int类型。
在计算一个字符串变量的大小时要区分sizeof与strlen的区别strlen是一个函数用于计算字符串的长度所以不包含字符串最后的’\n’而sizeof是计算变量所占内存大小包括字符串结束符’\n’。
typedef取别名。
typedef可以为一个数据类型定义一个新的名字但是不能创建一个新的类型。与#define不同typedef仅限于为数据类型取别名而不能为表达式或具体的值取别名。#define发生在预处理阶段typedef发生在编译阶段。
volatile防止编译器优化说明变量在程序执行中可被隐含地改变。
volatile是易变的意思它修饰的变量表示该变量的值可能被某些因素所修改比如操作系统、硬件外设或其他线程等等。volatile关键字修饰的变量编译器不会对改变量进行优化访问。
当我们读取一个普通变量的值时编译器为了加快访问速度一般会在缓存中读取该变量的值而不是直接去寄存器取值。但是有时候寄存器的值并不是通过程序去修改的比如嵌入式开发中常用开发板进行开发很多时候寄存器的值会被芯片的外设所修改。这时候虽然我们程序中并没有去修改寄存器的值但是寄存器值却因为外界因素而发生了改变。当我们去访问这种变量的时候如果不加volatile关键字编译器默认会在缓存中取值而此时缓存中的值是一个旧值变量的真实值已经发生了改变。所以加volatile关键字就是为了告诉编译器不要对访问进行优化每次都应该去变量的地址处去访问变量值以此来确保每次取到的都是变量的最新值。
下面通过例子说明
int val 1;
int a val;
int b val;在上面的代码中变量val没有被用作左值也就是说在程序中变量val的值没有被显式改变这时候编译器就会认为变量val的值没有发生过改变并会对val的访问做优化处理。当给变量a赋值时编译器取到val的值之后赋给a并且这个值会被放到缓存中。当给b赋值时因为编译器认为val的值没有发生改变所以会直接在缓存中取val的值而不会去val变量的地址处取值这样大大提高了访问速度。这么做的前提是两次访问val的语句之间没有将val当左值的语句即修改val值的语句。
如果说将val变量修饰为volatile变量那就不同了。
volatile int val 1;
int a val;
int b val;此时编译器认为val的值是随时可能发生改变的不管程序中有没有将val当作左值的语句每次访问val变量都会区val变量的地址处去访问。也就是说在给a赋值时编译器将会在val地址处取值当给b赋值时编译器依然会去val变量的地址处取值。
一般来说对寄存器变量、端口数据变量、多线程共享数据变量使用volatile修饰可以保证对变量真实值的稳定访问。