一、内存
内存==地址==指针
每个内存单元的大小就是一个字节
内存单元的编号就是地址,地址就是指针
二、指针变量和地址
%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;
}
如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。