嵌入式Hacker (es-hacker)

Embedded bsp developer enjoys thinking and hacking opensource and develop boards(NanoPi, LicheePi, RPi...)

0%

每天一点C / 一维数组与指针

哈喽,我是老吴,继续记录我的学习心得。

每天一点系列是我对微习惯的践行。现在能做到每天一点 C,将来就会有更多的每天一点系列,没人规定嵌入式软件工程师就只能学习 C 语言和折腾 Linux,不要给自己设限。

为什么是每天一点?
养成写作习惯的第一步:放弃追求大段的、固定的、安静的写作时间,培养随时写作的能力。

同样的技巧也能应用到技术学习上,工程师也可以放弃追求大段的、固定的、安静的看书写代码的时间。遇到一个新知识,第一时间找到相关资料,找到切入点并将其记录在自己的技术笔记中,并且定期复查及完善。

30 岁的中年工程师能做到每天坚持学习,你还抱怨没时间学习吗?


每天一点C / 数组和指针

正文目录:

1
1. 数组名是该数组首元素的地址
2
2. 用指针操作数组
3
3. 数组和指针的关系密切
4
4. 编写处理一维数组的函数:传递数组起始地址+数组长度
5
5. 编写处理一维数组的函数:传递数组起始地址+结束地址

学习环境:

  • Ubuntu 16.04
  • gcc version 5.4.0

1. 数组名是该数组首元素的地址

演示 demo:

1
#define MONTHS 12
2
int main(void)
3
{
4
    int days[MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31};
5
    
6
    if (days == &days[0])
7
        printf("TRUE\n");
8
    
9
    return 0;
10
}

运行效果:

1
$ gcc array_name.c -o array_name
2
3
$ ./array_name 
4
TRUE

相关要点:

  • 数组名和该数组首元素的地址都是常量,在程序的运行过程中,不会改变。

2. 用指针操作数组

演示 demo:

1
#define SIZE 4
2
int main(void)
3
{
4
    short dates [SIZE];
5
    short * pti;
6
    short index;
7
    double bills[SIZE];
8
    double * ptf;
9
    
10
    pti = dates;    // assign address of array to pointer
11
    ptf = bills;
12
    printf("%23s %15s\n", "short", "double");
13
    for (index = 0; index < SIZE; index ++)
14
        printf("pointers + %d: %10p %10p\n",  index, pti + index, ptf + index);
15
    
16
    return 0;
17
}

运行效果:

1
$ gcc point_array.c -o point_array
2
3
$ ./point_array
4
                  short          double
5
pointers + 0: 0x7ffc890b17c0 0x7ffc890b17d0
6
pointers + 1: 0x7ffc890b17c2 0x7ffc890b17d8
7
pointers + 2: 0x7ffc890b17c4 0x7ffc890b17e0
8
pointers + 3: 0x7ffc890b17c6 0x7ffc890b17e8
  • 指针的值是它所指向对象的地址,指针前面使用 * 运算符可以得到该指针所指向对象的值。

  • %p 会以十六进制显示指针的值

  • 必须声明指针所指向对象类型,原因之一:计算机既要知道储存对象的地址,又要知道储存对象需要多少字节。

  • 指针加 1 指的是增加一个数据存储单元。对数组而言,这意味着加 1 后的地址是下一个元素的地址,而不是下一个字节的地址。


3. 数组和指针的关系密切

以下关系表明了数组和指针的关系十分密切:

1
days + 2 == &days[2]   // TRUE,相同的地址
2
*(days + 2) == days[2] // TRUE,相同的值

演示 demo:

1
int main(void)
2
{
3
    int days[MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31};
4
    int index;
5
    
6
    for (index = 0; index < MONTHS; index++)
7
        printf("Month %2d has %d days.\n", index +1, *(days + index));
8
    
9
    return 0;
10
}

运行效果:

1
$ gcc point_array2.c -o point_array2
2
3
$ ./point_array2 
4
Month  1 has 31 days.
5
Month  2 has 28 days.
6
...
7
Month 12 has 31 days.
  • C 语言在描述数组表示法时借助了指针:ar[n]的意思是*(ar + n)。

  • 可在编写程序时适时使用 数组表示法或指针表示法,这是两种功能等效的方法。大多数情况下可以用指针表示数组,反过来,也可以用数组表示指针。

  • 编译器编译这两种写法生成的代码相同。


4. 编写处理一维数组的函数:传递数组起始地址+数组长度

假设要编写一个处理数组的函数,该函数返回数组中所有元素之和,待处理的是名为 data 的 int 类型数组,应该如何调用该函数?

演示 demo:

1
int sum(int ar[], int n);
2
int main(void)
3
{
4
    int data[SIZE] = {20,10,5,39,4,16,19,26,31,20};
5
    long answer;
6
    
7
    answer = sum(data, SIZE);
8
    printf("The total number of data is %ld.\n", answer);
9
    printf("The size of data is %zd bytes.\n",
10
           sizeof data);
11
    
12
    return 0;
13
}
14
15
int sum(int ar[], int n)
16
{
17
    int i;
18
    int total = 0;
19
    
20
    for( i = 0; i < n; i++)
21
        total += ar[i];
22
    printf("The size of ar is %zd bytes.\n", sizeof ar);
23
    
24
    return total;
25
}

运行效果:

1
$ gcc func_array.c -o func_array
2
func_array.c: In function ‘sum’:
3
func_array.c:26:53: warning: ‘sizeof’ on array function parameter ‘ar’ will return size of ‘int *’ [-Wsizeof-array-argument]
4
     printf("The size of ar is %zd bytes.\n", sizeof ar);
5
                                                     ^
6
func_array.c:19:13: note: declared here
7
 int sum(int ar[], int n)
8
9
$ ./func_array
10
The size of ar is 8 bytes.
11
The total number of data is 190.
12
The size of data is 40 bytes.
  • 数组名是该数组首元素的地址,所以实参 data 是一个储存int 类型值的地址,那么函数 sum 的形参数就应该是一个指针。

  • 在函数声明或函数定义中,int ar[] 等效于 int *ar,本质上都应该看作是指针。

  • 只有在函数声明或函数定义中,才可以用int ar[]代替int *ar

  • 函数声明可以省略参数名,所以下面 4 种原型都是等价的:

1
int sum(int *ar, int n);
2
int sum(int *, int);
3
int sum(int ar[], int n);
4
int sum(int [], int);
  • 注意编译器的警告信息:warning: ‘sizeof’ on array function parameter ‘ar’ will return size of ‘int *’,这是在提示程序员你打印的是指针的 size,而不是整个数组的 size。

  • 一般会把数组的参数作为其中一个参数传递给处理数组的函数。


5. 编写处理一维数组的函数:传递数组起始地址+结束地址

演示 demo:

1
int sump(int * start, int * end);
2
int main(void)
3
{
4
    int data[SIZE] = {20,10,5,39,4,16,19,26,31,20};
5
    long answer;
6
    
7
    answer = sump(data, data + SIZE);
8
    printf("The total number of data is %ld.\n", answer);
9
    
10
    return 0;
11
}
12
13
int sump(int * start, int * end)
14
{
15
    int total = 0;
16
    
17
    while (start < end)
18
    {
19
        total += *start;
20
        start++;next element
21
    }
22
    
23
    return total;
24
}

运行效果:

1
$ gcc func_array2.c -o func_array2
2
3
$ ./func_array2
4
The total number of marbles is 190.

相关要点:

  • 注意 answer = sump(data, data + SIZE) 和 while (start < end),这是一种推荐用法,简洁且清晰。因为下标从 0 开始,所以 data + SIZE 指向数组末尾的下一个位置。

  • data + SIZE 是推荐的,但是 data[SIZE] 则是错误的。

  • 处理数组的函数实际上用指针作为参数,但是在编写这样的函数时,可以选择是使用数组表示法还是指针表示法。数组表示法让函数是处理数组的这一意图更加明显,而对于喜欢指针的程序员,使用指针表示法,则觉得使用指针更自然。

思考技术,也要思考人生

学习技术,更要学习如何生活

你和我各有一个苹果,如果我们交换苹果的话,我们还是只有一个苹果。但当你和我各有一个想法,我们交换想法的话,我们就都有两个想法了。

嵌入式系统 (Linux、RTOS、OpenWrt、Android) 和 开源软件 感兴趣,想和更多人互相交流学习,请关注公众号:嵌入式Hacker,一起来学习吧。

无论是关注或转发,还是打赏,都是对作者莫大的支持。觉得文章对你有价值的话,不妨点个 在看和点赞 哦。

ps:
欢迎加入我的微信群:加我,我拉你进群,暗号(加群)。

祝各位工作顺利,家庭幸福,财源滚滚~

这是一篇有趣的文章吗?

欢迎关注我的其它发布渠道