死磕C语言指针

兜兜转转还是逃不过 C 语言,这该死的缘分。

先看一眼我的西野七濑

学习自:https://zhuanlan.zhihu.com/p/89121683

目录

1 指针

1.1 指针是乜嘢

1.2 指针的声明

1.3 运算符

1.4 简单的小例子们:

例子1 

例子2:指针在函数间通信

1.5 指针的运算

1.5.1 指针加减运算

1.5.2 间址运算

1.5.3 指针的相减 = 两个地址之间的偏移量

2 指针与数组

2.1 指针和一维数组

2.1.1 定义数组及指针变量

2.1.2 能动手就不要瞎扯

2.2 指针和二维数组

2.2.1 定义

2.2.2 能动手就不要瞎扯

3 指针数组

3.1 定义

3.2 灵活的双手动起来

4 多级指针

5 字符指针与字符串

5.1 定义

5.2 打代码

6 指针型函数

6.1 定义

6.2 敲代码

7 指向函数的指针

7.1 定义

7.2 码代码

8 结构体指针

8.1 结构体变量指针

8.1.1 定义

8.1.2 干活

8.2 结构体数组指针

8.2.1 定义

8.2.2 搬砖


1 指针

1.1 指针是乜嘢

指针(pointer):一个值为内存地址的变量。

char 类型变量的值是字符,int 类型变量的值是整数,指针变量的值是地址。

1.2 指针的声明

数据类型 *指针名,这里的 * 表明声明的变量是一个指针,没有访问指针目标的含义。

int * pi; // pi是指向int类型变量的指针
char * pc; // pc是指向char类型变量的指针
float * pf; // pf是指向float类型变量的指针

指针存放的值是一个地址,在大部分系统内部,该地址由一个无符号整数表示,但不能把指针认为是整数类型。

  • 指针变量不能赋予常量值
  • 不能接受键盘输入的数据

1.3 运算符

间接运算符 *:访问地址的目标

val = *ptr; // 找出ptr指向的值

地址 & 运算符:取变量的存储地址

&num 表示变量 num 的地址。

1.4 简单的小例子们:

例子1 

#include <stdio.h>
 
int main ()
{
   int  num = 97;   
   int  *p;        // 指针变量的声明 
   p = &num;       //在指针变量中存储 num 的地址 

   printf("Address of var variable: %p\n", &num );
   // 指针变量中存储的地址 
   printf("Address stored in ip variable: %p\n", p );
   // 使用指针访问值 
   printf("Value of *ip variable: %d\n", *p );
   return 0;
}

结果:

图解

例子2:指针在函数间通信

#include<stdio.h>

void interchange(int *u, int *v);

int main(){
	int x = 5, y = 10;
	printf("Originally x = %d and y = %d.\n", x,y);
	
	interchange(&x, &y);
	printf("Now x = %d and y = %d.\n", x,y);
	
	return 0;
}

void interchange(int *u, int *v){// 参数为指针,函数传递的是地址 ,u与v存的是地址 
	int temp;
	temp = *u; // 直接改变对应地址存放的值 
	*u= *v;
	*v= temp; 
}

结果:

1.5 指针的运算

知识点:

  • 三个操作符的优先级:() > [] > *,没错 * 是最卑微的
  • *p++ 里,* 号和 ++ 属于同一优先级,且方向都是从右向左的,*p++ 和 *(p++) 作用相同

1.5.1 指针加减运算

#include<stdio.h>

void main(){
	int a=10,b=20,c=30;
	int *p=&b;
	printf("%p\n",&a);
	printf("%p\n",&b);
	printf("%p\n",&c);
	p=p+1;
	printf("%p\n",p);
	p=p-2;
	printf("%p\n",p);	
} 

结果:

1.5.2 间址运算

*p++:* 与 ++ 同优先级,结合方向为自右向左,p++,先赋值再自加,因此结果等价于 *p 返回其值,p++

*++p:++p,先自加后赋值,等价于 p++, *p 返回其值

(*p)++:括号优先,等价于 *p 返回其值,然后将 *p 的结果 + 1

例:

#include<stdio.h>

int main(){
	int a[] = {10,20,30}, *p=a; 
	printf("*p++ is %d\n", *p++);   
	p=a; 
	printf("*++p is %d\n", *++p); 
	p=a; 
	printf("++(*p) is %d\n", ++(*p)); 
	p=a;
	printf("a[0] is %d\n",a[0]);
	printf("(*p)++ is %d\n", (*p)++); 
	return 0;
}

结果:

1.5.3 指针的相减 = 两个地址之间的偏移量

2 指针与数组

2.1 指针和一维数组

2.1.1 定义数组及指针变量

数组名 s 代表的是该数组首元素的首地址,&s 代表的是整个数组的首地址

s == &s[0],返回 ture

&s == &s[0], 是错误的比较,尽管 &s 与 &s[0] 的地址值相同,但两者的含义不同

2.1.2 能动手就不要瞎扯

移动指向数组的指针

数组元素的引用方法:

因为 ps 是变量,s 是符号常量,不能给 s 赋值, s = p(×)

例子:

#include<stdio.h>

int main(){
	int i;
	int a[5] = {1,2,3,4,5}, *p = a;   // p作为一维数组 a 的指针
	// 通过 a[i] 方式输出所有元素
	for(i=0; i<5; i++){
		printf("%d ",a[i]);
	} 
	printf("\n");
	// 通过 *(a+i)方式输出所有元素
	for(i=0; i<5; i++){
		printf("%d ",*(a+i));
	}
	printf("\n");
	// 通过 *(p+i)方式输出所有元素,p不移动 
	for(i=0; i<5; i++){
		printf("%d ",*(p+i));
	}
	printf("\n");
	printf("a is %p\n", a);
	printf("p is %p\n", p);
	// 通过 *p方式输出所有元素,p移动 
	for(i=0; i<5; i++){
		printf("%d ",*p++);
	}
	printf("\n");
	printf("%p", p);
}

结果:

2.2 指针和二维数组

参考:C/C++二维数组总结

2.2.1 定义

(1) 二维数组定义

定义了一个Array[3][4],那就指的是定义了一个三行四列的矩阵形状的二维数组,如下图所示。这样的矩阵在内存中是以箭头右边的方式存放的,也就是说实际上我们定义的二维数组在内存中仍然像是一维数组那样连续存储的。可以想象为把一个矩阵一层层伸展铺平。

int array[3][4];   // 内含 int 数组的数组

数组名 array 是该数组首元素的地址,一维数组首元素的地址,而 array[0] 本身是一个内含 4 个整数的数组,

array = &array[0]。

&array 是整个数组的首地址。

array,&array,&array[0],&array[0][0],虽然地址值相同,但有各自的含义。

例子:

#include<stdio.h>

int main(){
	
	int array[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
	
	printf("array is %d\n",array);
	printf("*array is %d\n",*array);
	printf("array[0] is %d\n",array[0]);
	printf("*array[0] is %d\n",*array[0]);
	printf("array[0][0] is %d\n",array[0][0]);
	printf("&array[0][0] is %d\n",&array[0][0]);
}

结果:

(2) 二维数组指针定义

基类型 (*指针变量)[整形表达式]

int a[2][3], (*p)[3]  = a;

() 优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是 n,也可以说是 p 的步长。执行 p+1 时,p 要跨过 n 个整型数据的长度。

p[i]:指向 a[i] 的元素

p++:指向数组 a 的后一个一维数组元素

p--:指向数组 a 的前一个一维数组元素

p+1:等价于 a+1

当 p 指向 a 数组的开头时,可以通过以下形式来引用 a[i][j]

*(p[i]+j):对应于 *(a[i]+j)

*(*(p+i)+j):对应于 *(*(a+i)+j)

*(*(p+i))[j]:对应于 *(*(a+i)+j)

p[i][j]:对应于 a[i][j]

数组指针 p 与对应的二维数组 a 的区别是:二维数组 a 是一个常量,数组指针 p 是一个变量

2.2.2 能动手就不要瞎扯

#include<stdio.h>

void main(){
	int i, j;
	int a[2][3] = {{1,2,3},{4,5,6}}, (*p)[3] = a;
	
	// 通过 a[i][j] 方式输出所有元素 
	for(i=0; i<2; i++)
		for(j=0; j<3; j++)
	printf("%d ",a[i][j]);	
	printf("\n");	
	
	// 通过 *(*(a+i)+j) 方式输出所有元素 
	for(i=0; i<2; i++)
		for(j=0; j<3; j++)
	printf("%d ",*(*(a+i)+j));	
	printf("\n");
	
	// 通过 *(p[i]+j) 方式输出所有元素 
	for(i=0; i<2; i++)
		for(j=0; j<3; j++)
	printf("%d ",*(p[i]+j));	
	printf("\n");
	
	// 通过 *(*(p+i)+j) 方式输出所有元素 
	for(i=0; i<2; i++)
		for(j=0; j<3; j++)
	printf("%d ",*(*(p+i)+j));	
	printf("\n");
				
}

3 指针数组

3.1 定义

存储指针的数组。它的每个元素都是指针变量,指向相同的数据类型。

数组类型 *指针数组名[元素个数];

int *p[3]; 

[] 比 * 的优先级高,p 先与 [3] 结合,形成 p[3] 的数组形式,它有 3 个元素,然年再与 * 结合,表示是指针类型的数组。

int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]

使用指针数组更方便地操作长短不一的字符串,而使用二维数组来操作字符串相对浪费空间,如下:

3.2 灵活的双手动起来

例子

#include <stdio.h>
 
const int MAX = 3;
 
int main ()
{
   const char *words[] = {"wyd","is","smart"};
   
   int i = 0;
   for ( i = 0; i < MAX; i++)
   {
      printf("Value of words[%d] = %s\n", i, words[i] );
   }
   return 0;
}

4 多级指针

二级指针:就是对一级指针再取地址

三级指针:就是对二级指针再取地址

#include <stdio.h>
 
const int MAX = 3;
 
void main ()
{
   int *p, **pp, ***ppp, s = 100;

   p = &s;
   // q 存 p 的地址,p 存 s 的地址
   pp = &p;
   ppp = &pp;
   
   printf("Value of p = %d\n", *p); 
   printf("Value of pp = %d\n", **pp);
   printf("Value of ppp = %d\n", ***ppp);
   
}

结果

5 字符指针与字符串

5.1 定义

指向 char 型数据的指针

5.2 打代码

#include <stdio.h>
 
void main ()
{
	char s [] = "wyd is smart";
	char *p = "wyd is handsome";
	
	*(s+3) = 0; // 在第4个字符位置加结束标志
	puts(s); 
	puts(p);
	//*(p+3)=0; 运行报错,字符串常量不能赋值 
	p=s;   // 没有这行,下面输入字符回车会报错 
	printf("%p",p); 
	gets(p);
	puts(p);
}

6 指针型函数

6.1 定义

函数返回值的数据类型决定了该函数的数据类型。

数值型函数:返回值为数值类型

字符型函数:返回值为字符类型

指针型函数:返回值是地址

数据类型 *函数名(参数)

如:int *func(int x,float y)

6.2 敲代码

例 1

#include <stdio.h>
 
int *f(int *x, int *y){// x与y都是指针变量,存的地址
    if(*x < *y)
        return x;
    else
        return y;
}
 
void main ()
{
    int x=7, y=8, *p;
    p=f(&x, &y);
    printf("%p\n", p);
    printf("%d\n", *p);
}

结果:

例 2

#include <stdio.h>
 
int *f(char *b){
	b += 3;
	return b;
}

void main ()
{
	char a[] = "abcdefg", *p;
	p = f(a);
	printf("%s\n",p);
}

结果

7 函数指针(指向函数)

7.1 定义

 C 语言中,指针变量除了保存数据的存储地址外,还可以保存函数的存储首地址。函数的存储首地址又称为函数的执行入口地址。

数据类型 (*函数指针名)();

如:int (*func)();                                                                                                                                                                                                   

7.2 码代码

#include <stdio.h>
 
int f1(int x){
	return x*x;
}
int f2(int x){
	return x*x*x;
}
// f1 和 f2 形参均为函数指针,存放函数的首地址
int f(int (*f1)(), int (*f2)(), int x){ 
	return f2(x) - f1(x);
}
void main(){
	int i;
	i = f(f1, f2, 2);
	printf("%d\n",i);
}

  结果: 4                              

8 结构体指针

8.1 结构体变量指针

8.1.1 定义

指向结构体变量的指针。

struct 结构体类型名 *结构体指针名;
 
struct Student st,*p = &st; // p 是指向结构体变量 st 首地址的指针
  • p 不是结构体变量,不能写成 p.age,要写成(*p).age,或 p -> age
  • -> 的优先级最高,如:p->age+1,相当于(p->age)+1,即返回 p->age 的值+1

8.1.2 干活

#include<stdio.h> 
 
struct Student{
	int no;
	char name[20];
}st={101,"apple"},*p;
 
int main(){
	p = &st;
	printf("num=%d,name=%s\n",st.no,st.name);
	printf("num=%d,name=%s\n",(*p).no,(*p).name);
	printf("num=%d,name=%s\n",p->no,p->name);
}

结果:

8.2 结构体数组指针

8.2.1 定义

指向结构体数组,即将该数组的起始地址赋值给该指针变量。

struct Student  s[40], *p=s;

8.2.2 搬砖

(1)第一回合

#include<stdio.h> 
 
struct Student{
	int no;
	char name[20];
}st[2]={{101,"apple"},{102,"peach"}}, *p;
 
int main(){
	p = &st;
	printf("num=%d,name=%s\n",p->no,p->name);
	printf("num=%d,name=%s\n",(p++)->no,p->name); // 先取得p->no的值,再进行p自增1,指向下一个元素st[1] 
	printf("num=%d,name=%s\n",p->no,p->name);
}

结果:

(2)第二回合

#include<stdio.h> 
 
struct Student{
	int no;
	char name[20];
}st[2]={{101,"apple"},{102,"peach"}}, *p;
 
int main(){
	p = &st;
	printf("num=%d,name=%s\n",p->no,p->name);
	printf("num=%d,name=%s\n",(++p)->no,p->name); // 先进行p自增1,指向下一个元素st[1] ,再取成员 no 的值 
}

结果:

 

已标记关键词 清除标记
相关推荐
课程简介: 历经半个多月的时间,Debug亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课程讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统中后端应用权限的管理,其中主要涵盖了六大核心业务模块、十几张数据库表。 其中的核心业务模块主要包括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,Debug还亲自撸了额外的附属模块,包括字典管理模块、商品分类模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流程! 核心技术栈列表: 值得介绍的是,本课程在技术栈层面涵盖了前端和后端的大部分常用技术,包括Spring Boot、Spring MVC、Mybatis、Mybatis-Plus、Shiro(身份认证与资源授权跟会话等等)、Spring AOP、防止XSS攻击、防止SQL注入攻击、过滤器Filter、验证码Kaptcha、热部署插件Devtools、POI、Vue、LayUI、ElementUI、JQuery、HTML、Bootstrap、Freemarker、一键打包部署运行工具Wagon等等,如下图所示: 课程内容与收益: 总的来说,本课程是一门具有很强实践性质的“项目实战”课程,即“企业应用员工角色权限管理平台”,主要介绍了当前企业级应用系统中员工、部门、岗位、角色、权限、菜单以及其他实体模块的管理;其中,还重点讲解了如何基于Shiro的资源授权实现员工-角色-操作权限、员工-角色-数据权限的管理;在课程的最后,还介绍了如何实现一键打包上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图: 以下为项目整体的运行效果截图: 值得一提的是,在本课程中,Debug也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发中,前端到后端再到数据库,最后再到服务器的上线部署运行等流程,如下图所示:
©️2020 CSDN 皮肤主题: 精致技术 设计师:blogdevteam 返回首页