day 5

本文详细讲解了指针数组和数组指针的概念,包括指针的基本原理、不同类型的指针数组(如整型指针数组、字符指针数组),以及二级指针和函数指针的应用实例。通过示例展示了如何初始化、操作和使用这些概念,以及如何在实际编程中灵活运用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、指针数组和数组指针

什么是指针?指针的全称指针变量,它是存放地址的变量。

地址有两种运算:1 偏移 ± N int a; &a+1 &a 2 间接运算 *一元运算

定义指针变量 int a; int* p = &a; 在声明定义的语句中的*不是运算符,而是说明变量是指针变量身份的符号。

*指针变量

[] 数组

() 函数

1. 指针数组

在分析一个标识符的含义时,如果标识符被很多符号修饰,由近及远先右后左。

是数组。 每个元素都是一个指针。 array of pointer

int *a[5]; |int *|int *|int *|int *|int *|

[]说明a是数组,*说明数组的元素是指针,int说明指针指向的是整型。

每个元素都是指针。指针名称:

a[0] int*

a[1] int*

a[2] int*

a[3] int*

a[4] int*

示例1:

#include <stdio.h>

int main()
{
	int a = 90,b = 55,x = 10;
	int *p[3] = {&a,&b,&x};//定义指针数组p,分别使用a b x的地址,对p[0] p[1] p[2] 初始化
    //于是p[0]指向 a  p[1]指向 b  p[2]指向 x

	int i; 
	for(i=0; i<3; i++)
	{
        //间接运算得到的结果不是值,而是地址指向的变量本身
		printf("%d\n ",*p[i]);//p[i]是一个指针变量 需要间接运算来访问它指向的变量
	}
}

示例2:

#include <stdio.h>

int main()
{
	int a[5] = {1,2,3,4,90};
	int b[5] = {55,6,7,8,9};
	int c[5] = {7,8,7,8,6};
	int *p[3] = {a,b,c}; //使用abc三个数组的首地址初始化p的三个元素
    //于是p[0]指向数组a p[1]指向数组b p[2]指向数组c

	int i,x;
	for(x=0; x<3; x++)//遍历数组p
	{
		for(i=0; i<5; i++)
		{
			printf("%5d", p[x][i]); //和二维数组的区别,a b c三个数组可能不是连续的;abc三个数组在这里是可以不等长的,如果数组不等长,内层for循环的写法也要发生变化。
            //p[0] 指向a  p[0]存放的是数组a的首地址
            //p[1] 指向b
            //p[2] 指向c
            //p[x][i] 等价于a[i] 或者b[i] 或者c[i]
		}
		printf("\n");
	}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-03081m6o-1645665093092)(C:\Users\mudonghai\Desktop\linux&C高级\day5\image-20220209095230000.png)]

强调:

不要使用sizeof求数组的长度,因为求不出来。

sizeof()只有在给它数组名的时候才能求出数组的长度。我们什么时候才能获得到数组名?1 数组是全局的 2 使用sizeof()的时候和数组在同一个作用域内。

在C/C++语言中,别人定义的数组如果不告诉你数组有多长,你永远也不知道数组有多长。求不出来数组长度。

2.字符指针数组

是数组。每个元素都是字符指针。

定义: char *p[3]; p[0] p[1] p[2] |char *|char *|char *|

示例3:

在定义的语句中,* 不是运算符,而是用来表达身份的符号。

间接运算得到什么结果?不是值!!!!而是地址对应的变量的本身。

间接运算是针对地址的运算,只有地址才能进行间接与运算。

#include <stdio.h> 

int main()
{
	char str[] = "hello";//数组长度6 因为字符串的结尾有'\0'
	char ss[] = "better";//7

	char *p[2] = {str, ss};//p[0]指向数组str  p[1]指向数组ss
 
	int i;
	for(i=0; i<2; i++)
    {
        printf("%s\n", p[i]);//%s需要字符串的首地址 p[i]是字符数组的首地址,字符数组中存放字符串,所以字符数组的首地址也是字符串的首地址
    }	
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zWzmF3xk-1645665093099)(C:\Users\mudonghai\Desktop\linux&C高级\day5\image-20220209103205805.png)]

练习1:

接示例3中代码,将两个字符串中的e换成a,使用p遍历字符串。

#include <stdio.h>

int main()
{
	char str[] = "hello";
	char ss[] = "better";
	char* p[] = {str, ss};
	int i;
	for(i = 0;i < 2;i++)
	{
		int j = 0;
		while(p[i][j] != '\0')//遍历字符串
		{
			if(p[i][j] == 'e')
			{
				p[i][j] = 'a';
			}
			j++;
		}
	}
	for(i = 0;i < 2;i++)
	{
		printf("%s\n", p[i]);
	}
	return 0;
}

练习2:

随机点名。 定义字符指针数组,初始化人名(字符串常量)。每次运行程序随机打印一个人名。

随机数组的角标

#include <stdlib.h>

srand(time(0));//初始化随机种子,种子只能初始化一次

rand()%N;//获得一个随机数

#include <stdio.h>
#include <stdlib.h>

int main()
{
	/*char* str = "hello";
	char* ss = "better";
	char* p[] = {str, ss};*/
	char* p[] = {
		"xiaozhao",
		"xiaoqian",
		"xiaosun",
		"xiaoli"
	};

	srand(time(0));//初始化随机种子
	int index = rand()%4;
	printf("%s\n", p[index]);
	return 0;
}

练习3:

定义字符指针数组 char *names[5]分别指向 5 个字符串常量,从小到大输出字符串的内容。

交换的是指针的指向

#include <stdio.h>
#include <string.h>

int main()
{
	char* p[] = {
		"xiaozhao",
		"xiaoqian",
		"xiaosun",
		"xiaoli",
		"xiaozhang",
		"xiaowang",
		"xiaoli",
		"xiaozhao"
	};

	int i, j;
	for(i = 0;i < 7;i++)
	{
		for(j = 0;j < 7-i;j++)
		{
			if(strcmp(p[j], p[j+1]) == 1)
			{
				char* t = p[j];
				p[j] = p[j+1];
				p[j+1] = t;
			}
		}
	}

	for(i = 0;i < 8;i++)
	{
		printf("%s\n", p[i]);
	}
	return 0;
}

3.数组指针

是指针。 是为了指向多维数组的指针。

数组名是数组的首元素地址,类型是数组的元素类型,使用指针指向数组时,需要将指针定义成数组的元素类型,

把数组名赋值给指针。

int a[3][4] = {{1,2,3,4},{6},{8}};

int (*p)[4]; // *离p最近,因为有()括起来了,所以这里p是指针,[]说明指针指向的是数组类型,int说明数组的元素是int类型。

p = a;

定义指针变量指向数组,需要把指针变量定义成数组的元素类型,然后把数组名赋值给指针变量

int main()
{
	int a[3][5] = {{1,2,3,4},{6},{8}};
	int(*p)[5];  // \*离p最近,因为有()括起来了,所以这里p是指针,[]说明指针指向的是数组类型,int说明数组的元素是int类型。
	p = a;
    //int* q = a;//语法错误
    //a是数组名,是数组的首元素地址,类型是元素类型的地址,所以a的地址类型是int [5]
	int i, j;
    for(i = 0;i < 3;i++)
    {
        for(j = 0;j < 5;j++)
        {
            printf("%d ", p[i][j]);
        }
		printf("\n");
    }
	return 0;
}

示例4:

#include <stdio.h> 

void print_var(int (*p)[4], int n)
{
	int i,j;
	for(i=0; i<n; i++)
	{
		for(j=0; j<4; j++)
		{
			printf("%5d", p[i][j]);
		}
		printf("\n");
	}
	return;
}

int main()
{
	int a[3][4] = {{1,2,3,4},{6},{6}};
	print_var( a,3 );
	return 0;
}

二、二级指针

指向指针的指针变量

int a = 100; int *p = &a; int **q = &p;

右边的*说明q是一个指针变量,*说明q指向的还是一个指针变量,int类型的指针变量。q是一个指向int类型指针变量的指针变量。给q赋值,需要int*类型的变量地址。

------------------------------------------------------

地址: 0x00001234 0x00001238 0x0000123c

变量: a p q

值 : 100 0x00001234 0x00001238

-------------------------------------------------------

表示:

​ a = 100 *p = 100 **q = 100

​ &a=0x00001234 p=0x00001234 *q=0x00001234

​ &p=0x00001238 q = 0x00001238

​ &q= 0x0000123c

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CjDTaMKy-1645665093100)(C:\Users\mudonghai\Desktop\linux&C高级\day5\image-20220209141427060.png)]

示例5:

#include <stdio.h> 
#include <stdlib.h> 
//函数功能是在堆空间申请一块内存,返回给主调函数。函数在堆空间创建了一个int类型数组。
int *get_memory(int n)
{
    //malloc是在堆空间申请内存,堆空间内存不会自动释放。
    //参数:内存的大小(字节)。   n*sizeof(int) 就是N个int类型的大小。就是N个元素的int类型数组。
    //返回值: void* 类型,无类型的指针。 无类型地址仅仅只一个地址,不能间接运算,因为没有void类型的变量。
    //所以我们会把返回的void*地址转换成我们需要的类型。在这段代码中,因为我们要创建int类型的数组,所以将地址转换成int*。
	int *p = (int *)malloc( n * sizeof(int) );  
	return p;  //将申请到内存的地址返回给主调函数
}

int main()
{
	int *p;
	int n;
	puts("Enter n:");
	scanf("%d", &n);
	p = get_memory( n ); //根据输入的整数,创建对应长度的整型数组 
	int i, sum = 0;
	for(i=0; i<n; i++)
	{
		scanf("%d", &p[i]);
		sum += p[i];
	}
	printf("%d\n", sum);

	free(p);//释放堆空间数组
	p = NULL;//在释放堆空间的内存后,将指针置空是好习惯,尽管在当前这段代码中没什么作用。
	return 0;
}

示例6:

#include <stdio.h> 
#include <stdlib.h> 

//在示例5的基础上改进,不是通过返回值的形式把地址返回给主调函数,而是通过参数的形式将地址返回给主调函数。
//想把被调函数中的地址通过参数的形式返回主调函数,被调函数返回的数据类型是int*,所以参数应该定义成能够存放int*类型变量地址的指针变量,所以参数类型应该是int**
void get_memory( int ** q, int n)
{
	*q = (int *)malloc( n * sizeof(int) ); 
	return ;
}

int main()
{
	int *p;
	int n;
	puts("Enter n:");
	scanf("%d", &n);
	get_memory( &p, n ); //使用主调函数中的变量p的地址,初始化被调函数的形参q
    //通过形参q把get_memory函数中申请的内存的地址赋值给了p
	int i, sum = 0;
	for(i=0; i<n; i++)
	{
		scanf("%d", &p[i]);
		sum += p[i];
	}
	printf("%d\n", sum);
	free(p);
	p = NULL;
	return 0;
}

三、指针函数、函数指针

1. 指针函数

返回值是指针类型的函数,叫指针函数。

char * strcpy(char *dest,const char *src);

char * strcat(char *dest,const char *s2);

void * malloc( size_t size );

void* 空类型指针,可以指向任何类型的变量。

2. 函数指针

是指针。 是用来指向函数的指针。

函数名就是函数的地址。
函数是在内存的代码段,函数名就是函数在内存中的地址。

函数指针实现多态。 盒饭 二楼食堂和盒饭,一荤两素,红烧鱼、芹菜土豆丝、西红柿炒鸡蛋

对象

买房

int get_sum(int *p, int n);    
int (*p1)(int *, int);  //()里的内容,取决于参数列表的类型
p1 = get_sum;


void print_arr(int *p, int n);  
void (*p2)(int *, int );      
p2 = print_arr;


void swap(char *q, char *p);   
void (*p3)(char *, char *);     
p3 = swap;
//或者写成
void (*p3)(char *, char *) = swap;//初始化


//调用函数:把函数名换成函数指针
get_sum(&a, b);
p1(&a, b);
(*p1)(&a, b);

练习4:

请定义指针,指向对应的函数,并调用。

int rand( );

int strcmp(const char*s1, const char* s2);

通过指针变量调用函数。

打印一个100以内的随机数

打印"hello world"和"hello farsight"的比较结果

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
	int(*pRand)() = rand;//定义函数指针pRand指向函数rand
	int(*pStrcmp)(const char*, const char*) = strcmp;

	srand(time(0));
	printf("%d\n", pRand()%100);//通过函数指针pRand调用函数rand
	printf("%d\n", pStrcmp("hello world", "hello farsight"));

	return 0;
}

问题:

以下变量的类型是?

int a; 整型变量

int a[10]; 10个元素的整型数组

int *a; 整型指针

int *a[10]; 10个元素的整型指针数组,数组中是10个int类型的指针

int (*a)[10]; 指向数组的指针,数组10个int类型元素

int (*a[3])[10]; 3个元素的指针数组,指针指向10 个int类型元素的数组

int *a(int); 返回值是int类型指针的函数

int (*a)(int); 函数指针,指向参数列表是int,返回值也是int的函数

int *(*a)(int); 函数指针,指向参数是int,返回值是int*的函数

int (*a[2])(int); 2个元素的指针数组,指针指向参数是int,返回值是int的函数

3. 函数指针数组

定义: int (*p[4])(int ,int );

示例7:

#include <stdio.h> 
//add sub mul返回值类型和参数列表都一样,所以可以使用同样类型的函数指针指向它们。
int add(int x,int b)
{
	return x+b;
}

int sub(int x,int b)
{
	return x-b;
}

int mul(int x,int b)
{
	return x*b;
}

int main()
{
    //数组p中的三个函数指针,分别指向了add sub mul
	int (*p[3])(int ,int ) = {add, sub, mul};  
	int i; 
	for(i=0; i<3; i++)
    {
        printf("%d\n", p[i](6,9));//i=0 add  i=1 sub i=2 mul
    }
	return 0;
}


作业:

选择题

  1. [单选题-C语言]

关于if语句,下面哪一种说法是错误的 ( ) (牛客网)

A: 一个if只能有一个else与之配对

B: if语句中可以有多个else if子句

C: if语句中可以包含循环语句

D: if语句中不能包含switch语句

  1. [单选题-位运算]

下面关于位运算符的叙述,正确的是?( ) (牛客网)

A: #表示"按位异或"的运算

B: &表示"按位或"的运算

C: ~表示"按位取反"的运算

D: ||表示"按位或"的运算

3.[单选题-C语言]

下面哪个语句无法通过编译? ( ) (牛客网)

A: if (x>y);

B: if (x=y) && (x!=0) x+= y;

C: if (x!=y) scanf("%d",&x); else scanf("%d",&y);

D: if (x<y) {x++; y++;}

  1. [单选-linux]

若基于Linux操作系统所开发的ARM应用程序源文件名为test.c,

那么要生成该程序代码的调试信息,编译时使用的GCC命令正确的是? ( ) (阿里巴巴)

A: arm-linux-gcc -c -o test.o test.c

B: arm-linux-gcc -S -o test.o test.c

C: arm-linux-gcc -o test test.c

D: arm-linux-gcc -g -o test test.c

  1. [单选-C]

若整型变量a、b、c、d中的值依次为:1、4、3、2。则条件表达式a<b?a:c<d?c:d的值 ( ) 。(牛客网)

A: 1

B: 2

C: 3

D: 4

6.[单选题-优先级]

已知int i=1, j=2;,则表达式i+++j的值为( )。

A: 1

B: 2

C: 3

D: 4

  1. [单选题]

在软件生命周期中,( )阶段负责“写出正确、易懂,容易维护的程序模块”。

A: 详细设计

B: 编码和单元测试

C: 确认测试

D: 总体设计

  1. [单选题]

break和continue两个都是( ) (美行科技)

(A)控制循环指令 (B) 数据结构

© 相等的判断 (D) 不包含在C++中

  1. [单选题]

有以下程序

main()

{ int a=666,b=888;

printf("%d\n",a,b);

}

程序运行后的输出结果是( )。

A: 错误信息

B: 666

C: 888

D: 666,888

编程题:

编写函数,[采用递归方法]实现将输入的字符串按反序输出。(北京麦邦)

函数自己调用自己

并没有改变字符串,仅仅是反着输出 “abc” 输出"cba"

void out_char(char *p)

{

}

#include <stdio.h>

void printNum(int num)
{
	printf("%d\n", num);
	if(num == 10)
		return;
	printNum(num+1);//递归
}

int main()
{
	printNum(1);
	return 0;
}

6.[单选题-优先级]

已知int i=1, j=2;,则表达式i+++j的值为( )。

A: 1

B: 2

C: 3

D: 4

  1. [单选题]

在软件生命周期中,( )阶段负责“写出正确、易懂,容易维护的程序模块”。

A: 详细设计

B: 编码和单元测试

C: 确认测试

D: 总体设计

  1. [单选题]

break和continue两个都是( ) (美行科技)

(A)控制循环指令 (B) 数据结构

© 相等的判断 (D) 不包含在C++中

  1. [单选题]

有以下程序

main()

{ int a=666,b=888;

printf("%d\n",a,b);

}

程序运行后的输出结果是( )。

A: 错误信息

B: 666

C: 888

D: 666,888

编程题:

编写函数,[采用递归方法]实现将输入的字符串按反序输出。(北京麦邦)

函数自己调用自己

并没有改变字符串,仅仅是反着输出 “abc” 输出"cba"

void out_char(char *p)

{

}

#include <stdio.h>

void printNum(int num)
{
	printf("%d\n", num);
	if(num == 10)
		return;
	printNum(num+1);//递归
}

int main()
{
	printNum(1);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

encounter♌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值