网站设计工资一般多少,东莞seoseo优化排名,崇信县门户网站首页,宠物网站怎么做#xff08;叠甲#xff1a;如有侵权请联系#xff0c;内容都是自己学习的总结#xff0c;一定不全面#xff0c;仅当互相交流#xff08;轻点骂#xff09;我也只是站在巨人肩膀上的一个小卡拉米#xff0c;已老实#xff0c;求放过#xff09;
字符类型介绍
char…叠甲如有侵权请联系内容都是自己学习的总结一定不全面仅当互相交流轻点骂我也只是站在巨人肩膀上的一个小卡拉米已老实求放过
字符类型介绍
char //字符数据类型
short //短整型
int //整形
long //长整型
long long //更长的整形
float //单精度浮点数
double //双精度浮点数
//C语言有没有字符串类型
类型的基本归类
整数类型
charunsigned charsigned char
shortunsigned short [int]signed short [int]
intunsigned intsigned int
longunsigned long [int]signed long [int]
浮点数类型
float
double
构造类型 数组类型 [ ]结构体类型 struct枚举类型 enum联合类型 union
指针类型
int *pi;
char *pc;
float* pf;
void* pv;
空类型
oid 表示空类型无类型
通常应用于函数的返回类型、函数的参数、指针类型。
整型在内存中的储存
变量的创建是要在内存中开辟空间的空间的大小是根据不同的类型而决定的。
int a 10;
int b -10;
我们知道a会被分配4个字节的空间那是如何存储的呢如此说来就必须要介绍一下原码、反码、补码了。计算机中的有符号数有三种表示方法即原码、反码和补码。
三种表示方式均有符号位和数值位两部分符号位都是用0表示正用-1表示负而数值位三种表示方法各有不同。
原码
直接将值按照正负规则翻译成二进制即可
反码
将原码的符号位不变其他位次按位取反即可
补码
反码1就就得到了补码
正数的原、反、补相同对于整型来说数据存放内存中其实存放的是补码。
为啥放反码
在计算机系统中数值一律用补码表示和存储原因在于使用补码可以将符号位和数值域统一处理同时加法和减法也可以统一处理CPU只有加法器此外补码和原码相互转换其运算过程是相同的。不需要额外的硬件电路。
瞅瞅内存中的存储是怎么样的吧 对于a和b分别储存的是补码但是顺序好像有的不对0a和f6通过十六进制查看怎么在最前面这个时候又要介绍一下大小端了。
大小端
什么是大小端
大端存储是指数据的低位保存在内存的高地址位而数据的高位保存在内存的低地址中
小端存储是指数据的低位保存在内存的低地址中而数据的高位保存在内存的高地址位
为什么有大端和小端
为什么会有大小端之分呢在计算机系统中我们以字节为单位每个地址单元都对应着一个字节一个字节8bit但是C语言中除了8bit的char外还要16bit的short32位的long。对于位数大于8位的处理器例如16位处理器32位处理器由于寄存器宽度大于一个字节那么必然存在着一个如果将多个字节安排的问题这就导致了大小端存储的问题。
如一个16bit的short型x在内存中的地址位0x0010x的值为0x1122,那么0x11为高字节0x22为低字节对于大端系统0x11放在地址中即0x0010中0x22放在高地址中即0x0011中小端模式小端则是相反的。我们常用的x86结构是小端模式而keil C51为大端模式。很多ARMDSP都为小端模式。有些ARM处理器还可以由硬件来选择大端模式还是小端模式。
经典例题判断当前机器的字节序
#includestdio.h//check1
int check_sys()
{int i 1;return (*(char*)i);
}//check2
int check_sys()
{union{int i;char c;}un;un.i 1;return un.c;
} int main()
{int ret check_sys();if (ret 1){printf(小端\n);}else{printf(大端\n);}return 0;
}
浮点数在内存中的储存
浮点数包括float、double、long double类型。
int main()
{int n 9;float* pFloat (float*)n;printf(n的值为%d\n, n);printf(*pFloat的值为%f\n, *pFloat);*pFloat 9.0;printf(num的值为%d\n, n);printf(*pFloat的值为%f\n, *pFloat);return 0;
} 出现这个结果是因为浮点数的存储模式和整数时是不同的解读方式自然也不同。
根据国际标准IEEE电子和电子工程协会754任意一个二进制浮点数V可以表示成下面的形式
1(-1)^S*M*2^E
2)-1)^S表示符号位当S为0时V为正数当S为1时V为负数
3M表示有效数字大于等于1小于2
4) 2^E表示指数位
例十进制的5.0写成二进制是101.0相当于1.01x2^2。那么按照上面V的格式可以得出S 0M 1.01E 2
十进制的-5.0写成二进制是-101.0相当于-1.01x2^2。那么按照上面V的格式可以得出S 1M 1.01E 2
IEEE 754规定对于32位的浮点数最高的1位是符号位S接着是8位是指数E剩下的23位有效数字M。 对于64位的浮点数最高的1位是符号位S接着是11位是指数E剩下的52位有效数字M。 IEEE 754 对有效数字M和指数E还有一些特别规定1M2也可以写成1.xxxxxx的形式。IEEE 754还规定计算机内部保存M时默认这个数的第一位总是1因此可以舍去只保存后面的xxxx部分比如保存1.01的时候只保存01等读取的时候再把第一位的1加上去这样做的目的是节省1位有效数字。以32位浮点数为例这样就可以留24位有效数字。
对于指数E情况比较复杂
首先E是一个无符号整数unsigned int这意味着如果E是8位它的取值范围是0~255如果E为11位它的取值范围为0~2047。但是我们知道科学计数法中E可以出现负数所以IEEE 754规定存入内存时E的真实值必须加上一个中间数对于8位的E这个中间数是127对于11位的E这个中间数是1023.比如2^10的E是10所以保存为32位浮点数时必须保存为1012710001001。
E不全为0或不全为1 这时浮点数就采用下面的规则表示即指数 E 的计算值减去 127 或 1023 得到真实值再将有效数字 M 前 加上第一位的1 。 比如 0.5 1/2 的二进制形式为 0.1 由于规定正数部分必须为 1 即将小数点右移 1 位 则为1.0*2^(-1) 其阶码为 -1127126 表示为 01111110 而尾数 1.0 去掉整数部分为 0 补齐 0 到 23 位 00000000000000000000000则其二进制表示形式为 : 0 01111110 00000000000000000000000
E全为0
这时浮点数的指数E等于1-127或者1-1023即为真实值有效数字M不再加上第一位的1而是还原为0.xxxxx的小数这样做是为了表示正负0以及接近0的很小的数字
E全为1
这时如果有效数字M全为0,表示无穷大正负取决于符号位S
这样就能够介绍上面的现象了整数9的二进制
0000 0000 0000 0000 0000 0000 0000 1001
如果以浮点数的形式解读M的值为全0结果则为0
浮点数9.0的二进制
0 10000010 001 0000 0000 0000 0000 0000
以整数的形式解读按照原、反、补的原则正好是1091567616
指针的进阶
字符指针char *
一般使用
int main()
{char ch w;char *pc ch;*pc w;return 0;
}int main()
{char* pstr hello world.;//这里是把一个字符串放到pstr指针变量里了吗printf(%s\n, pstr);return 0;
}
代码char* pstr hello world .不是将字符串整体放入到字符指针之中本质是把字符串hello world .首字母的地址放到了pstr中。即将h的地址存放到指针变量中。 经典例题
#include stdio.h
int main()
{char str1[] hello world.;char str2[] hello world.;char* str3 hello world.;char* str4 hello world.;if (str1 str2)printf(str1 and str2 are same\n);elseprintf(str1 and str2 are not same\n);if (str3 str4)printf(str3 and str4 are same\n);elseprintf(str3 and str4 are not same\n);return 0;
} 这里str3和str4指向的是同一个常量字符串C/C会把常量字符串存储到单独的一个内存区域。当几个指针。指向同一个字符串的时候它们实际上会指向同一块内存。但是相同的常量字符串去初始化不同的数组的时候会开辟出不同的内存块所以str1和str2不同str3和str4不同。
数组指针这是指针
下面哪一个是指针
int *p1[10];
int (*p2)[10];
//p1, p2分别是什么
解答int (*p2) [10] p先和*结合说明p是一个指针变量然后指针指向的是一个大小为10个整数的数组所以p是一个指针指向一个数组叫数组指针。
注[ ]的优先级要高于*的所以必须 加上来保证p先和*结合。
数组名和数组名
对于下面数组
int arr[10];
我们知道arr是数组名数组名表示首元素的地址。那arr数组名到底是啥
#include stdio.h
int main()
{int arr[10] {0};printf(%p\n, arr);printf(%p\n, arr);return 0;
} 打印出来是一样的再看
#include stdio.h
int main()
{int arr[2] { 0 };printf(arr %p\n, arr);printf(arr %p\n, arr);printf(arr1 %p\n, arr 1);printf(arr1 %p\n, arr 1);return 0;
} 结论其实arr和arr虽然值是一致的但是意义是不同的实际上arr表示的是整个数组的地址而不是数组首元素的地址。arr1跳过的是整个数组 arr与arr1相差8。
数组指针的使用
例1
#include stdio.h
int main()
{int arr[10] { 1,2,3,4,5,6,7,8,9,0 };int(*p)[10] arr;//把数组arr的地址赋值给数组指针变量p//但是我们一般很少这样写代码return 0;
}
例2
#include stdio.h
void print_arr1(int arr[3][5], int row, int col)
{int i 0;for (i 0; i row; i){for (int j 0; j col; j){printf(%d , arr[i][j]);}printf(\n);}printf(\n);
}
void print_arr2(int(*arr)[5], int row, int col)
{int i 0;for (i 0; i row; i){for (int j 0; j col; j){printf(%d , arr[i][j]);}printf(\n);}
}
int main()
{int arr[3][5] { 1,2,3,4,5,6,7,8,9,10 };print_arr1(arr, 3, 5);//数组名arr表示首元素的地址//但是二维数组的首元素是二维数组的第一行//所以这里传递的arr其实相当于第一行的地址是一维数组的地址//可以数组指针来接收print_arr2(arr, 3, 5);return 0;
}
数组参数、指针参数
一维数组传参
#include stdio.hvoid test(int* arr)
{}void test2(int** arr)
{}
int main()
{int arr[10] { 0 };int* arr2[20] { 0 };test(arr);test2(arr2);
}
二维数组传参
#include stdio.h
void test(int arr[3][5])//ok
{}
void test(int arr[][5])//ok
{}
//总结二维数组传参函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组可以不知道有多少行但是必须知道一行多少元素。
//这样才方便运算。void test(int(*arr)[5])//ok
{}int main()
{int arr[3][5] { 0 };test(arr);
}
一级指针传参
#include stdio.h
void print(int* p, int sz)
{int i 0;for (i 0; i sz; i){printf(%d\n, *(p i));}
}
int main()
{int arr[10] { 1,2,3,4,5,6,7,8,9 };int* p arr;int sz sizeof(arr) / sizeof(arr[0]);//一级指针p传给函数print(p, sz);return 0;
}
二级指针传参
#include stdio.h
void test(int** ptr)
{printf(num %d\n, **ptr);
}
int main()
{int n 10;int* p n;int** pp p;test(pp);test(p);return 0;
}
函数指针
先看为敬
#include stdio.h
void test()
{printf(hehe\n);
}
int main()
{printf(%p\n, test);printf(%p\n, test);return 0;
} 输出的是两个地址这两个地址是test函数的地址那我们的函数的地址想要保存起来怎么保存
void test()
{printf(hehe\n);
}
//下面pfun1和pfun2哪个有能力存放test函数的地址
void (*pfun1)();
void *pfun2();
pfun1可以存放pfun1先和*结合说明pfun1是指针指针指向的是一个函数指向的函数无参数返回值类型为void。
函数指针数组
函数指针数组的定义
int (*parr[])();
parr先和[ ]结合说明parr是数组数组的内容是 int (*)()类型的函数指针。
函数指针数组的用途转移表
使用常规思路
#includestdio.h
int main()
{int x, y;int input 1;int ret 0;do{printf(*************************\n);printf( 1:add 2:sub \n);printf( 3:mul 4:div \n);printf(*************************\n);printf(请选择);scanf(%d, input);switch (input){case 1:printf(输入操作数);scanf(%d %d, x, y);ret add(x, y);printf(ret %d\n, ret);break;case 2:printf(输入操作数);scanf(%d %d, x, y);ret sub(x, y);printf(ret %d\n, ret);break;case 3:printf(输入操作数);scanf(%d %d, x, y);ret mul(x, y);printf(ret %d\n, ret);break;case 4:printf(输入操作数);scanf(%d %d, x, y);ret div(x, y);printf(ret %d\n, ret);break;case 0:printf(退出程序\n);break;default:printf(选择错误\n);break;}} while (input);return 0;
}
使用函数指针数组
#include stdio.h
int add(int a, int b)
{return a b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}
int main()
{int x, y;int input 1;int ret 0;int(*p[5])(int x, int y) { 0, add, sub, mul, div }; //转移表while (input){printf(*************************\n);printf( 1:add 2:sub \n);printf( 3:mul 4:div \n);printf(*************************\n);printf(请选择);scanf(%d, input);if ((input 4 input 1)){printf(输入操作数);scanf(%d %d, x, y);ret (*p[input])(x, y);}elseprintf(输入有误\n);printf(ret %d\n, ret);}return 0;
}
指向函数指针数组的指针
#includestdio.h
void test(const char* str)
{printf(%s\n, str);
}
int main()
{//函数指针pfunvoid (*pfun)(const char*) test;//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[10])(const char*) pfunArr;
return 0;
}
回调函数
回调函数是通过函数指针调用的函数如果你把函数的指针作为参数传递给另外一个函数当这个指针被用来调用其所指向的函数时我们就可以说这时回调函数。回调函数不由该函数的实现方直接调用而是在特定的事件或条件发生时由另外的一方调用用于对该事件或条件进行响应。
使用回调函数模拟实现qsort采用冒泡排序的方式
#include stdio.h
int int_cmp(const void* p1, const void* p2)
{return (*(int*)p1 - *(int*)p2);
}
void _swap(void* p1, void* p2, int size)
{int i 0;for (i 0; i size; i){char tmp *((char*)p1 i);*((char*)p1 i) *((char*)p2 i);*((char*)p2 i) tmp;}
}
void bubble(void* base, int count, int size, int(*cmp)(void*, void*))
{int i 0;int j 0;for (i 0; i count - 1; i){for (j 0; j count - i - 1; j){if (cmp((char*)base j * size, (char*)base (j 1) * size) 0){_swap((char*)base j * size, (char*)base (j 1) * size, size);}}}
}
int main()
{int arr[] { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };//char *arr[] {aaaa,dddd,cccc,bbbb};int i 0;bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);for (i 0; i sizeof(arr) / sizeof(arr[0]); i){printf(%d , arr[i]);}printf(\n);return 0;
}
over see you next time!