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