当前位置:   article > 正文

c语言指针详解

c语言指针


&是取地址符号 是纬度的
*是取值符号 是降度的

指针的定义和使用

指针就是内存中的一个地址编号,指针变量用于存储地址,指针变量也是一个变量。

#include<stdio.h>

int main(void) {
	int a = 0xaabbccdd;
	printf("%p\n", &a);//获取a的内存地址
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

内存中的每一个数据都会分配相应的地址
char 占内存字节,分配地址
int 占内存字节,分配地址

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
windows电脑在做数据存储时采用小端对齐低位数据放在低位内存地址高位数据放在高位内存地址)。
大端对齐:低位数据放在高位内存地址,高位数据放在低位内存地址。

在这里插入图片描述

计算机的字节顺序模式分为大端数据模式和小端数据模式,它们是根据数据在内存中的存储方式来区分的。小端对齐和大端对齐都是计算机中数据存储的一种方式。

数据类型* 变量名 定义指针类型变量
*变量名 = 值 通过指针间接修改变量的值

#include<stdio.h>

int main(void) {
	int a = 0;
	printf("%p\n", &a);//获取a的内存地址
	//定义指针变量存储变量地址
	int* b = &a;
	printf("%p\n", b);
	*b = 20;//通过指针间接修改变量的值
	printf("%d\n", a);
	printf("%d\n", *b);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

结果:
0000004CCAAFF874
0000004CCAAFF874
20
20

指针间接赋值

  1. 两个变量:普通变量、指针变量
  2. 建立关系 指针变量=&普通变量
  3. 通过 *运算符赋值 *指针变量=值

指针大小

int main(void) {
	int a = 0;
	int* b = &a;
	printf("%d\n", sizeof(a));//4
	printf("%d\n", sizeof(b));//64位打印的是8,32位打印的是4
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

所有的指针类型存储的都是内存地址,内存地址都是一个无符号16进制整型数。

64位操作系统下所有指针类型是8个字节大小,32位操作系统下所有指针类型是4个字节大小。所有的指针类型占的字节大小都一样

int main(void) {
	printf("%d\n", sizeof(int*));
	printf("%d\n", sizeof(char*));
	printf("%d\n", sizeof(short*));
	printf("%d\n", sizeof(long*));
	printf("%d\n", sizeof(float*));
	printf("%d\n", sizeof(double*));
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

既然地址是无符号类型的整型数(unsigned int),我们定义一个整型int不行吗?为什么要用int*

int main(void) {
	int a = 0;
	int b = &a;
	
	//*b = 666;//err: b不是指针类型,而是一个整型
	*(int*)b = 666;//强制类型转换,把一个普通变量转成int*类型

	printf("%d\n", a);//666
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

既然char* 占4字节大小,int* 也是占4个字节大小,我可以都用int* 不用char*吗?

#include<stdio.h>

int main(void) {
	char c = 'a';
	int* b = &c;
	
	printf("%p\n", b);//007AFDD3
	printf("%p\n", &c);//007AFDD3

	printf("%d\n", c);//97
	printf("%d\n", *b);//-858993567
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这里插入图片描述

野指针和空指针

野指针:指针变量指向一个未知(内存)的空间。

#include <stdio.h>

int main() {
	// 野指针
	int* b = 100;
	printf("%d\n,",*b);//可能会err: 操作野指针对应的内存空间可能报错
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在这里插入图片描述
可以看到报错了:引发了异常: 读取访问权限冲突。为什么?操作系统将0-255作为系统占用不允许读写操作。

程序中允许出现野指针,但是操作野指针对应的内存空间可能报错,有几个问题,1. 这块内存地址允不允许访问;2. 这里有没有数据;3. 这数据能不能修改 。

指针变量也是变量,是变量就可以任意赋值,不要越界即可(32位为4字节,
64位为8字节),但是,任意数值赋值给指针变量没有意义,因为这样的指针
就成了野指针,此指针指向的区域是未知(操作系统不允许操作此指针指向的
内存区域)。所以,野指针不会直接引发错误,操作野指针指向的内存区域才会出问题。在程序运行过程中,我们尽量不要把一个具体的值(或者变量的值)直接赋值给指针变量。

空指针:是指内存地址编号为0的空间。

野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量(空闲可用),c语言中,可以把NULL赋值给此指针,这样就标志此针为空指针,没有任何指针。

操作空指针对应的空间一定会报错,空指针可以用作条件判断

int main() {
	int* b = NULL;
	//*b = 666;//err: 引发了异常: 写入访问权限冲突。
	//printf("%d\n", *b);//err: 引发了异常: 读取访问权限冲突。

	if ( b == NULL ){
		printf("这是空指针");
	}

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

NULL是一个值为0的宏常量: #define NULL ((void *)0)

万能指针 void*

void*指针可以指向任意变量的内存空间,万能指针并不是真的万能,而是他能接收任意类型的数据,在通过万能指针修改变量的值时,需要转成变量对应的的指针类型。

万能指针可以直接赋值给其他类型的指针,其他类型指针赋值给万能指针的时候,最好强转一下类型才可以。

可以将所有指针类型赋值给万能指针 ,万能指针一般用作于函数形参。

#include <stdio.h>

int main() {
	printf("void*在内存中占的字节大小为: %d\n", sizeof(void*));//64位打印的是8,32位打印的是4
	void* b = NULL;

	int a = 10;
	b = (void*)&a;//指向变量时,最好转换为void*

	//*b = 87;//err:  非法的间接寻址	void本身不是数据类型
	//printf("void在内存中占的字节大小为: %d\n", sizeof(void));//0  不允许使用不完整的类型

	//使用指针变量指向的内存时,强转为int*
	*(int*)b = 87;
	printf("%d\n", a);//87
	printf("%d\n", *(int*)b);//87
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

const修饰的指针变量

第一种修饰普通变量

普通的常变量我们通过一级指针修改

#include <stdio.h>

int main() {
	const int a = 123;
	//a = 456;//err: 左值被const修饰了

	printf("a修改前:%d\n", a);//123
	//通过指针间接改变常量的值
	int* b = &a;
	*b = 456;
	printf("a修改后:%d\n", a);//456

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

可以通过指针修改const定义的常变量,但是不能修改#define定义的宏定义常量。为什么?#define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。(宏定义不分配内存,变量定义分配内存)。const常变量会在内存中分配(可以是中也可以是中)。

第二种修饰指针类型

#include <stdio.h>

int main() {
	int a = 10;
	int b = 20;
	const int* c = &a;
	printf("a的地址:%p\n", &a);//006FF838
	printf("b的地址:%p\n", &b);//006FF82C
	printf("c修改前:%p\n", c);//006FF838 = a
	c = &b;//无问题
	printf("c修改后:%p\n", c);//006FF82C = b

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
#include <stdio.h>

int main() {
	int a = 10;
	int b = 20;
	const int* c = &a;

	c = &b;//ok:可以修改指针变量的值
	//*c = 99;//err:不可以修改指针对应的内存空间的值

	printf("%d\n", *c);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这里插入图片描述

const 修饰指针类型 可以修改指针变量的值,不可以修改指针指向内存空间的值。这里的是指针对应的内存空间,指向常量的指针。
const int* c = &a;
c = &b;

PS:我们可以这样记, const 跟 * 近不能修改 * ,跟c近不能修改c,const相当于加锁一样,哪个加上锁哪个就不能修改了。

第三种修饰指针变量

const 修饰指针变量 可以修改指针指向内存空间的值,不可以修改指针变量的值。这里的c是指针变量。
int* const c = &a;
*c = 200;

int main() {
	int a = 10;
	int b = 20;
	int* const c = &a;
	//c = &b;//err
	*c = 200;//ok
	
	printf("%d\n", a);//200
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

第四种修饰指针类型修饰指针变量 (常量指针)

一级指针常变量我们通过二级指针来修改
const修饰指针类型 修饰指针变量
const int* const c = &a; 这里的c是只读指针
通过二级指针可以修改一级指针的值(相当于c),修改一级指针对应内存空间的值(*c)。

同理,三级指针可以修改二级指针的值,可以依次往前扣。

#include <stdio.h>

int main() {
	int a = 10;
	int b = 20;

	//const修饰指针类型 修饰指针变量
	const int* const c = &a;

	printf("改变之前:%d\n", *c);//10

	// c = &b;//err:表达式必须是可修改左值
	// *c = 200;//err:表达式必须是可修改左值
	int** cc = &c;

	//1
	// *cc = &b;//改变c的值
	// printf("改变之后:%d\n", *c);//20
	
	//2
	**cc = 21;//改变*c的值
	printf("改变之后:%d\n", *c);//21

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

在这里插入图片描述

指针和数组

指向数组的指针

  • 指针变量 [ 下标 ]
  • *(指针变量 + 偏移量)

数组名字是数组的首元素地址,但它是一个常量。

int arr[] = { 1,2,3,4,5 };
printf("%p\n", arr);//0000009C47B4F988
printf("%p\n", &arr[0]);//0000009C47B4F988
  • 1
  • 2
  • 3
#include <stdio.h>

int main() {
	int arr[] = { 1,2,3,4,5 };

	int* p = arr;
	for (int i = 0; i < 5; i++)
	{
		//printf("%d\n", arr[i]);//ok
		//printf("%d\n", p[i]);//ok,与上面一样结果
		
		/* 
			int*指针  指针类型+1等同于内存地址+sizeof(int)
		*/
		//printf("%d\n", *(arr+i));//ok,与上面一样结果
		printf("%d\n", *(p+i));//ok,与上面一样结果
	}

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

指针类型+1 等同于 内存地址+sizeof(类型),这里面元素并不是以内存地址编号为单位的,单位都是具体类型为单位的。

#include <stdio.h>

int main() {
	int arr1[] = { 1,2,3,4,5 };//int占4个字节
	int* p1 = arr1;
	printf("%p\n", p1);//000000D9A22FF828
	p1++;//p1=p1+1;
	printf("%p\n", p1);//000000D9A22FF82C

	char arr2[] = { 'W','X','H','Y' };//char占1个字节
	char* p2 = arr2;
	printf("%p\n", p2);//0000008CDE33F734
	p2 = p2 + 1;
	printf("%p\n", p2);//0000008CDE33F735


	double arr3[] = { 1.23,5.2,6.3,5.5 };//double占8个字节
	double* p3 = arr3;
	printf("%p\n", p3);//000000F68AB3F7C8
	p3 = p3 + 1;
	printf("%p\n", p3);//000000F68AB3F7D0

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

两指针相减,等到的结果是两个指针的偏移量(步长),所有的指针类型相减都是int类型。

指针+1 相当于 +sizeof(类型)
指针-1 相当于 -sizeof(类型)
指针相减 = 变量值(地址编号)相减/sizeof(类型)

int main() {
	int arr1[] = { 1,2,3,4,5 };//int占4个字节
	int* p1 = arr1;
	printf("%p\n", p1);//00000086013BF8C8
	p1++;
	printf("%p\n", p1);//00000086013BF8CC

	int step = p1 - arr1;//C8-CC=4 ,相当于 4/sizeof(int)=1
	printf("步长:%d\n",step);//1


	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
指针跟数组的不同点
一个是变量,一个是常量
空间占的字节大小不一样

数组作为函数参数会退化为指针,丢失了数组的精度(数组元素个数)

#include <stdio.h>

//数组作为函数参数会退化为指针,丢失了数组的精度
void BubbleSort(int arr[5]) {//arr[] 或者 arr[100] 得到的结果都一样
	int length = sizeof(arr)/sizeof(arr[0]);
	printf("长度:%d\n", length);//1
	printf("%d\n",sizeof(arr));//4
	
}

int main() {
	int arr[5] = { 1,2,3,4,5 };
	
	BubbleSort(arr);
	
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

既然退化成指针了,我们可以写成int* arr,他们是等价的

#include <stdio.h>


/* 冒泡排序 */
//数组作为函数参数会退化为指针,丢失了数组的精度
void BubbleSort(int* arr, int length) {

	for (int i = 0; i < length - 1; i++)
	{
		for (int j = 0; j < length - 1 - i; j++)
		{
			/*if (arr[j]>arr[j+1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}*/
			if (*(arr + j) > *(arr + j + 1))
			{
				int temp = *(arr + j);
				*(arr + j) = *(arr + j + 1);
				*(arr + j + 1) = temp;
			}
		}
	}
}


int main() {
	int arr[6] = { 6,2,7,3,4,5 };
	int length = sizeof(arr) / sizeof(arr[0]);
	BubbleSort(arr, length);

	for (int i = 0; i < length; i++)
	{
		printf("%d\n", arr[i]);
	}

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

结果:234567

如果我们指针去操作数组元素情况,最好写成数组样式,简单明了,一般情况不写指针样式。

指针运算

指针运算跟指针类型对应的基本类型有关。

指针加减运算

(1)加法运算

  • 指针计算并不是简单的整数相加
  • 如果是int*,+1的结果是增加一个int的大小
  • 如果是char*,+1的结果是增加一个char的大小
#include <stdio.h>

int main() {
	int arr1[] = { 1,2,3,4,5 };//int占4个字节
	int* p1 = arr1;
	printf("%p\n", p1);//000000D9A22FF828
	p1++;//p1=p1+1;
	printf("%p\n", p1);//000000D9A22FF82C
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

举个例子: 字符串拷贝的操作
1.通过数组操作

#include<stdio.h>

//字符串拷贝的操作
void CopyStr(char* dest, char* ch) {
	int i = 0;
	//while (ch[i] != '\0')
	//while (ch[i] != 0 )//等价于上一行
	while (ch[i]) //等价于上一行
	{
		dest[i] = ch[i];
		i++;
	}
	dest[i] = 0;//其实就是'\0'
}

int main(void) {

	char ch[] = "hello world";
	char dest[100];

	CopyStr(dest, ch);
	printf("%s\n", dest);

	return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

2.指针加减运算

void CopyStr(char* dest, char* ch) {
	int i = 0;
	while (*(ch + i)) {
		*(dest + i) = *(ch + i);//注意一定要加括号,因为优先级别的问题
		i++;
	}

	*(dest + i) = 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

3.把2再简化,还能这样写

void CopyStr(char* dest, char* ch) {
	
	while (*ch) {
		*dest = *ch;
		dest++;//指针+1 相当于指向数组下一个元素 内存地址变化了sizeof(char)
		ch++;
	}

	*dest = 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

4.上面都还不是指针的最终版本,接下来我们再写个不一样的

void CopyStr(char* dest, char* ch) {
	while (*dest++ = *ch++);
}
  • 1
  • 2
  • 3

第一个算dest++、ch++,但其实是最后一个算的;然后是取值*dest、ch,接着给dest赋值;赋值完之后到这里表达式已经计算完成了,虽然++还没加但是表达式已经完成了,赋值完之后作为while循环的条件。

总的来说就是,第一步 取值*dest、*ch,第二步 *dest=*ch,第三步 把这个值作为条件判断,赋值哪个就是哪个进行判断,如果是0,这时候0已经赋值过去给*dest,不满足循环结束循环,最后一步 dest++、ch++。

(2)减法运算

  • 跟加法运算差不多
#include<stdio.h>

int main(void) {
	int arr[] = { 1,2,3,4,5,6 };
	int* p;
	//arr[-1]//数组小标越界
	p = &arr[3];

	//指针操作数组时,下标允许是负数
	printf("%d\n",p[-2]);//相当于*(p-2)	,结果2

	printf("%p\n", arr);//0113FCC0 
	printf("%p\n", p);//0113FCCC 地址相差12个

	int step = p - arr;// 12 / sizeof(int)=偏移量
	printf("偏移量:%d\n",step);//3

	p--;//指针的加减运算和指针的类型有关
	p--;
	p--;
	printf("%p\n", p);//0113FCC0

	return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

指针加减偏移量是允许的;减法可以减指针,但是没有意义,此外还能用 > 、?、&& 等等

#include<stdio.h>

int main(void) {
	//指针和运算符的操作
	int arr[] = {1,2,3,4,5,6,7,8};
	int* p = &arr[3];

	//野指针
	//指针加减偏移量是允许的,但两个指针相加乘除是不允许的,取余也不行
	//p = p + arr;//err: “ + ”: 不能添加两个指针。两个指针相加一定是野指针,就没有意义了
	 p = p - arr;//ok  减法可以减指针
	//p = p * arr;//err
	//指针乘除偏移量是不允许的,取余也不行
	//p = p * 6;//err
	//p = p / 6;//err

	if (p > arr) {
		printf("真\n");
	}

	if (p && arr) {
		printf("指针p有值\n");
	}
	//三目运算符
	printf("%s\n", p > arr?"p大于arr":"p小于arr");

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

指针数组

每一个元素都是一个指针,指针数组是一个二维数组模型。

#include <stdio.h>

int main() {
	int a = 87, b = 65, c= 89;
	//定义数组 数据类型 数据名[元素个数]={值1,值2,值3 }
	int* arr[3] = {&a,&b,&c};
	//printf("%d\n",*arr[0]);//87

	for (int i = 0; i < sizeof(arr)/sizeof(arr[0]); i++)
	{
		printf("%d\n", *arr[i]);
	}
	printf("指针数组大小:%d\n", sizeof(arr));//12
	printf("指针元素大小:%d\n", sizeof(arr[0]));//4

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

问1: 为什么我把int* arr[3] = {&a,&b,&c};换成char* arr[3] = {&a,&b,&c};
得到的结果是一样的?

87
65
89
指针数组大小:12
指针元素大小:4
  • 1
  • 2
  • 3
  • 4
  • 5

答: *是取值, &是取地址,因为两个不同的数组保存的是同一个地址值,所以一样。

指针数组是一个特殊的二维数组模型

#include <stdio.h>
int main() {
	//指针数组里面的元素存储的是指针
	int a[] = { 1,2,3 };
	int b[] = { 4,5,6 };
	int c[] = { 7,8,9 };

	int* arr[] = { a,b,c };

	//printf("%p\n",a);//004FFC40
	//printf("%p\n",arr[0]);//004FFC40
	//printf("%p\n",&a[0]);//004FFC40

	//注意
	//printf("%p\n", arr);//004FFC04 跟a不相等
	//printf("%p\n", &arr[0]);//004FFC04
	/* arr是指针数组的首地址  */

	//for (int i = 0; i < sizeof(arr)/sizeof(arr[0]); i++)
	//{
	//	printf("%d\n",*arr[i]);//1 4 7
	//}

	//指针数组是一个特殊的二维数组模型
	//printf("%d\n", a[1]);//2
	//printf("%d\n", arr[0][1]);//2

	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		for (int j = 0; j < 3; j++)
		{
			//二维数组
			//printf("%d\t",arr[i][j]);
			//printf("%d\t", *(arr[i] + j));//arr[i]地址+偏移量j
			// *(arr+i)=arr[i]
			printf("%d\t", *(*(arr + i) + j));
		}
		puts("");
	}

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

指针数组对应于二级指针,最好还是用二维数组吧,比较好看。

puts(char* s) 输出更简洁、更方便。而且使用 puts() 函数连换行符 ‘\n’ 都省了,使用 puts() 显示字符串时,系统会自动在其后添加一个换行符s可以是字符指针变量名字符数组名,或者直接是一个字符串常量。功能是将字符串输出到屏幕。输出时只有遇到 ‘\0’ 也就是字符串结束标志符才会停止。

puts(“”);相当于printf(“\n”);

多级指针

C语言允许有多级指针存在,在实际的程序中一级指针最常用,其次是二级指针。二级指针就是指向一个一级指针变量地址的指针。

#include<stdio.h>

int main() {
	int a[] = { 1,2,3 };
	int b[] = { 4,5,6 };
	int c[] = { 7,8,9 };
	int* arr[] = { a,b,c };
	//指针数组和二级指针建立关系
	int** p = arr;

	printf("%d\n", **p);//1
	//二级指针加偏移量 相当于跳过了一个一维数组大小
	printf("%d\n", **(p + 1));//4 = arr[1][0]
	//一级指针加偏移量相当于跳过一个元素
	printf("%d\n", *(*p + 1));//2 = arr[0][1]
	printf("%d\n", *(*(p + 1) + 1));

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
#include<stdio.h>

int main() {
	int a = 10;
	int b = 20;
	int* p = &a;
	int** pp = &p;
	int*** ppp = &pp;
	//*ppp == pp == &p
	//**ppp == *pp == p == &a
	//***ppp == **pp == *p == a
	 
	//*pp = &b;//等价于p=&b
	//printf("%d\n",*p);//20

	**pp = 666;
	printf("%d",a);//666

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

指针和函数

值传递和地址传递

值传递:形参不影响实参的值。

#include<stdio.h>

void replace(int a, int b) {
	int temp = a;
	a = b;
	b = temp;
}

int main() {
	int a = 10;
	int b = 20;

	//值传递
	replace(a, b);

	printf("%d,%d", a, b);//10,20

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

地址传递:形参可以改变实参的值。

#include<stdio.h>

void replace(int* a, int* b) {
	int temp = *a;
	*a = *b;
	*b = temp;
}

int main() {
	int a = 10;
	int b = 20;

	//地址传递
	replace(&a, &b);

	printf("%d,%d", a, b);//20,10

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

数组作为函数参数

数组名做函数参数,函数的形参会退化成指针丢失精度,因为一级指针二级指针没办法统一,所以是丢失精度。需要传递 元素个数(或叫精度)。

  1. 用数组样式
#include<stdio.h>

//追加字符串
void AddStr(char* target,char* s) {
	int i = 0;
	for (;target[i]!=0;i++) {}
	//printf("%d\n", i);//5
	//printf("%d\n", strlen(target));//5,相当于strlen(target)
	int j = 0;
	while (s[j] != 0) {
		target[i + j] = s[j];
		j++;
	}
	//target[i + j] = '\0';//默认是0
}

int main() {
	char target[100] = "hello";
	char s[] = "world";

	AddStr(target,s);

	printf("%s\n", target);//helloworld

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  1. 用指针样式
void AddStr(char* target,char* s) {
	int i = 0;
	for (;*(target+i)!=0;i++) {}
	//printf("%d\n", i);//5
	//printf("%d\n", strlen(target));//5,相当于strlen(target)
	int j = 0;
	while (*(s+j)!= 0) {
		*(target+i + j) = *(s+j);
		j++;
	}
	//target[i + j] = '\0';//默认是0
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  1. 指针++
void AddStr(char* target,char* s) {
	while (*target)target++;
	while (*s) {
		*target = *s;
		s++;
		target++;
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

再优化一下

void AddStr(char* target,char* s) {
	while (*target)target++;
	while (*target++ = *s++);
}
  • 1
  • 2
  • 3
  • 4

字符串去空格

#include<stdio.h>

//字符串去空格
void RemoveSpace(char* s)
{
	char newStr[100] = { 0 };
	char* temp = newStr;
	int i = 0;
	int j = 0;
	while (s[i] != '\0')
	{
		if (s[i] != ' ') {
			newStr[j] = s[i];
			j++;
		}
		i++;
	}
	printf("newStr:%s\n",newStr);//helloxy
	while (*s++ = *temp++);
}

int main() {
	char s[] = "   h  e l lo x   y ";
	RemoveSpace(s);
	printf("s:%s\n", s);//helloxy
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

其实还能优化,思路是找到不是空格就覆盖前面的值。

#include<stdio.h>

//字符串去空格
void RemoveSpace(char* s)
{
	//用来遍历字符串
	char* ftemp = s;
	//记录非空格字符串的
	char* rtemp = s;
	while (*ftemp)
	{
		if (*ftemp != ' ') {
			*rtemp = *ftemp;
			rtemp++;
		}
		ftemp++;
	}
	*rtemp = 0;
}

int main() {
	char s[] = "   h  e l lo x   y ";
	RemoveSpace(s);
	printf("s:%s\n", s);//helloxy
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

指针作为函数的返回值

#include<stdio.h>

char* StrChr(char* s, char ch) {
	int i = 0;
	while (s[i])
	{
		if (s[i] == ch) {
			return &s[i];
		}
		i++;
	}
	return NULL;
}
int main() {
	char s[] = "hello xy";
	char* p = StrChr(s,'x');
	if (p==NULL) {
		printf("未找到\n");
	}
	else {
		printf("%s\n",p);
	}
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

结果:xy

字符串查找字符串

#include <stdio.h>

char* QueryStr(char* src,char* dest) {
	//临时指针
	char* fsrc=src;//给源字符串src遍历用的
	char* rsrc=src;//记录相同字符串首地址
	char* tdest = dest;
	while (*fsrc) {
		rsrc = fsrc;
		while (*fsrc == *tdest && *fsrc!='\0') {//如果两字符串都是末尾"llo\0" "llo\0"会数组下标越界
			fsrc++;
			tdest++;
		}
		if (*tdest == '\0') {
			return rsrc;
		}
		//如果不相同,我们要回滚
		tdest = dest;//目标字符串更新到起始位置
		fsrc = rsrc;
		fsrc++;
	}
	return NULL;
}

int main() {
	char src[] = "hello xy";
	char dest[] = "llo";

	char* p = QueryStr(src,dest);
	printf("%s\n",p);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

指针和字符串

栈区字符串和数据区字符串的区别

#include <stdio.h>

int main() {
	char s[] = "hello xy";
	char* p = "hello xy";
	
	s[2] = 'm';
	//p[2] = 'm';//err:写入访问权限冲突。
	//*(p+2) = 'm';//err:写入访问权限冲突。

	printf("%s\n", s);//hemlo xy
	printf("%s\n", p);//hello xy
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

为什么会报错?原因是因为创建字符串存储的位置是不同的。
char s[] = “hello xy”;存储的是栈区字符串
char* p = “hello xy”;是数据区下的常量区字符串,指针指向的是字符串常量区的地址。例如printf(“hello xy”);这个字符串没有一个名字去标志字符串所在的位置,所以这个字符串所在的位置就是数据区下的常量区字符串。这个字符串常量区的内容是不允许修改的。

#include <stdio.h>

int main() {
	char* p1 = "hello xy";
	char* p2 = "hello xy";
	printf("%p\n", p1);
	printf("%p\n", p2);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

结果:地址一样:00A78B40。因为这两个字符串是完全一致并且不能修改,在内存中做一份就可以了,p1和p2同时指向字符串常量区的地址,并不是p1和p2不能修改,而是"hello xy"对应的区域中的数据不能修改,他们地址相同而且都不允许修改,他们是只读的,多个指针指向同一地址是可以的。

字符串数组

#include <stdio.h>

int main() {
	//指针数组:每个单元存储一个指针,合起来就是一个指针数组
	//第一种方式
	char s1[] = "favorite";
	char s2[] = "person is";
	char s3[] = "xy";
	char* arr1[] = { s1,s2,s3 };	

	//字符串数组
	//第二种
	char* arr2[] = { "favorite","person is","xy" };

	//打印
	for (int i = 0; i < 3; i++)
	{
		printf("%s ", arr1[i]);
	}
	puts("");
	for (int i = 0; i < 3; i++)
	{
		printf("%s ", arr2[i]);
	}

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

区别:arr1里面的字符串可以修改,arr2的字符串(是常量字符串,把地址放到了arr2里)不能修改。

字符指针作为函数参数

#include <stdio.h>

int getstr_length(char* ch ) {
	//计算字符串有效长度
	int i = 0;
	while (ch[i]!='\0')
	{
		i++;
	}
	return i;
}

int getstr_length2(char* ch) {
	//计算字符串有效长度
	char* temp = ch;
	while (*temp != '\0')temp++;

	return temp-ch;
}

int main() {
	char* ch = "hello xy";
	//int length = getstr_length(ch);//8
	int length = getstr_length2(ch);//8
	printf("长度: %d\n",length);

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

getstr_length 和 getstr_length2两个都行,如果不希望改变参数里面的内容可以写成getstr_length2(const char* ch)

主函数参数

我们平常主函数都是 main(void) 或者干脆什么都不写 main(),但是 正常情况下主函数是有两个参数的。我们程序在运行起来是通过主函数开始执行,可以对主函数进行传参的操作,我们cmd运行程序后面有时候会带一些参数,我们可以自己去捕获一下。

假设命令是 gcc -o main main.c
==> 4个参数
int argc 表示传递参数的个数
char* argv[] = { “gcc” , “-o” ,“main” ,“main.c” } 表示参数具体内容

#include <stdio.h>

int main(int argc,char* argv[]) {
	printf("%d\n",argc);
	for (int i = 0; i < argc; i++)
	{
		printf("%s\n", argv[i]);
	}

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这个代码我们需要单独去外边(cmd)运行一下

gcc -o main.exe main.c -std=c99
D:\cCode\VSstudyCYuYan\day08\main.exe  空格之后随便输字符串
  • 1
  • 2

-std=c99 c99库,因为我在循环内部定义参数应该使用c99编辑器,for (int i = 0;

在这里插入图片描述
可以看到输出了D:\cCode\VSstudyCYuYan\day08\main.exe,这个是我们的运行程序,运行程序也算作参数的一个,它自己能把自己捕获到,算作字符串中的第一个。我输入的命令是6个字符串。

我们还可以加个判断,如果缺少参数我们就不给他运行下去

#include <stdio.h>

int main(int argc,char* argv[]) {
	//printf("%d\n",argc);

	if (argc < 3) {
		printf("需要2个参数\n");
		return -2;
	}

	for (int i = 0; i < argc; i++)
	{
		printf("%s\n", argv[i]);
	}

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

在这里插入图片描述
argv[]不是在其他程序调用传递的,而是在执行应用程序的时候传递的。很多程序会有这个内容,但是用不到它,写起来也麻烦,有时候我们就不写。如果你想传递参数的情况下,用这两个参数去传递去接收。比如你要想编译程序的情况下,你就知道哪个是源程序哪个是其他程序。

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/191916
推荐阅读
相关标签
  

闽ICP备14008679号