C语言指针

一、内存

内存==地址==指针

每个内存单元的大小就是一个字节

内存单元的编号就是地址,地址就是指针

二、指针变量和地址

%p是用来专门打印地址的

int a = 10;
int *pa=&a;

pa左边写的是int*,*是在说明pa是指针变量,而前面的int是在说明pa指向的是整形(int)类型的对象

2.1解引用

下一步

若改成char*类型仔细观察

总结:指针的类型决定了,对指针解引用的权限(int*可以开4个字节,char*只能开1个字节)

2.2指针+-整数

指针类型决定了指针向前或者向后走一步多大

2.3指针变量大小

指针变量的大小和类型无关,只要指针类型的变量,在相同的平台下,大小相同

32位平台,指针变量大小是4个字节

64位平台,指针变量大小是8个字节

2.4指针-指针

前提:两个指针指向同一块空间,才能得到指针和指针之间的元素个数

三、指针的使用和传址调用

为什么在Swap1中没有进行调换

Swap2中因为传过去的是地址则a和b的地址发生了改变,在Swap2中a和b发生了交换。

总结:swap函数中是形参,临时变量,当函数退出时,临时变量销毁,相反传指针,改变的是main中的变量值。

四、野指针

指针未初始化

指针越界访问

指针指向的空间释放

int* test()
{
	int a = 10;
	//...
	return &a;
}
int main()
{
	int*p = test();

	//printf("hehe\n");//为什么这里加了一句代码,*p的值就变了

	printf("%d\n", *p);

	return 0;
}

如果不使用或者不知道写什么地址用NULL

五、const修饰指针变量

void test()
{
	int n = 10;
	int m = 30;
	int* p = &n;
	*p = 20;
	p = &m;
}
void test1()
{
	int n = 10;
	int m = 200;
	const int* p = &n;
	//*p=200;不能这么写
	p = &m;//但是p可以改,p是指针变量可以通过p进而改变*p的值
	printf("%d", *p);
	//const在*的左边,限制的是*p,不能通过指针变量p修改p指向空间的内容,但是p是不受限制的
}
void test2()
{
	int n = 10;
	int m = 300;
	int* const p = &n;
	*p = 100;//一开始*p=10,但是*p=100把n的值也改变了,n=100,但是n的地址没有改变。
	//p = &m;不能这么写
	printf("%p", p);
	//const放在*的右边,修饰的指针变量本身,保证了指针变量的内容不能修改,但指针指向的内容可以修改
}
test3()
{
	int n = 10;
	int m = 20;
	int const* const p = &n;
	//*p=300;不能这么写
	//p=&m;不能这么写
}
int main()
{
	test();
	test1();
	test2();
	test3();
	return 0;
}

const可以两边都加,p和*p都被限制了

六、数组与指针

数组名(arr)就是首元素地址(特例:sizeof(数组名)计算的是整个数组的大小,&arr取出的是整个数组的大小)

int arr[ ]={1,2};

指针怎么用(解引用,指针+-整数等)数组名就可以怎么用

6.1、arr和&arr

&arr和arr==&arr[0],指向的都是首元素地址,但是&arr和arr的跨步不一样

例子

int arr[ 10 ] = { 0 } ;

&arr+1是跨了40个字节,arr+1是跨了4个字节

6.2、数组传参的本质

注意是这里传过去的是数组首元素地址,并不是数组

建议,以后这么传数组首元素地址

七、指针冒泡排序

//冒泡排序
void bubble_sort(int* arr, int sz)
{
	int i = 0;//行
	for (i = 0; i < sz-1; i++)//sz-1只需要9趟例如9 8 7 6 5 4 3 2 1最后的0就不需要排序
	{
		int j = 0;//列
		for (j = 0; j < sz - i-1; j++)//sz-1-i,为什么sz要减1,是因为下面j+1,如果不减1,栈溢出
		{
			
			if ((*(arr + j)) >( *(arr + j+1)))
			{
				int t = *(arr + j+1);
				*(arr + j+1) = *(arr + j);
				*(arr + j) = t;
			}
		}
	}
}
int main()
{
	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i=0; i < sz; i++)
	{
		scanf("%d", arr + i);
	}
	printf("\n");
	bubble_sort(arr, sz);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

八、二级指针

什么是二级指针?

存放一级指针变量的地址

int ** pp=&p

int * *是ppa的类型

int *说明pp指向的对象类型int *

*说明ppa是指针变量

九、指针数组

什么是指针数组

存放指针的数组

9.1指针数组模拟二维数组

int main()
{
    int arr1[] = { 1,2,3,4,5 };
    int arr2[] = { 2,3,4,5,6 };
    int arr3[] = { 3,4,5,6,7 };

    int* arr[3] = {arr1, arr2, arr3};//整型指针数组

    int i = 0;

    for (i = 0; i < 3; i++)
    {
        int j = 0;
        for (j = 0; j < 5; j++)
        {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }

    return 0;
}

十、字符指针变量

字符指针变量有一点像数组---可以类比

例如

char*p=‘abcd’;

这个数组是不能修改的也就是  p[3]='9'

printf("%c","abcd"[3]);

这个“abcd”[3]也可以是p[3]

十一、数组指针变量

数组指针变量应该是:存放发应该是数组的地址,能够指向数组的指针变量

int(*p)[10]

p是指针,指针指向的是数组,数组有10个元素,每个元素的类型是int

11.1、二维数组传参的本质

二维数组的数组名表示的就是第一行的地址

二维数组传参的本质上就是传递了地址,传递的是第一行这个一维数组的地址

仔细看下面的代码

int(*p)[2]是什么意思

我们已知二维数组的数组名是一维数组的地址,传的是数组地址就要用数组指针int(*)[2]。int [2]是一位数组的类型,所以第一行的地址类型就是数组指针类型int(*)[2]

void test(int(*p)[2], int a, int b)
{
    int i = 0;
    int j = 0;
    for (i = 0; i < a; i++)
    {
        for (j = 0; j < b; j++)
        {
            printf("%d", *(*(p + i) + j));
        }
        printf("\n");
    }
}
int main()
{
    int arr[2][2] = { {1,2},{3,4} };
    test(arr, 2, 2);

    return 0;
}

十二、函数指针变量

函数指针变量就是---存放函数地址的变量

void menu()
{
	printf("******************************\n");
	printf("****  1. add     2. sub   ****\n");
	printf("****  3. mul     4. div   ****\n");
	printf("****  0. exit             ****\n");
	printf("******************************\n");
}

int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}


int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	int(*p[5])(int, int) = {0,Add,Sub,Mul,Div};//p先和[]结合说明p是数组*说明是指针数组而函数指针变量是int(*)()
	do
	{
		menu();
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("输入两个数:");
			scanf("%d %d", &x, &y);
			ret = (*p[input])(x, y);//---------(*p[input])(x,y)-------*解引用p[input]
			printf("%d\n",ret);
		}
		else if (input == 0)
		{
			printf("退出");
		}
		else
		{
			printf("输入有误");
		}
	} while (input);
	
	return 0;
}

如何理解上面的int(*p[5])(int,int)---说明p是指针数组,存放了5个函数地址,这5个函数的类型是int (int,int)

12.1  typedef

typedef是用来类型重新命名,将复杂变简单

如 

unsigned int写的不方便,但是可以这样

typedef unsigned int a;将unsigned int重新命名a

数组指针和函数指针有点区别

例如:数组指针类型是 int(*)[5]

typedef int(*a)[5]-----------区别在于新的类型名必须在*的右边

函数指针和数组指针一样.

十三、qsort函数

void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*));

13.1 qsort的模拟使用

数组

int cmp(const void*p1,const void*p2)
{
    return (*(int*)p1) - (*(int*)p2);
}
int main()
{
    int arr[] = { 1,2,3,4,5,9,8,7,6 };
    int i = 0;
    qsort(arr, sizeof(arr) / sizeof(arr[0]),sizeof(int),cmp);
    for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

结构体

struct Stu
{
    char name[20];
    int age;
};

//年龄
int cmp(const void* p1, const void* p2)
{
    return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
void test()
{
    struct  Stu s[] = { {"zhangsan",20},{"lisi",30},{"wangwu",15} };
    int sz = sizeof(s) / sizeof(s[0]);
    qsort(s, sz, sizeof(s[0]), cmp);
    for (int i = 0; i < sz; i++)
    {
        printf("Name: %s, Age: %d\n", s[i].name, s[i].age);
    }
   
}
int main()
{
    test();
    return 0;
}
struct Stu
{
    char name[20];
    int age;
};

//姓名
int cmp(const void* p1, const void* p2)
{
    return strcmp(((struct Stu*)p1)->name - ((struct Stu*)p2)->name);
}
void test()
{
    struct  Stu s[] = { {"zhangsan",20},{"lisi",30},{"wangwu",15} };
    int sz = sizeof(s) / sizeof(s[0]);
    qsort(s, sz, sizeof(s[0]), cmp);
}
int main()
{
    test();
    return 0;
}

13.2qsort模拟实现

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 num, int size, int (*cmp)(const void*, const void*))//因为不知道比较的类型是什么所以要用void*另外传递的指针不能改变所以要用const
{
    int i = 0;
    int j = 0;
    for (i = 0; i < num - 1; i++)
    {
        for (j = 0; j < num - 1 - i; 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[] = { 2,3,1,5,6,8,9,7 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    bubble(arr, sz, sizeof(arr[0]), cmp);//cmp--回调函数就是一个参数,将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数

    return 0;
}

如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。

  • 23
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值