一、指针
int a = 10;
a = 20;
变量a占用4个字节的内存空间,我们要想改变这个段内存空间的值,需要将这个变量的值改成20,这个是通过变量的别名来访问内存的方法。我们再来看看一段代码:
#include
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进行赋值是等效的。
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,也就是说指针的大小是固定的。
*号类似一把钥匙 号类似一把钥匙,通过这把钥匙可以通过这把钥匙可以打开内存,,读取内存中的值和设置内存的值。
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;
}
运行结果:
可以看到传值调用没法改变变量的值,传址调用可以改变变量的值,这是因为传值调用的时候函数内部访问的是栈上的数据,调用完就销毁了,传址调用函数内部直接访问内存地址,可以直接修改地址的值。
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类型,这个在数据封装中是非常有用的。
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
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;
}
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)当两个指针指向的元素不在同一个数组中时,结果未定义
二、数组
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为数组的元素个数。
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)数组是一段连续的内存空间
(2) 数组的空间大小为sizeof(array_type) * array_size
(3) 数组名可看做指向数组第一个元素的常量指针
数组的访问
(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;
}
void test_array4(int a[5])
{
printf("sizeof(a) = %d\n", sizeof(a));
}
运行结果:
可以看到数组作为形参时被转换成了指针,指针的大小就是8或者4
三、总结
(1)指针和数组都可以看做一种数据类型,指针指向对应的数据类型,数组的数据类型是type [num]
(2)指针声明时只分配了用于容纳指针的4或者8字节空间
(3)在作为函数参数时,数组参数和指针参数等价
(4)数组名在多数情况可以看做常量指针, 其值不能改变
四、代码分享:
#include
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