【C进阶】* 指针(1)

目录

1. 字符指针

2. 指针数组

3. 数组指针

       3.1、数组指针的定义

       3.2、&数组名VS数组名

       3.3、数组指针的使用

4. 数组参数、指针参数

       4.1、一维数组传参

       4.2、二维数组传参

       4.3、一级指针传参

       4.4、二级指针传参


1. 字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char* ;

  • 一般使用:
int main()
{
    char ch = 'w';
    char *pc = &ch;
    printf("%c\n", ch);  // w
    printf("%c\n", *pc); // w
    //所以*pc的内容 = ch的内容 = 'w';
    return 0;
}
  • 再如:
int main()
{
	char arr[] = "abcdef";
	char* pc = arr;
	printf("%s\n", arr);  //abcdef
	printf("%s\n", pc);   //abcdef
	return 0;
}
  • 还有一种使用方式如下:
     
#include<stdio.h>
int main()
{
	const char* pstr = "hello bit";  //这里是把一个字符串放到pstr指针变量里了吗?
	printf("%c\n", *pstr);  // h
	printf("%s\n", pstr);   // hello bit
    char* p = "abcdef";
    *p = 'w';  // err 编译器报错,最好在开始加上const修饰,这样若再修改*p编译器会直接报警,像上述的const char* pstr = "hello bit";一样
	return 0;
}

代码 const char* pstr = "hello bit.";

特别容易让大家以为是把字符串 hello bit 放到字符指针 pstr 里了,但是/本质是把字符串 hello bit. 首字符的地址放到了pstr中。

上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量 pstr 中。

  • 那就有可这样的面试题:
#include<stdio.h>
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcdef";
	const char* p1 = "abcdef";
	const char* p2 = "abcdef";
	if (arr1 == arr2)
	{
		printf("hehe\n");
	}
	else
	{
		printf("haha\n");
	}
	if (p1 == p2)
	{
		printf("hehe\n");
	}
	else
	{
		printf("haha\n");
	}
	return 0;
  • 运行结果如下:

  • 出现haha的原因:

这里我们为了存储abcdef创建了arr1和arr2两个数组,那么这个字符串在内存中一定是有两块空间的,arr1和arr2两块不同数组都创建了abcdef,两个不同数组的数组名当然是不同首元素的地址,当这两个地址在不同空间上的时候,那么这两个值自然不一样,所以haha。

  • 出现hehe的原因:

这里两个字符串abcdef一模一样,且都是常量字符串,各自又不能进行修改,没有必要在内存里存两份,为了在内存使用方便,能够节省空间,不管是p1还是p2都指向同一块空间的起始位置的地址,也就是第一个字符的地址。所以p1==p2,所以hehe。

2. 指针数组

  • 概念:指针数组是一个存放指针的数组。
#include<stdio.h>
int main()
{
	int arr[10] = { 0 }; // 整形数组
	char ch[5] = { 0 };  // 字符数组
	int* arr1[5];  // 存放整形指针的数组 - 指针数组
	char* arr2[5]; // 存放一级字符指针的数组 - 指针数组
    char** arr3[5];// 存放二级字符指针的数组 - 指针数组
	return 0;
}
  • 用途:
#include<stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int d = 40;
	int i = 0;
	int *arr[4] = { &a,&b,&c,&d };
	for (i = 0; i < 4; i++)
	{
		printf("%d ", *(arr[i]));  // 10 20 30 40
	}
	return 0;
}

上述代码是只是为了便于理解而写出来的,但真实情况下很少有类似上述代码的,因为有点小low。

正确用法如下:

  • 字符指针数组:
#include<stdio.h>
int main()
{
	char* arr[] = { "abcdef","qwer","zhangsan" };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		printf("%s\n", arr[i]);
	}
	return 0;
}
  • 画图解释 char* arr[ ] = { "abcdef","qwer","zhangsan" };

  •  整形指针数组
#include<stdio.h>
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[] = { arr1,arr2,arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *(parr[i] + j));
		}
		printf("\n");
	}
	return 0;
}
  • 画图解释 int* parr[ ] = { arr1,arr2,arr3 };

3. 数组指针

   3.1、 数组指针的定义

  • 数组指针是指针?还是数组?

答案是:指针

我们已经熟悉:

整形指针: int * pint; 能够指向整形数据的指针

浮点型指针: float * pf; 能够指向浮点型数据的指针

数组指针应该是:能够指向数组的指针

  • 下面代码哪个是数组指针?
int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?
  • 解释:
int *p1[10]; // 指针数组
int (*p2)[10];// 数组指针

//解释p1
//p1先和[]结合,说明p1是个数组,数组有10个元素且每个元素类型是int*,所以p1是指针数组

//解释:p2
//p2先和*结合,说明p2是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p2是一个指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p2先和*结合。以此确保p2是数组指针

   3.2、 &数组名VS数组名

  • 对于下面的数组:
int arr[10];

arr 和 &arr 分别是啥?

我们知道arr是数组名,数组名表示数组首元素的地址。

那&arr数组名到底是啥?

  • 我们看一段代码:
#include <stdio.h>
int main()
{
    int arr[10] = { 0 };
    printf("   arr=   %p\n", arr);
    printf("&(arr[0])=%p\n", &(arr[0]));
    printf("  &arr=   %p\n", &arr);
    return 0;
}
  • 运行结果如下:

可见数组名和&数组名打印的地址是一样的 

难道两个是一样的吗?

  • 我们再看一段代码:
#include <stdio.h>
int main()
{
    int arr[10] = { 0 };
    printf("        arr=  %p\n", arr);
    printf("      arr+1=  %p\n", arr + 1);
    printf("\n");
    printf("  &(arr[0])=  %p\n", &(arr[0]));
    printf("&(arr[0])+1=  %p\n", &(arr[0]) + 1);
    printf("\n");
    printf("       &arr=  %p\n", &arr);
    printf("   &arr + 1=  %p\n", &arr + 1);
    return 0;
}
  • 运行如下:

根据上面的代码我们发现,我们都知道数组名arr就是首元素地址,所以arr和&arr[0]是一样的,所以后续即使分别+1得到的地址也是对应相等的。其实&arr和arr,虽然值是一样的,但是意义应该不一样的。

实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)

本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型

数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40. 

  • 再来一组代码:
int arr[10];
int* p = arr;
int(*p2)[10] = &arr;  //取出的是数组的地址,既然是数组的地址,就应该放到数组指针变量中
//p2就是一个数组指针

p存放的是第一个元素的地址,p+1跳过1个int,*p访问一个int数据4个字节

p2存放的是arr数组的地址,p2+1跳过整个数组

*p2访问到的是arr数组,*p2等价于arr,那么*p2就是数组名,数组名又相当于首元素的地址,所以*p2本质上是arr数组第一个元素的地址。

p<--->*p2<---->arr

   p+1<--->*p2+1

   3.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
    //但是我们一般很少这样写代码,我们先用数组指针打印出10个元素看看
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *((*p) + i));  // 1 2 3 4 5 6 7 8 9 10
	}
    return 0;
}
char* arr[5];
char* (*pa)[5] = &arr;
//pa是指针变量的名字,(*pa)中*说明pa是指针,[5]说明pa指向的数组是5个元素,char*说明pa指向的数组的元素类型是char*
  • 上述用数组指针打印一维数组的方法多少有些多此一举,明明可以用for循环打印,却偏偏用数组指针,无疑是脱裤子放屁!!!(狗头保命)
  • 数组指针至少用到二维数组以上才方便些,如下:
#include<stdio.h>
// 参数是数组的形式
void print1(int arr[3][5], int x, int y)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < x; i++)
	{
		for (j = 0; j < y; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
// 参数是指针的形式
void print2(int(*p)[5], int x, int y)
{
	int i = 0;
	for (i = 0; i < x; i++)
	{
		int j = 0;
		for (j = 0; j < y; j++)
		{
			printf("%d ", *(*(p + i) + j));
			//printf("%d ", p[i][j]);
			//printf("%d ", *(p[i] + j));
			//printf("%d ", (*(p + i))[j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	print1(arr, 3, 5); // arr - 数组名 - 数组名就是首元素地址
    //数组名arr,表示首元素的地址
    //但是二维数组的首元素是二维数组的第一行
    //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
    //可以数组指针来接收
	print2(arr, 3, 5);
	return 0;
}

  •  注意:
  • print2函数形参那用指针的过程:

首先,print2函数实参的数组名是首元素第一行的地址,第一行是个一维数组,那么传给形参的就是一维数组的地址,所以应该放到数组指针里头去。该指针指向的是个数组,数组有5个元素,每个元素是int。所以就是void print2(int(*p)[5], int x, int y)

  • print2函数4个不同打印输出的方式等价:

当要打印数组的内容时,需要遍历数组,先创建变量i,用for循环。p是指向一行的,p+i是表示跳过i行,此时就表示下标为i的这一行,此时再解引用*(p+i)就找到了这一行,就相当于拿到了这一行的数组名,然后再创建变量j,同样for循环,我们找到了这一行,需要再找到这一行的某个元素,此时再加上j,就找到了下标为j的元素地址*(p + i) + j,然后括号再括起来,再*解引用,就找到了i行j列的元素。*(*(p + i) + j),而下面三种的打印方式与刚才这种是等效的,就像下述代码一样。

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
		printf("%d ", *(arr + i));
		printf("%d ", arr[i]);  
		printf("%d ", p[i]);
		// arr[i] == *(arr + i) == *(p + i) == p[i] 等价
	}
  • 学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:
int arr[5]; // arr是一个5个元素的整形数组
int* parr1[10];// parr1是一个数组,数组有10个元素,每个元素的类型都是int*,parr1是指针数组
int(*parr2)[10];//parr2是一个指针,该指针指向了一个数组,数组有10个元素,每个元素的类型都是int,parr2是数组指针
int(*parr3[10])[5];//parr3是一个数组,该数组的有10个元素,每个元素是一个数组指针,该数组指针指向的数组有5个元素,每个元素是int

4. 数组参数、指针参数

   4.1、 一维数组传参

#include <stdio.h>
void test(int arr[])//ok?    可
{}
void test(int arr[10])//ok?  可
{}
void test(int* arr)//ok?     可
{}
void test2(int* arr[20])//ok?可
{} 
void test2(int** arr)//ok?   可
{}
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

   4.2、 二维数组传参

  • 无指针:
#include<stdio.h>
void test(int arr[3][5])//ok?  可
{}
void test(int arr[][5])//ok?   可
{}
void test(int arr[5][])//ok?   不可
{}
void test(int arr[][])//ok?    不可
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。行可以省略,但列不可省略
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
int main()
{
	int arr[3][5] = { 0 };
	test(arr); // 二维数组传参
	return 0;
}
  • 有指针:
#include<stdio.h>
void test(int* arr)//ok?   不可 arr是个二维数组,二维数组的数组名表示第一行的地址,第一行是个一维数组,而int* arr是存放整形地址的
{}
void test(int** arr)//ok?  不可 数组名是首元素的地址,是第一行的地址,是数组的地址,数组的地址不能存到二级指针里,二级指针是存放一级指针变量的地址
{}
void test(int* arr[5])//ok? 不可
{}
void test(int(*arr)[5])//ok? 可 第一行的地址是5个整形的指针
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr); // 二维数组传参
	return 0;
}

   4.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]);
	//一级指针p,传给函数
	print(p, sz);
	return 0;
}
  • 当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
#include<stdio.h>
void test1(int* p)
{}

void test2(char* p)
{}
int main()
{
	int a = 10;
	int* p1 = &a;
	test1(&a); //可
	test1(p1); //可

	char ch = 'w';
	char* pc = &ch;
	test2(&ch);//可
	test2(pc); //可
	return 0;
}

   4.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;
}
  • 当函数的参数为二级指针的时候,可以接收什么参数?
void test(char** p)
{}
int main()
{
	char c = 'b';
	char* pc = &c;
	char** ppc = &pc;
	char* arr[10];
	test(&pc); // 可
	test(ppc); // 可
	test(arr);//Ok? 可
	return 0;
}

猜你喜欢

转载自blog.csdn.net/bit_zyx/article/details/122483665