【C语言】学习笔记5——指针(1)

1. 指针:一种以符号形式使用地址的方法。

  因为计算机的硬件指令非常依赖地址, 所以使用指针的程序更有效率。尤其是, 指针能有效地处理数组,数组地表示法其实是在变相地使用指针。

  例子:数组名是数组首元素的地址。 也就是说,如果 flizny 是一个数组,下面的语句成立 

flizny == &flizny[0]

  flizny 和 &flizny 都表示数组首元素的地址(&是地址运算符)。两者都是常量,在程序运行中,不会改变,但是,可以把他们赋值给指针变量, 然后可以修改指针变量。

//指针地址

#include <stdio.h>
#define SIZE 4

int main()
{
    short dates[SIZE];
    //声明一个short类型的指针变量 
    short * pti;    
    short index;

    double bills[SIZE];
    double * ptf;
    
    pti = dates;
    ptf = bills;
    
    printf("%23s %15s\n", "short", "double");
    for(index = 0; index < SIZE; index++)
        printf("pointers + %d: %10p %10p\n", index, pti + index, ptf + index); 
    return 0;
 } 
 /*
 Output:
                   short          double
pointers + 0: 000000000062FE30 000000000062FE10
pointers + 1: 000000000062FE32 000000000062FE18
pointers + 2: 000000000062FE34 000000000062FE20
pointers + 3: 000000000062FE36 000000000062FE28
 */

不知道为什么, 我以前上学的时候看指针跟看天书一样,现在再看指针突然很简单了。可能现在代码写多了,很容易分清楚变量,值, 变量名, 地址的关系。

所以现在看来学校开C语言来作为计算机语言的入门科目确实是有待商榷的,诚然C语言是最早的高级语言,学会了C语言会很容易学习其他计算机语言。但是先学习像java / python这样简单粗暴的计算机语言,可以让学生很轻松地理解写程序到底是怎么一回事。然后再回头深究它底层的原理会轻松地多。

我觉得可以把指针理解为一个变量。这个变量的大小取决于计算机的地址大小(我的电脑是64位的,所以一个指针大小就是8 bytes)。比如上面程序中的例子

//声明一个short类型的指针, 指针增量为sizeof(short)=2, 也就是说pti + 1相当于 pti 所表示的地址 + 2
short * pti;

//声明一个double类型的指针, 指针的增量为sizeof(double)=4, 也就是说ptl + 1 表示 pti 所指向的地址 + 4
double * ptf;

看上面的程序结果也很容易看出来, 再看书上的图也是符合的

 

2. 函数、数组和指针

  因为数组名是该数组首元素地地址,作为实际参数地数组名要求形式参数是一个与之匹配的指针。只有在这种情况下,C才会把 int ar[] 和 int * ar 解释成一样的。 也就是说, ar是指向int的指针。

  由于函数原型可以省略参数名, 所以下面4中原型都是等价的。  

int sum(int * ar, int n);

int sum(int *, int);

int sum(int ar[], int n)

int sum(int [], int);

  但是, 在函数定义中不能省略参数名, 下面两种形式的函数定义等价:

int sum(int *ar, int n) 
{
  //函数内容省略  
}

int sum(int ar[], int n)
{
 //函数内容省略
}

  看下面示例程序, marbles 的大小为40 bytes, 说明marbles表示的是数组, 而 ar的大小是8 bytes, 说明函数接收的是一个地址();

//数组元素之和

#include <stdio.h>
#define SIZE 10

//声明函数原型 
int sum(int ar[], int n);

int main()
{
    int marbles[SIZE] = {15, 23, 24, 69, 14, 12, 15, 18, 16, 35};
    long answer;
    
    answer = sum(marbles, SIZE);
    
    printf("marbles 所有元素之和为 %1d.\n", answer);
    
    printf("marbles 的大小为%u bytes.\n", sizeof marbles);
    
    return 0;
 }
 
 int sum(int ar[], int n)
 {
     int i;
     int total = 0;
     
     for(i = 0; i < n; i++)
     {
         total += ar[i];
     }
     printf("ar 的大小为 %u bytes.\n", sizeof ar);
     return total;
 }
 
 
 /*
 Output:
 
ar 的大小为 8 bytes.
marbles 所有元素之和为 241.
marbles 的大小为40 bytes.

 */

 3. 使用指针形参

/*使用指针形参*/

#include <stdio.h>
#define SIZE 10

int sump(int * start, int * end);

int main()
{
    int marbles[SIZE] = {15, 23, 24, 69, 14, 12, 15, 18, 16, 35};
    
    long answer;
    
    answer = sump(marbles, marbles + SIZE);
    
    printf("the total number of marbles is %1d.\n", answer);
    
    return 0;
 }
 
 //使用指针算法 
 int sump(int * start, int * end)
 {
     int total;
     while (start < end)     // 越界判断 
     {
         total += * start;  //累加数组元素的值 
         start++;           // 让指针指向下一个元素 
     }
     
     return total;
 }
 
 
 /*
 Output:
 
 the total number of marbles is 241.

 */

4. 指针操作

  a. 赋值:可以把地址赋值给指针。

  b. 解引用:* 运算符给出 指针所指地址上 存储的值。

  c. 取址:和所有变量一样,指针也有自己的地址和值,也可以用取址运算符 & 来获取指针本身的地址

  d. 指针与整数相加: 可以运用 + 运算符把指针与整数相加, 或整数与指针相加。 无论哪种情况,整数都会和指针所指向类型的大小(byte为单位)相乘, 然后把结果与初始地址相加。

  e. 指针递增(递减): 递增(递减)指向数组的指针可以让该指针指向该数组的下(上)一个元素。

  f. 指针减去一个整数:用初始地址减去 整数和指针所指向类型的大小(byte为单位)相乘 的结果。

  g. 指针求差:可以计算两个指针的差值。 两个指针所指向的地址的差值 / 指针所指向类型的大小

  h. 比较: 使用关系运算符可以比较两个指针的值,前提是两个指针都指向相同类型的对象

// 指针操作

#include <stdio.h>

int main()
{
    int urn[5] = {100, 200, 300, 400, 500};
    
    int * ptr1, * ptr2, * ptr3;
    
    ptr1 = urn;
    ptr2 = &urn[2];
    
    printf("pointer value, dereferenced pointer, pointer address:\n");
    printf("ptr1 = %p, *ptr1 = %d, &ptr1 = %p\n", ptr1, *ptr1, &ptr1);
    
    ptr3 = ptr1 + 4;         //指针加法
    printf("\n adding an int to a pointer:\n");
    printf("ptr1 + 4 = %p, *(ptr1 + 4) = %d\n", ptr1 + 4, *(ptr1 + 4));
    
    ptr1++;             //递增指针
    printf("\n values after ptr1++:\n");
    printf("ptr1 = %p, *ptr1 = %d, &ptr1 = %p\n", ptr1, *ptr1, &ptr1);
    
    ptr2--;             //递减指针 
    printf("\n values after ptr2--:\n");
    printf("ptr2 = %p, *ptr2 = %d, &ptr2 = %p\n", ptr2, *ptr2, &ptr2);
    
    //恢复 
    --ptr1;
    ++ptr2;
    printf("\n pointers reset to original values:\n");
    printf("ptr1 = %p, ptr2 = %p\n", ptr1, ptr2);
     

    
    //一个指针减去另一个指针
    printf("\n subtracting one pointer from another:\n");
    printf("ptr2 = %p, ptr1 = %p, ptr2 - ptr1 = %1d\n", ptr2, ptr1, ptr2 - ptr1);
    
    //一个指针减去一个整数
    printf("\n subtracting an int from a pointer:\n");
    printf("ptr3 = %p, ptr3 - 2 = %p\n", ptr3, ptr3 - 2);
    
    return 0; 
     
 } 
 
/*
Output:
pointer value, dereferenced pointer, pointer address:
ptr1 = 000000000062FE30, *ptr1 = 100, &ptr1 = 000000000062FE28

 adding an int to a pointer:
ptr1 + 4 = 000000000062FE40, *(ptr1 + 4) = 500

 values after ptr1++:
ptr1 = 000000000062FE34, *ptr1 = 200, &ptr1 = 000000000062FE28

 values after ptr2--:
ptr2 = 000000000062FE34, *ptr2 = 200, &ptr2 = 000000000062FE20

 pointers reset to original values:
ptr1 = 000000000062FE30, ptr2 = 000000000062FE38

 subtracting one pointer from another:
ptr2 = 000000000062FE38, ptr1 = 000000000062FE30, ptr2 - ptr1 = 2

 subtracting an int from a pointer:
ptr3 = 000000000062FE40, ptr3 - 2 = 000000000062FE38

 
*/ 

 

5. 不要解引用未初始化的指针:

  

int * pt;   //未初始化的指针

*pt = 5;   //严重的错误。 因为pt还没有初始化,我们不知道pt指向谁,解引用不知道把5赋值给谁

 

6. 保护数组中的数据(按值传递还是按地址传递)

  a. 函数中的值传递和地址传递:编写一个处理基本类型(如 int)的函数时, 要选择时传递int类型的值还是传递指向int类型的指针。 通常都是直接传递数值, 只有程序需要在函数中改变该数值时,才会传递指针。对于数组别无选择,必须传递指针,因为这样做效率高。如果按一个函数按值传递数组, 则必须分配足够的空间来存储原数组的副本,然后把数组所有的数据拷贝至新的数组中。 如果把数组的地址传递给函数,让函数直接处理原数组则效率要高。

  b. 传递地址会导致一些问题(可能会对数值进行意外的修改)。C通常都是按值传递数据,因为这样可以保证数据的完整性。

7. 对形参使用const : 如果函数的意图不是修改数组中的数据内容, 那么在函数原型和函数定义中声明形式参数时应使用关键字const 

 

   

  

 

posted @ 2018-08-19 20:08  早起的虫儿去吃鸟  阅读(744)  评论(0编辑  收藏  举报