C语言程算课:指针与结构体的一些理解

指针:

C语言指针运算中,&是取地址符号,*是解地址符号,是一对逆运算,对于一个变量a和一个指针p有下面两式成立:

*(&a) = a;
&(*p) = p;

我们一般认为指针的定义类型是 int* ,因此int* p表示定义指针p,* 的位置随意,可以写成int *p。

这里p就是指针,而不是说*p是指针。

打印地址:

int main() {
	int a = 5;
	printf("%p %p %d %d\n", &a, a, &a, a);
	return 0;
}

//output: 0062ff1c 00000005 6487836 5
//%p用于打印地址,%d也能输出,实质上都是输出整型。
//但%p优点是以16进制数字输出,并且自动补全到8位,共有16^8=2^32位地址。

int main() {
	int a = 5, b = 8;
	printf("%p %p %p %d", &a+1, (&a)+1, &a, *(&a+1) );	
    return 0;
}

//output: 0062ff1c 0062ff1c 0062ff18 8

上面的输出结果解释:&和*作为指针运算符,优先级比加减乘除高,所以第一个和第二个写法输出是一样的,一个int型占四个字节,因此加一地址加4(对比输出三、四)
在栈空间中连续定义的变量地址是连续的,&a+1是指向b的指针,解地址可以得到b的值(不建议这么写,这是一种越界行为)。

下面的例子,继续帮助理解优先级不同导致的输出结果不同:

int main() {
	int a[6];
	int* p = a;              //a为一个数组时,a本身就是地址,不用加&。
	a[0] = 114514; a[1] = 1919810;
	printf("%d %d\n", *(p+1), *p+1);
	return 0;
}
//output: 1919810 114515
//第一个是输出了a[1],第二个是输出了a[0]+1。

既然提到数组那就补充些二维数组与指针的知识:

int a[5][5];
int* p;

p = &a[3][2];    //单个元素取地址是指针。
p = a[3];        //a加上第一维,就是a[3]这一行五个元素的指针,a[3]是五个元素的第一个元素地址。直接赋值,不用取地址。
p = *a;          //a什么都不加,那就是指针的指针(这东西是可以嵌套的)。对a取地址,就是第一行第一个元素的地址。
printf("%d", *(*a) );    //对a取两次地址,输出第一个元素的值。
printf("%d", *(*(a+2)+3) );  //取出第三行第四个元素。

下面是二维数组的指针:

int* p1[8];       //这是定义了8个int型指针
int (*p2) [5];    //这是定义了一个指向数组的指针(数组长度为5),因为数组的返回值本身就是指针,所以这个写法就可以理解为指针的指针了(就是和"a"是同一层的)。

//赋值方式(对比上一个代码):
p1[1] = &a[0][0];
p1[2] = a[0];
p1[3] = *a;       //这是p1指针数组中的三个不同的指针,但是它们值是一样的。

//p1是个数组,但是p2只有一个。
p2 = &a[0];
p2 = a;            //两个式子等价。

printf("%d", *(*(p2 + 2)+3) );   //与上一份代码相同,都表示第三行第四个元素。

结构体:

&与*优先级相同,而结构体的成员选择符号“.”的优先级要高于两者,当定义一个结构体指针并读取指向的成员时,应有如下写法:

(*p).data = a;     //*p.data = a; 会编译错误

↓结构体与指针的详细用法:

struct Node{
	int data;
	char fu;
	char *name;
	int shuzu[];
}dian1 = {2, 'g', "this", {0,1,2,3,4} }, dian2 = {3, 'g', "this is a test name", {} };

struct Node *p1 = &dian1, *p2 = &dian2;    //结构体指针;


int main() {
	dian1.data = 2333;
	(*p1).data = 2333;
	  p1->data = 2333;       //三个式子等价;

	printf("%d %d\n", dian1.data, dian1.shuzu[2]);

	printf("%p %p %c %c\n", p2->name, (*p2).name, *((*p2).name), *((*p2).name+3) );
	return 0;
}
/*
output:
2333 2
0040c049 0040c049 t s

*/

char指针可以用字符串赋值,指针保存的是第一个字符的地址,接地址可以得到第一个字符,通过加上一个常数获得字符串第i个字符。

typedef 替换:

typedef struct Dian{
	int zhi;
}D, E, *P;


int main() {
	D a = {5};
	E b = {6};     //虽然结构体内只有一个整型,但还是要括起来。
	P p = &a;
	printf("%d %d ", p->zhi, a.zhi);    //*P指针用"->",不是指针用"."
	b = a;                              //a和b分别用D和E来定义,但是本质上都是Dian,是同一个类型。
	printf("%d", b.zhi);
	return 0;
}

补充一些typedef用法,typedef可以替换数据类型,就是为复杂的声明定义简单的别名,与宏定义有一些差异。本身是一种储存类的关键字,与auto,extern,mutable,static,register等关键词不能出现在同一个表达式。

下面是详细用法:

typedef double db;    //与宏定义不同,typedef是一个语句,末尾要加";"
db pi = 3.1415926;    //db直接当double用。



typedef unsigned long long ull;   //比较常用的写法,定义起来方便了很多。

//这里补充一个问题,就是一定要用对应的格式输出结果,否则会出错。
//举个例子:
ull t = 2;
unsigned int  r = 2;
printf("%llu %llu\n",t,r);   //llu是输出无符号long long

//output: 2 18042985811804162
//输出错误是因为ull和ul占的字节长度不同,输出时地址越界了,这个是ub问题,不同编译器输出结果不同,不必要追究原因,只要注意这个问题就好。
//正确输出: printf("%llu %lu\n",t,r);



typedef int array [4];
array a;
a[2] = 2333;    //array可以直接当成定义一个长度为4的数组来用。

typedef int array2 [5][6];
array2 b;
b[3][1] = 114514;   //array2可以直接当定义二维数组来用。




typedef int* pointer;
pointer p;            //用pointer相当于定义指针。

typedef int* pointer2 [5];
pointer p2;            //定义指针数组;

不定期补充哦。

------update : 2023.03.07

posted @ 2023-05-14 17:45  maple276  阅读(69)  评论(0编辑  收藏  举报