数据存储和指针的进阶版

29 阅读15分钟

一.数据存储_数据类型,整型存储和大小端

#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

debug版本.png

debug的直观图.png

release(优化)

不是所有死循环都会优化

release版本.png

release版本的直观图.png

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
}

内存存储结果.png

在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理

同时,加法和减法也可以统一处理(cpu只有加法器)此外,补码与原码互相转换,其运算过程是相同的,不需要额外的硬件电路(原码取反+1得到补码,补码也可以取反+1得到原码)

原码和补码相加的区别.png

2.2大小端介绍

大端【字节序】存储:

把一个数据的高位字节序的内容存放在低地址处,低位字节序的内容放在高地址处

小端【字节序】存储把一个数据的低位字节序的内容放在高地址,高位字节序的内容放在高地址处

大小端直观图.png

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

截断与整型提升.png

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
        
        无法精确保存
        
        

浮点数在内存中的存储方式直观图.png

double类型在内存中存储方式的直观图.png

首先,如果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内存中取出来的2.3情况.png

二.数据存储_字符指针和指针数组

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;
    }

p1=p2 arr1!=arr2的情况.png

p1=p2 arr1!=arr2的直观图.png

2.指针数组

是用来存放指针的数组

int arr[10];//整型数组
char ch[5];//字符数组
//指针数组
int* arr2[6];//存放整型指针的数组
char* arr3[5];//存放字符指针的数组

指针数组存放直观图.png

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;
}

指针数组存放直观图2.png

模拟出了一个二维数组,但是其中三个元素不是连续存放的

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;

}

指针数组的结果.png

三.指针进阶——数组指针

指向数组的指针 数组指针是用来存放数组的地址

类比:整型指针-指向整型的指针

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;

}

数组指针的正确用法结果.png

二维数组直观图.png

四.指针进阶——数组指针的使用,数组参数,指针参数

int arr[5]; //arr是1整型数组
int *parr1[10];//parr1是整型指针数组
int (*parr2)[10];//parr2是数组指针
int (*parr3[10])[5];//parr3是存放数组指针的数组

parr3直观图.png

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;
}

&函数名的结果.png

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);

函数指针使用结果.png

函数可以通过函数指针调用另一个函数

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;
}

通过函数指针调用函数的结果.png

( *( void (*)() )0 )();
void(*)()函数指针类型
(void(*)())    强制类型转换  将0int变为函数
    以上代码是一次函数调用,调用的是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;
}

冒泡排序的结果1.png

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;
}

使用qsort函数进行升序的结果.png

将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;
}