第二节、指针的进阶
1.字符指针
在指针的类型中我们知道有一种指针类型为字符指针 char* ;
注意:
const char* pstr = "hello";
这里并不是把字符串 hello 放到字符指针 pstr 里了,本质是把字符串 hello 首字符的地址放到了 pstr 中。
#include <stdio.h>
int main() {
char str1[] = "hello";
char str2[] = "hello";
const char *str3 = "hello";
const char *str4 = "hello";
if(str1 ==str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if(str3 ==str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
//str1 and str2 are not same
//str3 and str4 are same
这里str3和str4指向的是一个同一个常量字符串。
C/C++会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。
所以str1和str2不同,str3和str4相同。
2.指针数组
指针数组是一个存放指针的数组。
int* arr1[10]; //整形指针的数组
char* arr2[10]; //一级字符指针的数组
char** arr3[10];//二级字符指针的数组
3.数组指针
3.1 定义
数组指针是指针,是能够指向数组的指针。
int (*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
3.2 “数组名” vs “&数组名”
int arr[10];
arr 和 &arr 是一样的东西吗?
举个例子:
#include <stdio.h>
int main() {
int arr[10] = { 0 };
printf("arr = %p\n", arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr= %p\n", &arr);
printf("&arr+1= %p\n", &arr+1);
return 0;
}
结果:
arr = 012FF6EC
arr+1 = 012FF6F0
&arr= 012FF6EC
&arr+1= 012FF714
从上面代码的结果可知:&arr和arr,有同样的数值,但本质上是不同的。
实际上:
arr是数组名,数组名表示数组首元素的地址。
&arr 表示的是数组的地址,而不是数组首元素的地址。
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.
4.函数指针
#include <stdio.h>
void test() {
printf("hello\n");
}
int main() {
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
结果:
003A1474
003A1474
输出了两个地址,这两个地址是 test 函数的地址。那么,如何保存函数的地址呢?
void test() {
printf("hehe\n");
}
void (*pfun)(); //pfun是指针,指向一个函数
补充两段有趣的代码:
//代码1
( *( void (*)() )0 )();
//把0强制转换成无参无返回值的函数指针类型,然后将0解引用后调用
//就是调用0地址处的函数
//代码2
void ( *signal( int , void(*)(int) ) )(int);
//signal是一个函数声明
//这个函数参数有2个,一个是int,另一个是函数指针,该指针指向的函数参数为int,返回类型是void
//signal函数的返回类型也是函数指针,该指针指向的函数参数int,返回类型是void
//简化版本
typedef void(* pfun_t)(int);
pfun_t signal(int,pfun_t);
5.函数指针数组
数组是一个存放相同类型数据的存储空间,指针数组是一个存放指针的数组。
那如果把函数的地址存到一个数组中,这个数组就叫函数指针数组。
int (*parr[10])();
函数指针数组的用途:转移表
实例:
使用函数指针数组实现计算器。
#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);
}else{
printf( "输入有误\n" );
}
printf( "ret = %d\n", ret);
}
return 0;
}
6.指向函数指针数组的指针
指向函数指针数组的指针是一个指针
指针指向一个数组 ,数组的元素都是函数指针
void test(const char* str){
printf("%s\n", str);
}
int main(){
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
return 0;
}
7.回调函数
回调函数就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
举个例子:
首先先演示一下qsort函数(快速排序)的使用:
qsot函数可以排序任意类型的数据
#include <stdio.h>
//qosrt函数的使用者须实现一个比较函数
//void*是一种无具体类型的指针
//void*的指针变量可以存放任意类型的地址
//void*的指针不能直接进行解引用、 +-*/ 操作
int int_cmp(const void * p1, const void * p2){
return (*( int *)p1 - *(int *) p2);
}
int main(){
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(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;
}
使用回调函数,模拟实现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;
}
8.指针和数组的笔试题
X86环境
(X86环境下地址大小为4,X64环境下地址大小为8)
//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a)); //16(数组名a单独放在sizeof内部,计算整个数组的大小)
printf("%d\n",sizeof(a+0)); //4 (a表示首元素的地址,a+0同样表示首元素的地址)
printf("%d\n",sizeof(*a)); //4 (*a表示对首元素地址的解引用)
printf("%d\n",sizeof(a+1)); //4 (a表示首元素的地址,a+1表示第二个元素的地址)
printf("%d\n",sizeof(a[1])); //4 (a[1]是数组的第二个元素)
printf("%d\n",sizeof(&a)); //4 (&a表示数组的地址)
printf("%d\n",sizeof(*&a)); //16(*&a表示对数组的地址的解引用)
printf("%d\n",sizeof(&a+1)); //4 (&a+1跳过整个数组后的地址)
printf("%d\n",sizeof(&a[0])); //4 (&a[0]表示数组第一个元素的地址)
printf("%d\n",sizeof(&a[0]+1)); //4 (&a[0]+1表示数组第二个元素的地址)
//字符数组
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr)); //6
printf("%d\n", sizeof(arr+0)); //4
printf("%d\n", sizeof(*arr)); //1
printf("%d\n", sizeof(arr[1])); //1
printf("%d\n", sizeof(&arr)); //4
printf("%d\n", sizeof(&arr+1)); //4
printf("%d\n", sizeof(&arr[0]+1)); //4
printf("%d\n", strlen(arr)); //随机值,因为末尾没有'\0'
printf("%d\n", strlen(arr+0)); //随机值,因为末尾没有'\0'
printf("%d\n", strlen(*arr)); //报错,strlen会把97('a'的ascii值)作为起始地址统计字符串,内存访问冲突
printf("%d\n", strlen(arr[1])); //报错,同上
printf("%d\n", strlen(&arr)); //报错,&arr是arr数组的地址,与形参参数 类型不符合
printf("%d\n", strlen(&arr+1)); //报错,同上
printf("%d\n", strlen(&arr[0]+1)); //随机值,因为末尾没有'\0'
//sizeof是操作符,任意类型均可
//sizeof获取了数据在内存中所占用的存储空间,以字节为单位来计数
//strlen是库函数,针对字符串
//strlen关注字符串中'\0'的位置,计算的是'\0'前出现了多少个字符
char arr[] = "abcdef";
printf("%d\n", sizeof(arr)); //7
printf("%d\n", sizeof(arr+0)); //4
printf("%d\n", sizeof(*arr)); //1
printf("%d\n", sizeof(arr[1])); //1
printf("%d\n", sizeof(&arr)); //4
printf("%d\n", sizeof(&arr+1)); //4
printf("%d\n", sizeof(&arr[0]+1)); //4
printf("%d\n", strlen(arr)); //6
printf("%d\n", strlen(arr+0)); //6
printf("%d\n", strlen(*arr)); //报错,strlen会把97('a'的ascii值)作为起始地址统计字符串,内存访问冲突
printf("%d\n", strlen(arr[1])); //报错,同上
printf("%d\n", strlen(&arr)); //报错,&arr是arr数组的地址,与形参参数 类型不符合
printf("%d\n", strlen(&arr+1)); //报错,同上
printf("%d\n", strlen(&arr[0]+1)); //5
char *p = "abcdef";
printf("%d\n", sizeof(p)); //4
printf("%d\n", sizeof(p+1)); //4
printf("%d\n", sizeof(*p)); //1
printf("%d\n", sizeof(p[0])); //1
printf("%d\n", sizeof(&p)); //4
printf("%d\n", sizeof(&p+1)); //4
printf("%d\n", sizeof(&p[0]+1)); //4
printf("%d\n", strlen(p)); //6
printf("%d\n", strlen(p+1)); //5
printf("%d\n", strlen(*p)); //报错
printf("%d\n", strlen(p[0])); //报错
printf("%d\n", strlen(&p)); //报错
printf("%d\n", strlen(&p+1)); //报错
printf("%d\n", strlen(&p[0]+1)); //5
//二维数组
int a[3][4] = {0};
printf("%d\n",sizeof(a)); //48
printf("%d\n",sizeof(a[0][0])); //4
printf("%d\n",sizeof(a[0])); //16
printf("%d\n",sizeof(a[0]+1)); //4
printf("%d\n",sizeof(*(a[0]+1))); //4
printf("%d\n",sizeof(a+1)); //4
printf("%d\n",sizeof(*(a+1))); //16
printf("%d\n",sizeof(&a[0]+1)); //4
printf("%d\n",sizeof(*(&a[0]+1))); //16
printf("%d\n",sizeof(*a)); //16
printf("%d\n",sizeof(a[3])); //16
总结:
数组名的意义:
- sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
- &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
- 除此之外所有的数组名都表示首元素的地址。
9.指针笔试题
笔试题1
int main(){
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
//2,5
笔试题2
struct Test{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
int main(){
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
//00000014
//00000001
//00000004
笔试题3
int main(){
int a[4] = { 1, 2, 3, 4 };
int *ptr1 = (int *)(&a + 1);
int *ptr2 = (int *)((int)a + 1);
printf( "%x,%x", ptr1[-1], *ptr2);
return 0;
}
//4,2000000
笔试题4
int main(){
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf("%d", p[0]);
return 0;
}
//1
笔试题5
int main(){
int a[5][5];
int(*p)[4];
p = a;
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
//FFFFFFFC,-4
笔试题6
int main(){
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int *ptr1 = (int *)(&aa + 1);
int *ptr2 = (int *)(*(aa + 1));
printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
//10,5
笔试题7
int main(){
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
//at
笔试题8
int main(){
char *c[] = {"ENTER","NEW","POINT","FIRST"};
char**cp[] = {c+3,c+2,c+1,c};
char***cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1]+1);
return 0;
}
//POINT
//ER
//ST
//EW
10.附录(qsort的使用)
#include<string.h>
#include<stdio.h>
#include<stdlib.h>//qsort的头文件
struct Student{
int id;
char name[50];
};
int int_cmp(const void* a, const void* b){
return *(int*)a - *(int*)b;
}
int char_cmp(const void* a, const void* b){
return *(char*)a - *(char*)b;
}
int double_cmp(const void* a, const void* b){
return *(double*)a > *(double*)b ? 1 : -1;
}
int struct_cmp(const void* a, const void* b) {
return ((struct Student*)a)->id - ((struct Student*)b)->id;
}
int str_cmp(const void* a, const void* b) {
return strcmp(*(char**)a, *(char**)b);
}
int main() {
//int排序
int num[5] = { 1,3,9,5,2 };
qsort(num, sizeof(num) / sizeof(num[0]), sizeof(num[0]), int_cmp);
for (int i = 0; i < sizeof(num) / sizeof(num[0]); i++) {
printf("%d ", num[i]);
}
printf("\n");
//char排序
char word[5] = "CADB";
qsort(word, sizeof(word) / sizeof(word[0]), sizeof(word[0]), char_cmp);
for (int i = 0; i < sizeof(word) / sizeof(word[0]); i++) {
printf("%c ", word[i]);
}
printf("\n");
//double排序
double in[5] = { 1.23,3.14159,2.6,5.74,4.11 };
qsort(in, sizeof(in) / sizeof(in[0]), sizeof(in[0]), double_cmp);
for (int i = 0; i < sizeof(in) / sizeof(in[0]); i++) {
printf("%lf ", in[i]);
}
printf("\n");
//struct排序
struct Student stu[3] = { {2,"小明"},{1,"小李"},{3,"小红"} };
qsort(stu, sizeof(stu) / sizeof(stu[0]), sizeof(stu[0]), struct_cmp);
for (int i = 0; i < sizeof(stu) / sizeof(stu[0]); i++) {
printf("%d%s ", stu[i].id,stu[i].name);
}
printf("\n");
//string排序
char *str[5] = { "AAA","EEE","CCC","BBB","DDD" };
qsort(str, sizeof(str) / sizeof(str[0]), sizeof(str[0]), str_cmp);
for (int i = 0; i < sizeof(str) / sizeof(str[0]); i++) {
printf("%s ", str[i]);
}
printf("\n");
return 0;
}