c 语言中的数组和指针

时间:2024-02-26 09:20:13

1 如何把数组初始化成全 0

声明的局部变量数组,如果没有初始化,那么数组保存在栈上,数组的内容是不确定的。

局部变量不像全局变量,全局变量如果没初始化,那么默认是全 0。

将数组初始化为 0 的方式主要有以下 3 种,本人在开发过程中习惯于使用第一种,即使用 {0} 将数组初始化为全 0。

(1)数组声明的时候使用 {0} 初始化

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main() {
  int a[10] = {0};
  int b[10][10] = {0};
  for (int i = 0; i < 10; i++) {
    printf("a[%d] = %d\n", i, a[i]);
  }

  for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 10; j++) {
      printf("b[%d][%d] = %d\n", i, j, b[i][j]);
    }
  }
  return 0;
}

 

 (2)memset()

使用 memset() 将数组设置成 0。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main() {
  int a[10] = {0};
  int b[10][10] = {0};
  for (int i = 0; i < 10; i++) {
    printf("a[%d] = %d\n", i, a[i]);
  }

  for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 10; j++) {
      printf("b[%d][%d] = %d\n", i, j, b[i][j]);
    }
  }
  return 0;
}

 

(3)使用 for 循环将数组元组逐个设置为 0

2 指针加减运算

指针的加减运算移动的单位是指针指向的数据的大小。

而不是把指针看做一个整型数,在这个数的基础上加减。

 

 2.1 一级指针

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

struct test {
  int a;
  char name[10];
  long data;
};

int main() {
  char c;
  char *pc = &c;

  int a = 0;
  int *pa = &a;

  struct test t;
  struct test *pt = &t;

  printf("pc: %p, pc + 1: %p\n", pc, pc + 1);
  printf("pa: %p, pa + 1: %p\n", pa, pa + 1);
  printf("pt: %p, pt + 1: %p\n", pt, pt + 1);
  printf("sizeof(struct test): %d\n", sizeof(struct test));
  printf("&t: %p, &t.a: %p, &t.name: %p, &t.data: %p\n", &t, &t.a, &t.name, &t.data);
  return 0;
}

pc 指向的数据类型是 char 类型,所以 pc + 1,地址移动 1 个字节。

pa 指向的数据类型是 int 类型,所以 pa + 1,地址移动了 4 个字节。

pt 指向的数据类型是 struct test 类型,所以 pt + 1,地址移动了 24 个字节。

 

结构体的大小并不是所有结构体成员大小的和,因为结构体中的成员需要对齐。

怎么才算对齐 ? 成员地址 % 成员大小 等于 0

从上边的打印可以看出来,成员 a 的地址是 0x7ffd20d18350 对 4 可以整除;

成员 name 是一个数组,不是以数组为整体进行对齐,而是以数组中的元素进行对齐;

name 最后一个元素的地址是 0x7ffd20d1835e,但是 data 却不能保存到 0x7ffd20d1835f 处,因为这个地址对 data 的长度不能整除,所以 data 保存在了 0x7ffd20d18360 处。

指针的减法,得到的是两个指针之间相隔的元素的个数。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>


int main() {
  int a[10] = {0};
  int *pa = a;
  int *pa5 = a +5;
  printf("pa5 - pa: %d\n", pa5 - pa);
  return 0;
}

 

2.2 二级指针

二级指针指向的数据类型是一级指针,所以二级指针加减运算的时候移动的单位是一个一级指针的大小。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

struct test {
  int a;
  char name[17];
  long data;
};

int main() {
  char c;
  char *pc = &c;
  char **ppc = &pc;

  int a = 0;
  int *pa = &a;
  int **ppa = &pa;

  struct test t;
  struct test *pt = &t;
  struct test **ppt = &pt;

  printf("ppc: %p, ppc + 1: %p\n", ppc, ppc + 1);
  printf("ppa: %p, ppa + 1: %p\n", ppa, ppa + 1);
  printf("ppt: %p, ppt + 1: %p\n", ppt, ppt + 1);
  printf("sizeof(struct test): %d\n", sizeof(struct test));
  printf("&t: %p, &t.a: %p, &t.name: %p, &t.data: %p\n", &t, &t.a, &t.name, &t.data);
  return 0;
}

3 数组加减运算

3.1 数组 sizeof()

数组名其实是一个指针,一维数组的数组名是一个一级指针 int *,二维数组的数组名是一个二级指针 int **。但是在使用 sizeof() 计算数组的大小的时候,并不是返回的一个指针的大小,而是返回的数组占用的空间。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main() {
  int a[10] = {0};
  int b[10][10] = {0};

  int *pa = a;
  int **pb = b;
  printf("sizeof(a): %d, sizeof(&a): %d, sizeof(pa): %d\n", sizeof(a), sizeof(&a), sizeof(pa));
  printf("sizeof(b): %d, sizeof(&b): %d, sizeof(pb): %d\n", sizeof(b), sizeof(&b), sizeof(pb));
  return 0;
}

 

3.2 一维数组加减运算

一维数组的数组名是一级指针 int *,使用数组名做加减运算,移动的单位是数组元素的长度。

一维数组的数组名取地址,然后做加减运算,移动的单位是整个数组的大小。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main() {
  int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

  printf("a: %p, a + 1: %p\n", a, a + 1);
  printf("&a: %p, &a + 1: %p\n", &a, &a + 1);
  printf("*a: %d\n", *a);
  return 0;
}

使用数组名做运算,a 和 a + 1 相差 4 个字节,即一个数组元素的长度。

使用数组名取地址做运算, &a 和 &a + 1相差整个数组的大小,4 * 10 = 40,即 0x28。

数组的加减运算有自己的特殊之处,并不能完全按照指针的加减运算来使用。

如果按照指针的加减运算,那么 &a + 1 偏移的长度应该是一个 int * 的长度,在 64 为机器上是 8。

3.3 二维数组加减运算

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main() {
  int a[4][4] = {{1, 2, 3, 4}, {11, 12, 13, 14}, {21, 22, 23, 24}, {31, 32, 33, 34}};

  printf("a: %p, a + 1: %p\n", a, a + 1);
  printf("*a: %p, *a + 1: %p\n", *a, *a + 1);
  printf("&a: %p, &a + 1: %p\n", &a, &a + 1);
  return 0;
}

二维数组的数组名是一个二级指针 int **, 加减运算移动的单位是一行的大小,这个例子中行和列都是 4,所以移动单位是 16。

二维数组名使用 * 取值之后就是一级指针,指向某一行,而指向某一行的指针和一维数组的数组名是类似的,移动的单位是一个元素的长度。

二维数组的数组名取地址再做加减运算,和一维数组是类似的,移动的单位是整个数组的大小。

 

4 字符数组

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main() {
  char *a = "hello";
  char b[] = "hello";

  printf("sizeof(a): %d, strlen(a): %d\n", sizeof(a), strlen(a));
  printf("sizeof(b): %d, strlen(b): %d\n", sizeof(b), strlen(b));

  printf("\n");
  printf("a: %p, a + 1: %p\n", a, a + 1);
  printf("b: %p, b + 1: %p\n", b, b + 1);
  return 0;
}

a 是一个 char * 指针,b 是一个 char 数组的数组名,两者还是有区别的。

sizeof(a) 是一个指针的大小,在 64 位系统中,长度是 8。

sizeof(b) 是一个数组的大小,长度是 6,包括字符串最后的结束符 '\0'。

strlen(a) 和 strlen(b) 是相同的,均是字符串的长度。使用 strlen() 获得的字符串的长度是不包括最后的字符串结束符 '\0' 的。

 

 

5 指针数组,数组指针

指针数组只的是一个数组,数组中的元素都是指针。

数组指针,可以叫数组的指针,是指一个指针,指向一个数组。

如下代码中,pa 是一个指针数组,数组长度是 2,可以保存两个指针。

ap 是数组指针,指向了数组 b。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main() {
  int a[4] = {1, 2, 3, 4};
  int b[5] = {5, 6, 7, 8, 9};

  int *pa[2] = {a, b};
  int *ap = b;

  for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
    printf("a[%d] = %d\n", i, pa[0][i]);
  }

  for (int i = 0; i < sizeof(b) / sizeof(b[0]); i++) {
    printf("b[%d] = %d\n", i, ap[i]);
  }

  return 0;
}

如下代码需要深入理解指针数组与数组指针的概念,记录如下。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main() {
  char *str[] = {"Welcome", "to", "Fortemedia", "Nanjing"};
  for (int i = 0; i < sizeof(str) / sizeof(str[0]); i++) {
    printf("str[%d] = %p\n", i, str[i]);
  }

  char **p = str + 1;
  printf("1111, *p: %s\n", *p); // to

  p++; // p 指向 Fortemedia
  printf("2222, *p: %s\n", *p);
  p--; // p 指向 to

  str[0] = (*p++) + 2; // p++ --> p 指向 Fortemedia, + 2 --> 超出数组范围
  printf("3333, *p: %s\n", *p);

  str[1] = *(p + 1); // p + 1 --> p + 1 指向 Nanjing,p 不变
  printf("4444, *p: %s\n", *p);

  str[2] = p[1] + 3; // p[1] --> Nanjing + 3 --> jing,因为 *p 的值也保存在 str[2] 这个位置,所以 p 也改变了
  printf("p[0]: %s, *p: %s, str[2]: %s, str[1]: %s\n", p[0], *p, str[2], str[1]);

  str[3] = p[0] + (str[2] - str[1]); // p[0] --> Fortemedia, str[2] jing, str[1] Nanjing,str[2] - str[1] = 3,p[0] + 3 指向 Nanjing 的最后一个字符 g

  printf("%s\n", str[0]);
  printf("%s\n", str[1]);
  printf("%s\n", str[2]);
  printf("%s\n", str[3]);
  return 0;
}