一.数据存储_数据类型,整型存储和大小端
#include<stdio.h>
int main()
{
int i = 0;
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%p\n", arr);
printf("%p\n", &i);
for (i = 0;i <= 12;i++)
{
arr[i] = 0;
printf("hehe\n");
}
return 0;
}
debug
release(优化)
不是所有死循环都会优化
1.数据类型介绍
1.1类型的基本归类
char //字符数据类型 1
short //短整型 2
int //整形 4
long //长整型 4/8
//sizeof(long)>= sizeof(int)
//32位平台是4 64位平台是8
long long//更长的整形C99 8
float //单精度浮点数 4
double//双精度浮点数 8 内置类型
类型的意义:
使用这个类型开辟内存空间的大小
(1)整型家族
char//本质是ASCII码值,是整型 类型1
unsigned char//无符号 类型2
signed char//有符号 类型3
//char到底是signed char还是unsigned char标准是未定义的,取决于编译器的实现
short
unsigned short [int]
signed short [int]//short--->signed short
int
unsigned int
signed int //int a; ---> signed int a;
long
unsigned long [int]
signed long [int]//
long long
unsigned long long [int]
signed long long [int]//
有些数据无负数 用unsigned
数据有正负 用signed
(2)浮点数家族
只要是小数就可以使用浮点型
float的精度低,存储的数值范围较小,double的精度高,存储的数据范围大
(3)构造类型(自定义类型)
数组类型 int arr1[5]
结构体类型 struuct
枚举类型 enum
联合类型 union
(4)指针类型
int* pi;
char* pc;
float* pf;
void* pv;
(5)空类型
void (函数的返回值,函数的参数,指针类型)
void test(void)
{
printf("hehe\n");
}
int main()
{
test(1);
return 0;
}
2.整形在内存中的存储
十进制 21
二进制 0b10101
八进制 025
十六进制 0x15
2.1原码 反码 补码
整数的二进制表示形式
正整数,三者相同
负整数,需要计算
原码:直接通过正负的形式写出的二进制序列
反码:原码的符号位不变,其他位按位取反
补码:反码+1
int main()
{
int a = 20;
//00000000000000000000000000010100
//0x00 00 00 14
int b = -10;
//10000000000000000000000000001010
//0x80 00 00 0a
//11111111111111111111111111110101
//0xfffffff5
//11111111111111111111111111110110
//0xfffffff6
}
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理
同时,加法和减法也可以统一处理(cpu只有加法器)此外,补码与原码互相转换,其运算过程是相同的,不需要额外的硬件电路(原码取反+1得到补码,补码也可以取反+1得到原码)
2.2大小端介绍
大端【字节序】存储:
把一个数据的高位字节序的内容存放在低地址处,低位字节序的内容放在高地址处
小端【字节序】存储把一个数据的低位字节序的内容放在高地址,高位字节序的内容放在高地址处
char ch='w' 1个字节无顺序
像short int float 都有顺序
我们常用的x86结构是小端模式
#include<stdio.h>
int main()
{
int a = 1;
if (*(char*)&a == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
int check_sys()
{
int a = 1;
return *(char*)&a;
}
int main()
{
int ret=check_sys()
if(ret==1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
从00000000~11111111
有符号的char 取值范围为-128~127
无符号的char 取值范围为0~255
从0000000000000000~1111111111111111
signed short 取值范围为-32768~32767
unsigned short 取值范围为0~65535
char short类型默认整型提升
3.浮点型在内存中的存储
3.1415926
1E10 1.0*10(科学计数)
float ,double ,long double
取值范围在float.h中定义
3.1浮点数存储规则
(整数和浮点数在内存中的存储方式不一样,以整数形式存就得以整数形式输出,浮点数同理)
根据国际标准IEEE(电气和电子工程协会),任意一个二进制浮点数V可以表示成以下形式:
(-1)^S*M*2^E
(-1)^S表示符号位,当S=0,V为正数;当S=-1,V为负数
M表示有效数字,>=1,<2
2^E表示指数位
V=5.0f;----->101.0----->1.01*2^2[ 类比:123.45----->1.2345*10^2 ]
(-1)^0*1.01*2^2
S=0 M=1.01 E=2
V=9.5f
=1001.1
=1.0011*2^3
=(-1)^0*1.0011*2^3
S=0 M=1.0011 E=3
V=9.6f
=1001.100……001101001
无法精确保存
首先,如果E为8位,它的取值范围为0255;如果E为11位,它的取值范围为02047.但是我们知道科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023.比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001
再比如·:
V=0.5f
=0.1
=1.0*2^(-1)
=(-1)^0*1.0*2^(-1)
S=0 M=1.0 E=-1
float-E(真实值)+127(中间值)->126--存储
double-E(真实值)+1023(中间值)->1022--存储
指数E从内存中取出还可以分为三种情况
(1)E不全为0或不全为1
E的计算值减去127(1023),得到真实值,再将M前加上第一位的1
(2)E全为0
浮点数的指数E等于1-127或者1-1023(即为真实值)
M不再加上第一位的1,而是还原为0.xxxxx的小数。这样是为了表示+-0,以及无限接近0的数字
(3)E全为1
M为全0,表示+-无穷大(正负取决于符号位s)
二.数据存储_字符指针和指针数组
1.字符指针
char*
int main()
{
const char* p1 = "abcdef";
const char* p2 = "abcdef";
char arr1[] = "abcdef";
char arr2[] = "abcdef";
if (p1 == p2)
printf("p1==p2\n");
else
printf("p1!=p2\n");
if (arr1 == arr2)
printf("arr1==arr2\n");
else
printf("arr1!=arr2\n");
return 0;
}
2.指针数组
是用来存放指针的数组
int arr[10];//整型数组
char ch[5];//字符数组
//指针数组
int* arr2[6];//存放整型指针的数组
char* arr3[5];//存放字符指针的数组
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
int* parr[3] = { arr1,arr2,arr3 };
return 0;
}
模拟出了一个二维数组,但是其中三个元素不是连续存放的
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
int* parr[3] = { arr1,arr2,arr3 };
int i = 0;
for (i = 0;i < 3;i++)
{
int j = 0;
for (j = 0;j < 5;j++)
{
//*(p+i)-->p[i]
//printf("%d ", *(parr[i] + j));
printf("%d ", parr[i][j]);
}
printf("\n");
}
return 0;
}
三.指针进阶——数组指针
指向数组的指针 数组指针是用来存放数组的地址
类比:整型指针-指向整型的指针
int *p1[10] 指针数组
int arr[10]={0};
int (*p2)[10]=&arr; 数组指针p2可以指向一个数组,该数组有10个元素,每个元素是int类型
p2的类型是int(*)[10]
char* arr[5]={0};
char (*pc)[5]=&arr;
//数组名
不合理的用法
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int (*p)[10] = &arr;
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0;i < sz;i++)
{
printf("%d ", *(*p + i));//p是指向数组的,*p其实就相当于数组名,数组名又是数组首元素的地址
//所以*p本质上是数组首元素的地址
}
return 0;
合理用法
int* p=arr;
int i = 0;
for(i=0;i<10;i++)
{
printf("%d ",*(p+i));
}
正确用法
void print2(int(*p)[5], int r, int c)
{
int i = 0;
for (i = 0;i < r;i++)
{
int j = 0;
for (j = 0;j < c;j++)
{
printf("%d ", *(*(p + i) + j));//*(p + i)表示的是第i行的数组名(首元素地址),+j表示访问的是第i行第j列
//p的类型是int(*)[5]
//p是指向一个整型数组的,数组5个元素 int[5]
//p+1->跳过一个5个int元素的数组
}
printf("\n");
}
}
int main()
{
int arr[3][5]={1,2,3,4,5,2,3,4,5,6,3,4,5,6,7};
print2(arr, 3, 5);
//arr表示的是第一行的地址,第一行的地址是一个一维数组的地址,这里的第一行表示的是含有5个整型元素的一维数组 所以用int (*p)[5]
return 0;
}
四.指针进阶——数组指针的使用,数组参数,指针参数
int arr[5]; //arr是1整型数组
int *parr1[10];//parr1是整型指针数组
int (*parr2)[10];//parr2是数组指针
int (*parr3[10])[5];//parr3是存放数组指针的数组
1.数组参数
(1)一维数组传参
#include<stdio.h>
void test(int arr[])
{}
void test(int arr[10])
{}
void test(int* arr)
{}
void test2(int* arr[20])
{}
void test2(int* *arr)
{}
int maain()
{
int arr[10]={0};
int* arr2[20]={0};//表示的是一个存了20个int*类型指针的数组
test(arr);
test2(arr2);
}
(2)二维数组传参
void test(int arr[3][5]) 可行
{}
void test(int arr[][]) 不可行
{}
void test(int arr[][5]) 可行
{}
void test(int* arr) 不行
{}
void test(int* arr[5]) 不行
{}
void test(int (*arr)[5]) 可以
{}
void test(int* *arr) 不行
{}
int main()
{
int arr[3][5]={0};
test(arr);//数组名表示首元素的地址,就是第一行的地址
}
(3)一级指针传参
#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]);
print(p,sz);
return 0;
}
如果说函数的参数部分是指针
void print(int* p)
{}
int a = 10;
int* ptr=&a;
int arr[10];
print(&a);
print(ptr);
print(arr);
(4)二级指针传参
#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;
}
如果函数的形式参数是二级指针,调用函数的时候可以传什么实参呢?
test(int** p)
{}
int *p1;
int **p2;
int* arr[10];
test(&p1);
test(p2);
test(arr);
2.函数指针
指向函数的指针,存放函数地址
类比于数组指针
数组指针-指向数组的指针
int Add(int x, int y)
{
return x + y;
}
int main()
{
int arr[5] = { 0 };
int (*p)[5] = &arr;//数组指针
//&数组名-取出的数组的地址
printf("%p\n", &Add);
printf("%p\n",Add);
//对于函数来说
//&函数名和函数名-取出的都是函数的地址
return 0;
}
int (*pf)(int,int)=&Add;
int ret=(*pf)(2,3);//int ret=pf(2,3);//int ret=(*****pf)(2,3);
//int ret=Add(2,3);
printf("%d\n",ret);
函数可以通过函数指针调用另一个函数
int Add(int x, int y)
{
return x + y;
}
void calc(int(*pf)(int,int))
{
int a = 3;
int b = 5;
int ret = pf(a,b);
printf("%d\n",ret);
}
int main()
{
calc(Add);
return 0;
}
( *( void (*)() )0 )();
void(*)()函数指针类型
(void(*)()) 强制类型转换 将0从int变为函数
以上代码是一次函数调用,调用的是0作为地址处的函数
1.把0强制类型转换为:无参,返回类型是void的函数的地址
2.调用0地址处的这个函数
void(* signal(int,void(*)(int)))(int);
signal是函数名,以上代码是一次函数声明
声明的signal函数的第一个参数的类型是int,第二参数的类型是函数指针,该函数指针指向的函数参数是int,返回类型是void,signal函数的返回类型也是一个函数指针,该函数指针指向的函数参数是int,返回类型是void
int Add(int,int)
typedef unsigned int uint;//重命名
typrdef void(* pf_t)(int);
//把void(*)(int)类型重命名为pf_t
pf_t signal(int,pf_t);
函数指针的用途是函数调用
3.函数指针数组
int (*pf)(int,int )=Add;//pf是函数指针
int (*arr[4])(int,int)={Add,Sub,Mul,Div};//arr就是函数指针的数组
//指向函数指针数组的指针
int (*(*arr)[4])(int,int)=&arr;
4.回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应
5.冒泡排序
只能用于整型的数据
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
void bubble_sort(int arr[], int sz)
{
int i = 0;
//趟数
for (i = 0;i < sz - 1;i++)
{
int flag = 1;//假设数组是排好序的
//一趟冒泡排序的过程
int j = 0;
for (j = 0;j < sz - 1 - i;j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
//把数组排成升序
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
int i = 0;
for (i = 0;i < sz;i++)
{
printf("%d ", arr[i]);
}
return 0;
}
qsort 使用快速排序的思想实现的一个排序函数,可以排序任意类型的数据
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
void bubble_sort(int arr[], int sz)
{
int i = 0;
//趟数
for (i = 0;i < sz - 1;i++)
{
int flag = 1;//假设数组是排好序的
//一趟冒泡排序的过程
int j = 0;
for (j = 0;j < sz - 1 - i;j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
}
////void qsort(void* base,//需要排序的数据的起始位置
//size_t num,//待排序的数据元素的个数
//size_t width,//待排序的数据元素的大小(单位是字节)
//int(*cmp)(const void* e1, const void* e2)//函数指针-比较函数cmp地址,e1和e2是要进行比较的两个元素的地址
//__cdecl 函数调用约定
//不是所以的数据都可以用 > 进行比较
//比较2个整型元素
//e1指向一个整数
//e2指向另外一个整数
int cmp_int(const void* e1, const void* e2)//void* 不能直接进行解引用
//int main()
// {
// int a = 10;
// char* pa = &a;
// void* pv = &a;//void*是无具体类型的指针,可以介绍任意类型的指针
// //所以不能解引用,也不能+-整数
//
// }
{
/*if (*(int*)e1 > *(int*)e2)
return 1;
else if (*(int*)e1 = *(int*)e2))
return 0;
else
return -1;*/
return (*(int*)e1 - *(int*)e2);
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
//把数组排成升序
int sz = sizeof(arr) / sizeof(arr[0]);
//bubble_sort(arr, sz);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
int i = 0;
for (i = 0;i < sz;i++)
{
printf("%d ", arr[i]);
}
return 0;
}
将bubble_sort函数用类似于qsort的形式表示
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
Swap(char* buf1,char* buf2,int width)
{
int i = 0;
for (i = 0;i < width;i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void*base,int sz,int width,int(*cmp)(const void*e1,const void*e2))
{
int i = 0;
//趟数
for (i = 0;i < sz - 1;i++)
{
int flag = 1;//假设数组是排好序的
//一趟冒泡排序的过程
int j = 0;
for (j = 0;j < sz - 1 - i;j++)
{
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
//交换
Swap((char*)base+j*width,(char*)base+(j+1)*width,width);
flag = 0;
}
//if (arr[j] > arr[j + 1])
/*{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = 0;
}*/
}
if (flag == 1)
{
break;
}
}
}
////void qsort(void* base,//需要排序的数据的起始位置
//size_t num,//待排序的数据元素的个数
//size_t width,//待排序的数据元素的大小(单位是字节)
//int(*cmp)(const void* e1, const void* e2)//函数指针-比较函数cmp地址,e1和e2是要进行比较的两个元素的地址
//__cdecl 函数调用约定
//不是所以的数据都可以用 > 进行比较
//比较2个整型元素
//e1指向一个整数
//e2指向另外一个整数
struct Stu
{
char name[20];
int age;
};
//比较名字
int cmp_stu_by_name(const void* e1,const void* e2)
{
//strcmp--->>0 ==0 <0
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//比较年龄
int cmp_stu_by_age(const void* e1, const void* e2)
{
//strcmp--->>0 ==0 <0
return (((struct Stu*)e1)->age-((struct Stu*)e2)->age);
}
void test2()
{
//测试使用qsirt来排序结构数据
struct Stu s[] = { {"zhangsan",15},{"lisi",30},{"wamgwu",25} };
int sz = sizeof(s) / sizeof(s[0]);
//qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
int cmp_int(const void* e1, const void* e2)//void* 不能直接进行解引用
//int main()
// {
// int a = 10;
// char* pa = &a;
// void* pv = &a;//void*是无具体类型的指针,可以介绍任意类型的指针
// //所以不能解引用,也不能+-整数
//
// }
{
/*if (*(int*)e1 > *(int*)e2)
return 1;
else if (*(int*)e1 = *(int*)e2))
return 0;
else
return -1;*/
return (*(int*)e1 - *(int*)e2);
}
void test1()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
//把数组排成升序
int sz = sizeof(arr) / sizeof(arr[0]);
//bubble_sort(arr, sz);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
int i = 0;
for (i = 0;i < sz;i++)
{
printf("%d ", arr[i]);
}
}
void test3()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
//把数组排成升序
int sz = sizeof(arr) / sizeof(arr[0]);
//bubble_sort(arr, sz);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
int i = 0;
for (i = 0;i < sz;i++)
{
printf("%d ", arr[i]);
}
}
void test4()
{
//测试使用qsirt来排序结构数据
struct Stu s[] = { {"zhangsan",15},{"lisi",30},{"wamgwu",25} };
int sz = sizeof(s) / sizeof(s[0]);
//qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
int main()
{
test4();
return 0;
}