[C语言]指针和数组(一)

一、指针

  1. 访问内存是否只能通过变量来进行访问
    我们知道程序中的变量只是一段存储空间的别名,那么是不是必须通过这个别名才能使用这段存储空间?
    我们来看以前访问内存空间的方法:
int a = 10;
a = 20;

变量a占用4个字节的内存空间,我们要想改变这个段内存空间的值,需要将这个变量的值改成20,这个是通过变量的别名来访问内存的方法。我们再来看看一段代码:

#include <stdio.h>

void test_pointer1()
{
	int a = 10;
	int* pa = &a;
	
	a = 20;
	printf("a = %d\n", a);
	
	*pa = 30;
	printf("a = %d\n", a);
}

int main()
{
	test_pointer1();
	return 0;
}

来看看运行的结果:
在这里插入图片描述
可以看到我们通过*pa和a进行赋值是等效的。

  1. 指针的本质
    (1) 指针在本质上也是一个变量
    (2) 指针需要占用一定的内存空间
    (3) 指针用于保存内存地址的值
    我们来理解一下:
    首先任何一种数据类型都可以理解为一种变量,指针也是一样的,其次既然指针是一种变量,那它就具有内存空间;既然指针也有内存空间,那它的内存空间应该要存放数据,这个数据就是和他对应变量内存的地址。我们来看看一段代码:
void test_pointer2()
{
	int b = 10;
	int* pb = &b;
	
	printf("sizeof(pb) = %d\n", sizeof(pb));
	printf("pb = 0x%x\n", pb);
}

运行结果:
在这里插入图片描述
我们可以看到指针的大小为8(有可能是4,取决于编译器是32位还是64位),指针的值是变量b的地址。

我们来想一下,既然指针也有大小,那是不是不同类型的指针大小不一样,我们来看一段代码:

struct C {
	int a;
	int b;
	short c;
};
void test_pointer3()
{
	int a = 10;
	char b = 20;
	
	struct C c;
	c.a = 30;
	c.b = 20;
	c.c = 10;
	
	int* pa = &a;
	char* pb = &b;
	struct C* pc = &c;
	
	printf("sizeof(pa) = %d, sizeof(pb) = %d, sizeof(pc) = %d\n", sizeof(pa), sizeof(pb), sizeof(pc));
}

这里我们分别看看int char和struct C类型的指针的大小,运行结果:
在这里插入图片描述
可以看到不同类型的指针大小都是8,也就是说指针的大小是固定的。

  1. 指针*的含义
    (1) 在指针声明时,,*号表示所声明的变量为指针 号表示所声明的变量为指针
    (2)在指针使用时,,*号表示取指针所指向的内存空间中的值
    注意:指针声明的时候可以不初始化,但是一般设置初始值为NULL。
    在这里插入图片描述

*号类似一把钥匙 号类似一把钥匙,通过这把钥匙可以通过这把钥匙可以打开内存,,读取内存中的值和设置内存的值。

  1. 形参中的传值和传址调用
    (1) 指针是变量,,因此可以声明指针参数
    (2) 当一个函数体内部需要改变实参的值,则需要使用指针参数 (3)函数调用时实参值将复制到形参
    (4) 指针适用于复杂数据类型作为参数的函数中
    我们来看一下传值和传址的区别:
void test_pointer4(int* m, int n)
{
	*m = 10;
	n = 20;
}
int main()
{
	int m = 1;
	int n = 1;
	test_pointer4(&m, n);
	
	printf("m = %d, n = %d\n", m, n);
	return 0;
}

运行结果:
在这里插入图片描述
可以看到传值调用没法改变变量的值,传址调用可以改变变量的值,这是因为传值调用的时候函数内部访问的是栈上的数据,调用完就销毁了,传址调用函数内部直接访问内存地址,可以直接修改地址的值。

  1. void*
    前面我们了解到指针也是一种变量,具有内存和地址,那么void也是一种数据类型,也是具有大小和地址的。**void类型可以指向任何一种数据类型的地址**,我们来看一段代码:
struct STD{
	char name[20];
	int number;
};
void test_pointer5(void* std)
{
	struct STD* s = (struct STD*)std;
	printf("name: %s\n", s->name);
	printf("number: %d\n", s->number);
}
int main()
{
	struct STD std;
	
	stpcpy(std.name, "xiaoming");
	std.number = 10;

	test_pointer5(&std);
	return 0;
}

运行结果:
在这里插入图片描述
我们可以看到作为形参的void*可以指向struct STD类型,这个在数据封装中是非常有用的。

  1. const和指针结合
    const修饰指针:
    修饰指针有几种情况,我们先看看以下代码:
const int* p; //p可变,p指向的内容不可变
int const* p; //p可变,p指向的内容不可变
int* const p; //p不可变,p指向的内容可变
const int* const p; //p和p指向的内容都不可变

可以看到const修饰谁,谁就是不可变的,比如const int* p,const修饰int类型,标明p指向的内容不可变,int* const p,const修饰p,标明p不可以变,也就是地址不可变。

我们看一个代码例子:

#include <stdio.h>



void test_const()
{
	const int a = 10;
	//a = 20; //报错, 对常量赋值
	int b = 10;
	int c = 20;
	
	const int* p1; //p可变,p指向的内容不可变
	int const* p2; //p可变,p指向的内容不可变
	int* const p3; //p不可变,p指向的内容可变
	const int* const p4; //p和p指向的内容都不可变
	
	p1 = &b;
	*p1 = 11; //报错,p指向的内容不可变
	
	p2 = &b;
	*p2 = 11; //报错,p指向的内容不可变
	
	p3 = &b;
	p3 = &c; //报错,p不可变
	
	p4 = &b;
	*p4 = 11; //报错,p指向的内容不可变
	p4 = &c; //报错,p不可变
}

int main()
{
	test_const();
	
	return 0;
}

编译的时候可以看到:
在这里插入图片描述
编译器遵循被const修饰的指针规则

  1. 指针的运算
    在讲指针运算的时候我们要始终理解指针就是一种数据类型,它具有地址和大小。
    我们先来看看指针的加减运算:
void test_pointer6(void)
{
	int a;
	int* pa = &a;
	printf("pa: 0x%x\n", pa);
	pa++;
	printf("pa: 0x%x\n", pa);
	pa--;
	printf("pa: 0x%x\n", pa);
}

运行结果:
在这里插入图片描述
可以看到指针加1的时候pa的值加了4,减1的时候pa减4,这样我们可以得到:
p +(-) n; <==> (unsigned int)p +(-) n*sizeof(*p);

结论:
当指针p指向一个同类型的数组的元素时 :p+1 将指向当前元素的下一个元素;p-1将指向当前元素的上一 个元素

指针之间的运算:

void test_pointer7(void)
{
	int a;
	int b;
	int* pa = &a;
	int* pb = &b;
	
	int diff = 0;
	
	printf("pb: 0x%x\n", pb);
	printf("pa: 0x%x\n", pa);
	
	diff = pb - pa;
	
	printf("diff: %d\n", diff);

}

运行结果:
在这里插入图片描述
这里可以看到指针相减并不是直接两个地址的的值直接相减,还需要除以数据类型的大小:
p1 – p2; <==>( (unsigned int)p1 - (unsigned int)p2) / sizeof(type);

注意:
(1)指针之间只支持减法运算,且参与运算的指针类型必须相同
(2)只有当两个指针指向同一个数组中的元素时,指针指针相减才有意义,其意义为指针所指元素的下标差
(3)当两个指针指向的元素不在同一个数组中时,结果未定义

二、数组

  1. 数组的概念
    数组是相同类型的变量的有序集合
    int a[5];
    在这里插入图片描述

  2. 数组的大小
    (1)数组在一片连续的内存空间中存储元素
    (2)数组元素的个数可以显示或隐式指定

int a[5] = {1,3};
int b[] = {2,4};

我们来想两个问题:
a[2], a[3], a[4]的值是多少? b包含了多少个元素?
直接来一段代码:

void test_array()
{
	int a[5] = {1, 3};
	int b[] = {2, 4};
	
	printf("sizeof(a) = %d\n", sizeof(a));
	printf("a[2] = %d, a[3] = %d, a[4] = %d\n", a[2], a[3], a[4]);
	
	printf("sizeof(b) = %d\n", sizeof(b));
	printf("b[0] = %d, b[1] = %d, b[2] = %d\n", b[0], b[1], b[2]); //这里可能会出错,出错的原因是内存越界
}

看看结果:
在这里插入图片描述
可以得到结论,数组没有被初始化的元素被系统初始化为0,数组的大小为数组sizeof(type) x num,type是数组元素的数据类型,num为数组的元素个数。

  1. 数组地址和数组名
    (1)数组名代表数组首元素的地址
    (2)数组的地址需要用取地址符&才能得到
    (3)数组首元素的地址值与数组的地址值相同
    (4)数组首元素的地址与数组的地址是两个不同的概念
    我们来看一段代码:
void test_array2()
{
	typedef int(AINT5)[5];
	
	int a[5] = {1, 2, 3, 4, 5};
	AINT5* pa = &a;
	pa++;
	
	printf("a = 0x%x, &a[0] = 0x%x\n", a, &a[0]);
	printf("pa++ = 0x%x\n", pa);
}

数组本质上讲还是一种数据类型,比如int a[5],它的数组类型是int [5],这样我们也可以定义一个指针来指向它:typedef int(AINT5)[5];
我们来看看运行的结果:
在这里插入图片描述

可以看到a和&a[0]是一样的,指向数组a的指针加1的时候,值变化了0x14,也就是5 x sizeof(int),我们可以得到结论是数组也是可以看做一种数据类型,类型为type [num],type为基本数据,num为数组元素个数。
结论:
(1)a为数组是数组首元素的地址
(2) &a为整个数组的地址
(3)a和&a的意义不同其区别在于指针运算
a + 1<> (unsigned int)a + sizeof(*a)
&a + 1<
> (unsigned int)(&a) + sizeof(*&a)

  1. 数组的本质
    (1)数组是一段连续的内存空间
    (2) 数组的空间大小为sizeof(array_type) * array_size
    (3) 数组名可看做指向数组第一个元素的常量指针

  2. 数组的访问
    (1)以下标的形式访问数组中的元素
    (2)以指针的形式访问数组中的元素

void test_array3()
{
	int a[5] = {1, 2, 3, 4, 5};
	
	a[0] = 2;
	a[4] = 1;
	
	*(a+0) = 2;
	*(a+4) = 1;
}
  1. 数组作为形参
    C语言中,,数组作为函数参数时, 编译器将其编译成对应的指针
    void f(int a[]); <> void f(int* a);
    void f(int a[5]); <
    > void f(int* a);
void test_array4(int a[5])
{
	printf("sizeof(a) = %d\n", sizeof(a));
}

运行结果:
在这里插入图片描述
可以看到数组作为形参时被转换成了指针,指针的大小就是8或者4

三、总结
(1)指针和数组都可以看做一种数据类型,指针指向对应的数据类型,数组的数据类型是type [num]
(2)指针声明时只分配了用于容纳指针的4或者8字节空间
(3)在作为函数参数时,数组参数和指针参数等价
(4)数组名在多数情况可以看做常量指针, 其值不能改变

四、代码分享:

#include <stdio.h>

struct C {
	int a;
	int b;
	short c;
};

struct STD{
	char name[20];
	int number;
};

void test_pointer1()
{
	int a = 10;
	int* pa = &a;
	
	a = 20;
	printf("a = %d\n", a);
	
	*pa = 30;
	printf("a = %d\n", a);
}

void test_pointer2()
{
	int b = 10;
	int* pb = &b;
	
	printf("sizeof(pb) = %d\n", sizeof(pb));
	printf("pb = 0x%x\n", pb);
}

void test_pointer3()
{
	int a = 10;
	char b = 20;
	
	struct C c;
	c.a = 30;
	c.b = 20;
	c.c = 10;
	
	int* pa = &a;
	char* pb = &b;
	struct C* pc = &c;
	
	printf("sizeof(pa) = %d, sizeof(pb) = %d, sizeof(pc) = %d\n", sizeof(pa), sizeof(pb), sizeof(pc));
}

void test_pointer4(int* m, int n)
{
	*m = 10;
	n = 20;
}

void test_pointer5(void* std)
{
	struct STD* s = (struct STD*)std;
	printf("name: %s\n", s->name);
	printf("number: %d\n", s->number);
}

void test_pointer6(void)
{
	int a;
	int* pa = &a;
	printf("pa: 0x%x\n", pa);
	pa++;
	printf("pa: 0x%x\n", pa);
	pa--;
	printf("pa: 0x%x\n", pa);
}

void test_pointer7(void)
{
	int a;
	int b;
	int* pa = &a;
	int* pb = &b;
	
	int diff = 0;
	
	printf("pb: 0x%x\n", pb);
	printf("pa: 0x%x\n", pa);
	
	diff = pb - pa;
	
	printf("diff: %d\n", diff);

}

void test_array1()
{
	int a[5] = {1, 3};
	int b[] = {2, 4};
	
	printf("sizeof(a) = %d\n", sizeof(a));
	printf("a[2] = %d, a[3] = %d, a[4] = %d\n", a[2], a[3], a[4]);
	
	printf("sizeof(b) = %d\n", sizeof(b));
	printf("b[0] = %d, b[1] = %d, b[2] = %d\n", b[0], b[1], b[2]); //这里可能会出错,出错的原因是内存越界
}

void test_array2()
{
	typedef int(AINT5)[5];
	
	int a[5] = {1, 2, 3, 4, 5};
	AINT5* pa = &a;
	pa++;
	
	printf("a = 0x%x, &a[0] = 0x%x\n", a, &a[0]);
	printf("pa++ = 0x%x\n", pa);
}

void test_array3()
{
	int a[5] = {1, 2, 3, 4, 5};
	
	a[0] = 2;
	a[4] = 1;
	
	*(a+0) = 2;
	*(a+4) = 1;
}

void test_array4(int a[5])
{
	printf("sizeof(a) = %d\n", sizeof(a));
}

int main()
{
	int m = 1;
	int n = 1;
	struct STD std;
	int a[5];
	
	stpcpy(std.name, "xiaoming");
	std.number = 10;
	
	test_pointer1();
	test_pointer2();
	test_pointer3();
	test_pointer4(&m, n);
	printf("m = %d, n = %d\n", m, n);
	test_pointer5(&std);
	test_pointer6();
	test_pointer7();
	
	test_array1();
	test_array2();
	test_array4(a);
	
	return 0;
}

makefile:

CC = gcc
CFLAGS = -g -Wall -O
main:test.o
	$(CC) $^ -o $@
%.o:%.c
	$(CC) $(CFLAGS) -c $^
clean:
	rm -rf test.o
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值