0%

C语言基础_指针&&结构体

之前讲了讲C语言基础,本来还准备第二天讲链表,包括动态申请空间啥的,结果被人说讲的太难,然后就放弃了……

以及下面的内容有可能很不严谨或者是错的,所以随便看看就行。

比初稿又加了一些内容……

指针

语法定义

指向一个数据类型,内容是所指向的东西的内存地址。

指针也有地址。

1
2
3
int a;
int *b = &a;
*b = 5;

第一个*代表b是一个指针,&代表取a的地址。

第二个*代表解引用,*b表示b的内容里的地址所在的东西。

可以将*与&看作两个互逆的运算。

我相信都能看懂。

1
2
int *b;
int* b;

以及声明一个指针时,有上面两种格式.第一种将*与b看做一个部分,第二种把int*看做一个特殊的类型。很显然第二种看法是错的,因为如果成立,那么int *a,b中a和b应该都是指针。

但是实际上有时候我们需要第二种想法,在某些时候可以帮我们更好理解,哪怕它是错的。

数组指针

下面的就是指针与复合类型了,很复杂。

指针与数组名

1
2
3
int a[5];
int *b = a;
int *b = &a[0];

数组名在大多数情况下在语法上相当于数组第一个元素的地址。

所以上面两种写法是等价的。

1
printf("%d",b[2]);

为什么能这样做呢?

因为b[i]的含义实际上与*(b + i)是等价的。

以及因为加法交换律的缘故,2[b]这种鬼畜的写法也是可以的,它与b[2]等价,因为*(b + 2) == *(2 + b)

但是,数组名就相当于指向第一个元素的指针吗?

答案是否定的,虽然在大多数情况下可以这样看,但是也有例外。

首先,数组名不能像指针那样自加自减。

其次,数组名在下面两个情况下代表整个数组:

1
2
int a[5];
printf(“%d,%d”,&a,sizeof(a));

所以我说,数组名只在大多数情况下在语法上相当于一个指向第一个元素的指针。

实质上是数组名在大多数情况下可以隐性转换为指针类型(大概)

指向数组的指针

怎样让一个指针指向整个数组?

1
2
int a[5];
int *b = &a;

对么?

编译之后报错:

main.cpp:8:11: error: cannot convert 'int (*)[5]' to 'int*' in initialization
int *b = &a;
          ^

先分析a的类型: 因为是数组,所以为 int [5]

注意这里的&a代表对a整个数组取地址,这个地址实际上和数组第一个元素地址相同,但是类型不同(&a与a在内容上是相同的)

&a对a取地址,所以类型变成了int (*)[5],代表整个数组的地址

左边与右边类型不一样,所以无法隐性转换

所以我们要声明一个类型是int (*)[5]的指针,而不是int *

正确的:

1
int (*b)[5] = &a;

为什么*要加括号呢?因为*运算符优先级比[]低:

1
int *b[5];

从标识符出发一层层向外看:

如果不加(),那么b先会与[5]结合,代表b是一个大小为5的数组,之后才会与*结合,代表这个数组中元素类型为指针,最后与int结合,代表指针指向int

如果加上括号,b会先与*结合,代表b是一个指针,之后与[5]结合,代表这个指针指向一个大小为5的数组,最后与int结合,代表数组元素的类型是int的

学会这种从标识符出发,从里到外一层层拨开的识别方法,就像剥洋葱皮一样

多维数组的指针分析判断

1
2
3
int *a[2][3];
int (*b[2])[3];
int (*c)[2][3];

分别说出a,b,c区别:

a先和[2]结合,代表a是一个大小为2的数组,在和[3]结合,代表这个数组元素类型是一个大小为3的数组(就相当于说a是一个二维数组),在与*结合,代表数组(大小为3的数组)的元素类型是指针,最后与int结合,代表指针指向一个int变量。

所以,a是一个二维数组,每个元素都是一个指向int的指针。

b先和[2]结合,代表a是一个大小为2的数组,在和*结合,代表这个数组元素类型是一个指针,在与[3]结合,代表指针指向一个大小为3的数组,数组是int的。

c和*结合,代表这个c是一个指针,再分别与[2][3]结合,代表指向一个二维数组。

char [] 和 char *

1
2
char *s1 = "abc";
char s2[1000] = "abc";

首先,写在代码里的"abc"叫做字面量,程序启动时会把它们存到静态区,不能被修改。

对于字符串字面量,它在语法上相当于这个字面量的地址,所以第一行相当于把这个字面量的地址付给s1这个指针,你不能用s1修改其中的值。

对于s2,他是一个字符数组,这样写就相当于用字面量初始化这个数组,你是可以修改其中的值的。

以及,你可以这样写

1
printf("%c","abcdefg"[7]);

这样相当于:

1
2
char *s = "abcdefg";
printf("%c",*(s + 7));

以及在函数参数的形参里,char *与char[]是等价的

1
2
3
4
void fun(char a[]) {
printf("%s",a);
}
fun("abcde");

函数指针

函数名相当于它的地址

1
2
3
4
5
void fun(int a,int b) {
printf("%d + %d = %d\n",a,b,a+b);
}
void (*a)(int,int) = fun;
a(1,2);

当使用a指针指向的函数时,直接把a当成函数名用就行了。

把指针与数组与函数邪恶的混在一起……

1
2
char (*(*x[3])())[5];
int (*(*foo)(int (*)[5]))[3];

x是一个数组,每个元素是一个指针,指针指向一个没有参数的函数,函数返回值是一个指针,指向长度为5的数组,数组元素是char

foo是一个指针,指向一个函数,函数参数是一个指向长度为5的数组的指针,函数返回值是一个指针,指向长度为3的数组,数组元素是int

结构体

一种将多种数据类型组合在一起的东西。

声明

1
2
3
4
5
6
struct Point {
int x;
int y;
}
struct Point a = (struct Point){1,2};
printf("x = %d,y = %d\n",a.x,a.y);

输出:x = 1,y = 2

typedef

结构体类型是struct Point,太长了怎么办?

用typedef取一个短名字

1
2
typedef struct Point p;
p a = (p){2,3};

以后把所有要打struct Point的地方换成p就行了

访问结构体成员和指针

如何访问结构体a的x变量?语法:a.x

如果是一个结构体指针,怎么访问它所指向的结构体的x变量?

1
2
3
p a = (p){1,9};
p *b = &a;
printf("%d %d\n",(*b).x,b->y);

首先,”.”运算符优先级比*高,所以要加括号。因为这样写很麻烦,所以人们发明了”->”运算符,你可以把它当成”.”去用,它与第一种写法是等价的。

a^=b^=a^=b这种写法的原理,为什么是错的?

首先这个语句是想要交换a和b的值。一般来说想要交换变量值必须有一个临时变量,类似于下面这种。

1
2
3
4
int tmp;
tmp = a;
a = b;
b = tmp;

但是有人觉得这样比较麻烦,为了装X,他会这样写:

1
2
3
a = a + b;
b = a - b;
a = a - b;

为什么这样是对的呢?因为加法是一种可逆的运算(逆运算就是减法)。

但如果a和b都很大,就溢出了,这时候这样就不行了。

怎么办呢?有人想到了异或。

异或可以看做是不进位的加法,而且运算可逆(对一个数a异或另一个数b异或两次就等于这个数a本身),所以有人就开始这样写。

1
2
3
a = a ^ b;
b = a ^ b;
a = a ^ b;

然后有人装X,于是用^=缩成一行

1
a ^= b ^= a ^= b;

然而这样写是错的,因为这是一个ub(未定义行为)。简而言之,就是规定在一个表达式里如果一个变量改变了它的值,那么它不要再出现第二次。因为变量第二次出现时的值可能是改变前,也可能是改变后的,具体顺序取决于编译器怎么实现。详情可以看这里

所以说像什么i+++++i这种都是错的,是百分之百的毒瘤写法。

类似的,下面的两个函数调用也是不确定的,不一定谁先被调用,所以输出也不确定……

1
2
3
4
5
6
7
8
9
10
11
int a() {
printf("first!\n");
return 1;
}
int b() {
printf("second!\n");
return 2;
}
int main() {
printf("%d\n",a() + b());
}

当然也有例外:

1
2
int a[10],*b = a;
while(b != a + 10) printf("%d\n",*b++);

b先后置自增,再解引用。这样虽然改变了b的值,但是是没问题的。前置自增也是一样。

以及用异或交换值有一个缺点,当一个数异或自己时,结果为0。所以异或前要先判断两个变量是否相等……