【指针进阶:C语言进阶(Day2)】

字符指针

对于一般字符指针的使用方式如下:

int main()
{
    char ch = 'w';
    char *pc = &ch;
    *pc = 'h';
    return 0;
 }

还有一种使用方式如下:

        当出现把字符串赋值给指针变量的这种形式时,实际上是把字符串首个字符的地址赋给指针变量,千万不要认为指针变量里放的是字符串!!!

来看一道相关的面试题:

#include <stdio.h>
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    const char *str3 = "hello bit.";
    const char *str4 = "hello bit.";
    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; }

运行结果如下:

原因:

        这里 str3 str4 指向的是同一个常量字符串。 C/C++ 会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1 str2 不同, str3 str4 不同。

数组指针

指针数组是一个存放指针的数组。
int* arr1 [ 10 ]; // 整形指针的数组
char * arr2 [ 4 ]; // 一级字符指针的数组
char ** arr3 [ 5 ]; // 二级字符指针的数组

指针数组

1、数组指针的定义

数组指针是指针?还是数组?
答案是:指针。
我们已经熟悉:
整形指针: int * pint ; 能够指向整形数据的指针。
浮点型指针: float * pf ; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。
数组指针通常怎么定义呢?下面哪一个是数组指针?
int * p1 [ 10 ];
int ( * p2 )[ 10 ];
//p1, p2 分别是什么?
int ( * p2 )[ 10 ];
// 解释: p 先和 * 结合,说明 p 是一个指针变量,然后指着指向的是一个大小为 10 个整型的数组。所以 p 是一个
指针,指向一个数组,叫数组指针。
这个数组指针的名称是p2,类型是int (*)[10]

2、&数组名VS数组名

我们来看一段代码:

 打印结果是一样的,二者难道是一样的吗?我们再来看一段代码:

根据上面的代码我们发现,其实 &arr arr ,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是 数组的地址 ,而arr数组名表示的是数组首元素的地址。(细细体会一下)
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址 +1 ,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是 40。
具体的差别其实在C语言初阶数组部分“数组名是什么”就有详细介绍过,可以通过以下链接阅读参考:

3、数组指针的使用

既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
 //注意:定义数组指针的时候要把指向数组的元素个数写清楚,不可以写成int (*p)[]
    return 0;
 }

//我们一般很少这样使用数组指针

我们来看一个数组指针的使用实例:

 分析:数组名arr,表示首元素的地址,但是二维数组的首元素是二维数组的第一行,所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址,这个时候除了直接用二维数组来接收,还可以数组指针来接收。

再来看一种变形:

分析: p的类型是数组指针,存放的是二维数组第一行元素的地址,它+1跳过的是整个一维数组,+i得到的是每行一维数组的地址,对p+i解引用得到每个一维数组的数组名,数组名又表示一维数组首元素的地址,+j得到的是每个一维数组的每一个元素的地址,对其整体解引用就能访问到二维数组的每个元素

学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:
int arr [ 5 ];//整形数组
int * parr1 [ 10 ];//指针数组
int ( * parr2 )[ 10 ];//数组指针
int ( * parr3 [ 10 ])[ 5 ];//?parr3先和[10]结合,说明这是一个有10个元素额数组,那么剩下的部分int(*)[5]应该就是数组中存放的元素的类型,由此,这应该是一个存放数组指针变量的数组

4、数组参数、指针参数

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?下面我们根据已学探讨这个问题。

一维数组传参:

#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 main()
{
 int arr[10] = {0};//整形数组
 int *arr2[20] = {0};//指针数组
 test(arr);//传过去的的是首元素地址,一个整形的地址
 test2(arr2);//传过去的是首元素的地址,一个指针的地址
}

二维数组传参

void test(int arr[3][5])//ok
{}
void test(int arr[][])//NO
{}
void test(int arr[][5])//ok
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//NO,这个是整形数组
{}
void test(int* arr[5])//NO
{}
void test(int (*arr)[5])//ok,数组指针来接收实参
{}
void test(int **arr)//NO,传来的参数不是指针的地址,是一个数组的地址
{}
int main()
{
 int arr[3][5] = {0};
 test(arr);//传过去的是第一行的地址,是数组指针
}

一级指针传参:

#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]);
 //一级指针p,传给函数
 print(p, sz);
 return 0;
 }
即当一个函数的参数部分为一级指针的时候,既能接收一级指针变量,又可以接收一维数组数组名。

二级指针传参:

void test(char **p)
{
 
}
int main()
{
 char c = 'b';
 char*pc = &c;
 char**ppc = &pc;
 char* arr[10];
 test(&pc);
 test(ppc);
 test(arr);//
 return 0; 
}

即当一个函数的参数部分为二级指针的时候,能接收二级指针变量和一维指针数组数组名。

函数指针

先来看一段代码:

输出的是两个地址,这两个地址都能表示  test 函数的地址。
那我们的函数的地址要想保存起来,怎么保存?
下面我们看代码:
void test ()
{
printf ( "hehe\n" );
}
// 下面 pfun1 pfun2 哪个有能力存放 test 函数的地址?
void ( * pfun1 )();
void * pfun2 ();
首先,能给存储地址,就要求 pfun1 或者 pfun2 是指针,那哪个是指针?
答案是:pfun1 可以存放。 pfun1 先和 * 结合,说明 pfun1 是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void

 有了这样一个概念,我们来分析两段有趣的代码:

// 代码 1
( * ( void ( * )()) 0 )();
// 代码 2
void ( * signal ( int , void ( * )( int )))( int );
代码一分析:首先我们要看到的是操作数0,对于0是怎么操作的呢?首先它和前面的大括号结合,表示的是强制类型转换,大括号里的类型是函数指针,再就是对这一函数指针解引用得到的是函数名,然后进行函数调用。
1、把0强制转化为无参,返回值类型为void的函数的地址
2、调用0地址处的这个函数
代码二分析:首先锁定的应该是 signal ( int , void ( * )( int )),signal应该是一个函数,它有两个参数,一个参数类型是整形,另一个参数类型是,参数为整形返回值类型我void 的函数,signal这个函数的返回值类型也是一个参数为int,返回值类型为void的函数指针。
代码 2 太复杂,和我们一般的理解不同,如何简化:
typedef void ( * pfun_t )( int );
pfun_t signal ( int , pfun_t );

函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组,
比如:
int * arr [ 10 ];
// 数组的每个元素是 int*
那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
int ( * parr1 [ 10 ])();
int * parr2 [ 10 ]();
int ( * )() parr3 [ 10 ];
答案是: parr1
parr1 先和 [] 结合,说明 parr1 是数组,数组的内容是什么呢?是 int (*)() 类型的函数指针。

函数指针数组的用途:转移表

例题:计算器

我们用一般的方法来实现一个只有加减乘除功能的计算器

#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;
    do
   {
        printf( "*************************\n" );
        printf( " 1:add           2:sub \n" );
        printf( " 3:mul           4:div \n" );
        printf( "*************************\n" );
        printf( "请选择:" );
        scanf( "%d", &input);
        switch (input)
       {
       case 1:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = add(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 2:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = sub(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 3:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = mul(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 4:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = div(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 0:
              printf("退出程序\n");
              breark;
        default:
              printf( "选择错误\n" );
              break;
       }
 } while (input);
    
    return 0;
 }

看到上面的代码,我们发现,其实代码存在一定程度的冗余,比如输入两个操作数的语句,每个功能内部都出现了一次,实际上,我们输入两个操作数以及把参数传给相应函数的操作都是相同的,那我们如何利用函数指针数组来简化代码。如下:

#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);//直接写p[input](x,y)也可以,因为函数名本身就能表示函数地址,这里把函数指针直接当函数名使用也是没有问题的,这里其实就是在调用函数
           printf( "ret = %d\n", ret);
         }
          else
               printf( "输入有误\n" );
          
     }
      return 0; 
}

指向函数指针数组的指针

指向函数指针数组的指针是一个 指针
指针指向一个 数组 ,数组的元素都是 函数指针 ;
如何定义?
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;
 }

回调函数

   回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应
我们首先通过一个函数 qsort ()的使用来理解:
排序方法有很多种:选择排序,冒泡排序,归并排序,快速排序等。快速排序是目前公认的一种比较好的排序算法。其速度很快,所以系统也在库里实现这个算法的函数,便于我们的使用。 这就是qsort函数(全称quicksort)。它是ANSI C标准中提供的,其声明在stdlib.h文件中。
 
功能:使用快速排序算法对给定数据类型进行排序
头文件: stdlib.h
用法:void qsort(void* base,size_t num,size_t width,int(__cdecl*compare)(const void*,const void*));
参数:
                  1 待排序数组,排序之后的结果仍放在这个数组中
      2 数组中待排序元素数量
        3 各元素的占用空间大小(单位为字节)
             4 指向函数的指针,用于确定排序的顺序(需要用户自定义一个比较函数)
  • qsort要求提供一个自己定义的比较函数。比较函数使得qsort通用性更好,有了比较函数qsort可以实现对数组、字符串、结构体等结构进行升序或降序排序。
  • 比较函数 int cmp(const void *a, const void *b) 中有两个元素作为参数(参数的格式不能变),返回一个int值,比较函数cmp的作用就是给qsort指明元素的大小是怎么比较的。
利用qsort()对一组整型数据进行排序:
#include <stdio.h>
#include<stdlib.h>
//qosrt函数的使用者得实现一个比较函数
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; 
}
//注意一点:
int main()
{
	int a = 10;
	void* pv = &a;//void*是无具体类型的指针,可以接受任意类型的地址
	//void*是无具体类型的指针,所以不能解引用操作,也不能+-整数
	
return 0;
}

我们之前学过对一组整型数据进行冒泡排序,但是如果我们要用比较其它类型的数据,这套代码就不能用了,那我们可不可以把冒泡比较的思想作为一种比较的思想,来写一个类似有qsort功能的函数,答案当然是可以的。

以下附上排序整型和结构体的完整代码,详解见以下博客

(13条消息) 【你还只会用冒泡排序的思想排整形数据吗?快来解锁它的隐藏用法!】_清蒸佩奇553的博客-CSDN博客

void swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		buf1++;
		buf2++;
	}
}
void bubble_qsort(void* base, int sz, int width, int (*cmp)(const void* e1, const void* e2))
{
	int i = 0;
	int j = 0;
	int flag = 1;
	for (i = 1; i < sz; i++)
	{
		for (j = 0; j < sz - 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 (flag == 1)
			break;
	}
}
int cmp_int(const void* e1, const void* e2)
{
	return (*(int*)e1 - *(int*)e2);
}
void test1()
{
	int arr[] = { 12,5,2,8,56,47,33,19 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_qsort(arr, sz, sizeof(arr[0]), cmp_int);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
#include<string.h>//strcmp的头文件
struct stu
{
	char name[20];
	int age;
};
int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);//strcmp用于比较两字符串大小,恰好其返回值就是>0,==0和<0
}
int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct stu*)e1)->age - ((struct stu*)e2)->age;//年龄是整数,直接按照整数的方式返回
}
void test2()
{
	struct stu s[] = { {"zhangsan",12},{"lisi",9},{"wangwu",18} };//这是一个结构体数组
	int sz = sizeof(s) / sizeof(s[0]);
	bubble_qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s,%d\n", s[i].name, s[i].age);
	}
	printf("---------------------\n");
	bubble_qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
	int j = 0;
	for (j = 0; j < sz; j++)
	{
		printf("%s,%d\n", s[j].name, s[j].age);
	}
}
int main()
{
	test1();//比较整型
	test2();//比较结构体
	return 0;
}

指针和数组相关笔试题

一、一维数组

int main()
{
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));//16
	//sizeof()内单独放数组名,a表示整个数组,这个数组有四个整型,因此是16
	printf("%d\n", sizeof(a + 0));// 4/8
	//sizeof()内不是单独放数组名,a表示数组内首元素地址,加减整数后都是地址,因此是4/8
	printf("%d\n", sizeof(*a));//4
	//sizeof()内不是单独放数组名,a表示数组内首元素地址,对其解引用,得到的是数组内的第一个元素,是整型,因此是4
	printf("%d\n", sizeof(a + 1));// 4/8
	//sizeof()内不是单独放数组名,a表示数组内首元素地址,加减整数后都是地址,因此是4/8
	printf("%d\n", sizeof(a[1]));//4
	//a[1]表示数组内的第二个元素,是整型,因此是4
	printf("%d\n", sizeof(&a));// 4/8
	//&a表示取出整个整个数组的地址,因此是4/8
	printf("%d\n", sizeof(*&a));//16
	//&a表示取出整个整个数组的地址,解引用得到的是整个数组,这个数组有四个整型,因此是16
	printf("%d\n", sizeof(&a + 1));// 4/8
	//&a表示取出整个整个数组的地址,+1跳过整个数组,但仍表示地址,因此是4/8
	printf("%d\n", sizeof(&a[0]));// 4/8
	//&a[0]表示数组首元素的地址,因此是4/8
	printf("%d\n", sizeof(&a[0] + 1));// 4/8
	//&a[0]表示数组首元素的地址,+1跳过数组中一个元素,这里表示数组中第二个元素的地址,因此是4/8
	return 0;
}

二、字符数组(1)

int main()
{
	char arr[] = { 'a','b','c','d','e','f'};
	printf("%d\n", sizeof(arr));//6
	//sizeof()内单独放数组名,a表示整个数组,这个数组有6个字符型,因此是6
	printf("%d\n", sizeof(arr + 0));// 4/8
	//sizeof()内不是单独放数组名,a表示数组内首元素地址,加减整数后都是地址,因此是4/8
	printf("%d\n", sizeof(*arr));//1
	//sizeof()内不是单独放数组名,a表示数组内首元素地址,对其解引用,得到的是数组内的第一个元素,是字符型,因此是1
	printf("%d\n", sizeof(arr[1]));//1
	//arr[1]表示数组中的第二个元素,因为是字符型,因此是1
	printf("%d\n", sizeof(&arr));// 4/8
	//&a表示取出整个整个数组的地址,因此是4/8
	printf("%d\n", sizeof(&arr + 1));// 4/8
	//&a表示取出整个整个数组的地址,+1跳过整个数组,但仍表示地址,因此是4/8
	printf("%d\n", sizeof(&arr[0] + 1));// 4/8
	//&a[0]表示数组首元素的地址,+1跳过数组中一个元素,这里表示数组中第二个元素的地址,因此是4/8
	printf("%d\n", strlen(arr));//19,随机值
	//strlen()函数接受的参数要是一个字符指针,这里传过去数组名即首元素地址,strlen向后读取时,遇到\0才会停止,我们不知道\0在哪里,因此是随机值
	printf("%d\n", strlen(arr + 0));//19,随机值
	//这里arr + 0也表示首元素地址,因此同上,是随机值
	printf("%d\n", strlen(*arr));//参数不是地址,因此此代码是错误的
	printf("%d\n", strlen(arr[1]));//参数不是地址,因此此代码是错误的
	printf("%d\n", strlen(&arr));//19
	//&arr表示整个数组的地址,数值上等于数组首元素地址,同上所述,也是随机值。
	printf("%d\n", strlen(&arr + 1));//13,随机值-6
	//&arr表示整个数组的地址,+1跳过整个数组,此时指向下个数组,向后读取同样要找到\0才停止,因此是随机值-数组长度
	printf("%d\n", strlen(&arr[0] + 1));//18,随机值-1
	//&arr[0]表示数组首元素地址,+1向后跳一个元素,指向数组中第二个元素,向后读取遇到\0停止,因此是随机值-1个元素长度
	return 0;
}

 字符数组(2)

int main()
{
	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr));//7
	printf("%d\n", sizeof(arr + 0));// 4/8
	printf("%d\n", sizeof(*arr));//1
	printf("%d\n", sizeof(arr[1]));//1
	printf("%d\n", sizeof(&arr));// 4/8
	printf("%d\n", sizeof(&arr + 1));// 4/8
	printf("%d\n", sizeof(&arr[0] + 1));// 4/8
	printf("%d\n", strlen(arr));//6
	printf("%d\n", strlen(arr + 0));//6
	//printf("%d\n", strlen(*arr));//参数不是地址,因此此代码是错误的
	//printf("%d\n", strlen(arr[1]));//参数不是地址,因此此代码是错误的
	printf("%d\n", strlen(&arr));//6
	printf("%d\n", strlen(&arr + 1));//12,随机值
	printf("%d\n", strlen(&arr[0] + 1));//5
	return 0;
}
//分析方式同上,需要注意的是:字符串的结尾默认有\0,sizeof()计算字符串
//的长度时包括\0,而strlen()计算的则是\0之前的内容,不包括\0.

 三、字符指针

int main()
{
	char* p = "abcdef";//这里p中存放的是字符串第一个字符的地址,即‘a’的地址
	printf("%d\n", sizeof(p));// 4/8
 //p是指针,因此是4/8
	printf("%d\n", sizeof(p + 1));// 4/8
 //p是a的地址,+1跳过一个字符,是指向‘b’的指针,因此大小是4/8
	printf("%d\n", sizeof(*p));//1
 //对p解引用得到的是字符‘a’,是字符型,因此是1
	printf("%d\n", sizeof(p[0]));//1
 //p[0]<=>*(p+0)<=>*p,因此是1
	printf("%d\n", sizeof(&p));// 4/8
 //对指针取地址是二级指针,二级指针也是地址,因此大小为4/8
	printf("%d\n", sizeof(&p + 1));// 4/8
 //指针+1还是指针,因此是4/8
	printf("%d\n", sizeof(&p[0] + 1));// 4/8
 //p[0]<=>*(p+0)<=>*p,*与&抵消,因此与 sizeof(p + 1))一样,是4/8
	printf("%d\n", strlen(p));//6
 //p是指向‘a’的指针,从a开始向后读取直至遇到\0,因此是6
	printf("%d\n", strlen(p + 1));//5
 // //p是a的地址,+1跳过一个字符,是指向‘b’的指针,向后读取直至遇到\0,因此是5
	//printf("%d\n", strlen(*p));//参数不是地址,因此此代码是错误的
	//printf("%d\n", strlen(p[0]));//参数不是地址,因此此代码是错误的
	printf("%d\n", strlen(&p));//3,随机值
	printf("%d\n", strlen(&p + 1));//11,随机值
	printf("%d\n", strlen(&p[0] + 1));//5
 //p[0]<=>*(p+0)<=>*p,因此与 strlen(p + 1))一样,因此是5
	return 0;
}

四、二维数组

int main()
{
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));//48
 //sizeof()内单独放数组名,a表示整个数组,这个二维数组有12个整型,因此是48
	printf("%d\n", sizeof(a[0][0]));//4
 //a[0][0]表示二维数组第一行第一列的元素,是整型,因此是4
	printf("%d\n", sizeof(a[0]));//16
 //a[0]相当于二维数组第一行的数组名,因此,表示整个一维数组的大小,一行有4个整型,因此是16
	printf("%d\n", sizeof(a[0] + 1));// 4/8
 //sizeof()内不是只有数组名,因此a[0]表示第一行一维数组的首元素地址,+1跳过一个元素,因此是指向第二个元素的指针,因此是4/8
	printf("%d\n", sizeof(*(a[0] + 1)));//4
 //结合上一行可知是对指向第二个元素的指针解引用,得到的是一个整型,因此是4
	printf("%d\n", sizeof(a + 1));// 4/8
 //sizeof()内不是只有数组名,a表示二维数组首元素地址,即第一行一维数组的地址,+1跳过一个一维数组,是指向第二行一维数组的指针,因此是4/8
	printf("%d\n", sizeof(*(a + 1)));//16
 //结合上一行代码,可知对指向第二行一维数组的指针解引用,得到一维数组,有四个整型,因此是16
	printf("%d\n", sizeof(&a[0] + 1));// 4/8
 //&a[0]表示取出第一行一维数组的地址,+1跳过一个一维数组,是指向第二行一维数组的指针,因此是4/8
	printf("%d\n", sizeof(*(&a[0] + 1)));//16
 //结合上一行代码,可知对指向第二行一维数组的指针解引用,得到一维数组,有四个整型,因此是16
	printf("%d\n", sizeof(*a));//16
 //sizeof()内不是只有数组名,因此*a表示对第一行一维数组指针解引用,得到第一行一维数组的内容,因此是16
	printf("%d\n", sizeof(a[3]));//16
 // a[3]相当于二维数组第4行的数组名,虽然已经越界了,但仍可根据原数组规则计算,表示整个一维数组的大小,一行有4个整型,因此是16
	return 0;
}
总结:
数组名的意义:
1. sizeof( 数组名 ) ,这里的数组名表示整个数组,计算的是整个数组的大小。
2. & 数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。

备注:

strlen()函数是专门用于计算字符串长度的函数,接收的参数类型是字符指针。

指针笔试题

一、

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1);
    printf( "%d,%d", *(a + 1), *(ptr - 1));
    return 0; }
//程序的结果是什么?

测试结果如下:

 

分析:首先&a取出整个数组的地址,+1跳过一个数组,指向整个数组之后同样大小的一个内存空间,将其强制转化为int*类型的指针,此时整型指针ptr访问的步长为一个整形的大小,为4字节,打印的第一个内容,a在这里表示的首元素地址,+1跳过数组中的一个整型元素,指向第二个元素,解引用后是2;打印第二个内容,根据以上分析,ptr-1是向前跳过一个整形,指向数组的最后一个元素,解引用后是5。

图例:

 

二、

//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
 printf("%p\n", p + 0x1);//这里的0x1表示16进制下的1
 printf("%p\n", (unsigned long)p + 0x1);
 printf("%p\n", (unsigned int*)p + 0x1);
 return 0;
 }

测试结果如下:

 

分析:已知p是结构体指针,处理之前访问的步长为整个结构体的大小,20字节,因此p+0x1得到的应该是指向整个结构体之后同样大小的内存空间,数值大小为p的值+十六进制下的20(14),即打印的第一个内容为100014;将其强制转化为unsigned long无符号长整型后,加减整数符合整形数据的运算,数值大小为p的值+十六进制下的1(1),即打印的第二个内容为100001;将其强制转化为unsigned int*无符号整型指针后,访问步长为一个整型的大小,4字节,数值大小为p的值+十六进制下的4(4),即打印的第二个内容为100004.

三、

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);//%x是以16进制的形式打印数值,ptr1[-1]<=>*(ptr1-1)
    return 0;
 }

测试结果如下:

 

分析:首先&a取出整个数组的地址,+1跳过一个数组,指向整个数组之后同样大小的一个内存空间,将其强制转化为int*类型的指针,此时整型指针ptr1访问的步长为一个整形的大小,为4字节,ptr1-1向前跳一个整型,指向4,解引用后打印4;a单独出现表示数组首元素地址,将其强制转换为整数后+1,得到的实际是a所占4个字节的第二个字节的地址,将其强制转换为int*整型指针,访问步长成为1个整型,4字节,由此可知对ptr-1解引用后为00 00 00 04,前面的0都省略,对ptr2解引用后为02 00 00 00,前面的0省略。

图例如下:

注意:计算机存储和读取数据的顺序都是从低地址到高地址 ,存储时,小端模式首先取低字节的数据存放在低地址而大端模式首先取高字节数据存放在低地址,读取时,对于小端模式就是从借位开始而大端模式则从高位开始。

四、

int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };//(0,1)属于逗号表达式,按照从左往右的顺序进行,但最后一步计算结果才是有效值
    int *p;//这里等效为{ 1, 3, 5)}
    p = a[0];
    printf( "%d", p[0]);
 return 0; 
}

测试结果如下:

分析:分析可知,a[3][2]是一个三行两列的二维数组,p是一个整型指针,a[0]相当于一维数组的数组名,单独出现,表示一维数组首元素的地址,p[0]<=>*(p+0)<=>*p,因为p是指向数组第一个元素的地址,因此解引用后得到的是第一个元素,1。

图例:

 

五、

int main()
{
    int a[5][5];
    int(*p)[4];//p的类型是int(*)[4]的指针
    p = a;//a单独出现表示二维数组第一行元素的地址,即一维数组的地址,类型为int(*)[5]
    printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    return 0; //p[4][2]<=>*(*(p+4)+2)
}

 测试结果如下:

 

分析:虽然把int(*)[5]类型的指针赋值给int(*)[4]类型的p,但p的访问步长仍然是4个整型元素,因此p+4后跳过16个整型元素,指向16个元素之后大小为4个整型构成数组的内存空间,对其解引用后得到的是后面4个整型构成的一个数组,以数组名的形式呈现,而数组名单独出现表示数组首元素的大小,因此+2就相当于向后跳过两个整型元素,对其解引用得到的即为此时指针指向的元素,取出其地址与a[4][2]的地址相减的绝对值,就得到了两地址之间的元素个数。&p[4][2] - &a[4][2]=-4,以整数的形式打印(%d)时,自然打印-4,而以地址形式打印是,地址应该是无符号的整数,通常以16进制的形式打印,因此编译器认为-4的补码是一个无符号整数,直接以16进制的形式打印其补码.

-4原码:10000000000000000000000000000100

-4反码:11111111111111111111111111111011

-4补码:1111 1111 1111 1111 1111 1111 1111 1100

16进制:FF FF FF FC

图例:

 

六、

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

 测试结果如下:

 

分析:&aa取出整个二维数组的地址,其访问步长为整个二维数组的大小,+1跳过整个二维数组,指向二维数组之后同样大小的内存空间,强制转换为int*类型的整型指针,访问步长变为1个整型的大小,aa二维数组名单独出现,表示的是首行一维数组的地址,其访问步长为一维数组的大小,+1跳过一个一维数组,指向二维数组的第二行一维数组,强制转换为int*类型的整型指针,访问步长变为1个整型的大小,二者-1,都是向前跳一个整型大小,解引用后分别打印,10,和5。

图例:

 

七、

#include <stdio.h>
int main()
{
 char *a[] = {"work","at","alibaba"};
 char**pa = a;
 pa++;
 printf("%s\n", *pa);
 return 0; 
}

 测试结果如下:


分析:a是一个指针数组,放的是三个字符串首字符的地址,a数组名单独出现,表示的是首元素的地址,即字符串“work”中字符'w'的地址的地址,将其赋值给二级指针pa,pa++,pa跳过一个一级指针大小,指向数组中的下一个字符串首字符的地址,对其解引用,得到的是字符串“at”中‘a’的地址,自然可以通过这个地址打印字符串“at”。

 图例:

 字符串存储在内存中的常量池中,不可被改变,因此有首字符地址就可以找到整个字符串

八、

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

 测试结果如下:

 

分析:分析,c,cp,cpp,可得它们在内存中的形式如下,箭头表示的是指针指向的位置

 接下来我们来挨个分析每行代码:

1、printf("%s\n", **++cpp);

cpp表示数组cp首元素地址,自加后cpp指向cp数组中的第二个元素,第一次解引用得到c+2,第二次解引用,得到的是c+2指向的内容,即&‘P’,通过此地址可以找到字符串“POINT”并打印。图例如下:

2、printf("%s\n", *--*++cpp+3);

cpp此时表示cp的第二个元素的地址,自加后,cpp指向cp数组中的第三个元素,解引用后得到c+1,自减后变成c,对c解引用得到c指向的内容,即&“E”,+3跳过字符串的3个元素,指向“ENTER”中的第4个元素再继续读取找到“ER”并打印。图例如下:


3、 printf("%s\n", *cpp[-2]+3);

cpp[-2]<=>*(cpp-2),即代码可写为*(*(cpp-2))+3,cpp-2后得到的地址指向cp数组的第一个元素,解引用后得到c+3,再次解引用,得到c+3指向的内容,即&“F”,+3跳过字符串的3个元素,指向字符串的第4个元素再继续读取找“ST”’并打印。图例如下


4、 printf("%s\n", cpp[-1][-1]+1); 

cpp[-1][-1]<=>*(*(cpp-1)-1),  cpp-1后指向cp数组的第二个元素,解引用后为第二个元素内容c+2,c+2指向c数组的第三个元素,-1后为c+1,指向数组c的第二个元素&‘N’,+1跳过字符串的1个元素,指向字符串的第2个元素再继续读取找“EW”’并打印。图例如下:

 

 

       至此对于指针的学习就要告一段落了,但其在后续数据结构中有非常重要的应用,因此要多花时间去研究,以便后续的学习更加得心应手!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值