指针的使用

指针的使用:指针有什么用?

指针应用场景一

交换两个变量的值

  1. void fnSwapValue(int *a, int *b)
  2. {
  3. int t = *a;
  4. *a = *b;
  5. *b = t;
  6. }

指针应用场景二a

  • 函数返回多个值,某些值就只能通过指针返回
    • 传入的参数实际上是需要保存带回的结果的变量
  1. void fnSearchMinMax(int target[], int length, int *min, int *max)
  2. {
  3. *min = *max = target[0];
  4. for (int i = 1; i < length; i++)
  5. {
  6. if (target[i] < *min)
  7. {
  8. *min = target[i];
  9. }
  10. if (target[i] > *max)
  11. {
  12. *max = target[i];
  13. }
  14. }
  15. }

指针应用场景二b

  • 函数返回运算的状态,结果通过指针返回
  • 常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错:
    • -10(在文件操作中会看到大量的例子)
  • 但是当任何数值都是有效的可能结果时,就得分开返回了
    • 后续的语言(C++, Java)采用了异常机制来解决这个问题
  1. int fnDevide(int a, int b, int *result)
  2. {
  3. int status = 1;
  4. if (b == 0)
  5. {
  6. status = 0;
  7. }
  8. else
  9. {
  10. *result = a / b;
  11. }
  12. return status;
  13. }

指针最常见的错误

  • 定义了一个指针,但是并没有让他指向任何变量,就开始使用指针

指针与数组:为什么数组传入函数后的sizeof不对了?

  • 函数参数表中的数组实际上是指针
    • sizeof(a) == sizeof(int *);
    • 但是可以用数组的运算符[]进行运算

数组参数

  • 以下四种函数原型是等价的:
    • int sum(int *arr, int n);
    • int sum(int *, int);
    • int sum(int arr[], int n);
    • int sum(int [], int);

数组变量是特殊的指针

  • 数组变量本身表达地址,所以
    • int a[10]; int *p = a; // 无需用&取地址
    • 但是数组的单元表达的是变量,需要用&取地址
    • a == &a[0]
  • []运算符可以对数组做,也可以对指针做
    • p[0] -> a[0]
  • *运算符可以对指针做,也可以对数组做:
    • *a = 25;
  • 数组变量const指针,所以不能被赋值
    • int b[] = int * const b;
    • 常量指针

指针与const

指针是const

  • 表示一旦得到了某个变量的地址,不能再指向其他变量
    • int * const q = &i; // q是const
    • *q = 26; // OK
    • q++; // ERROR
    • q的值不能被改变

所指是const

  • 表示不能通过这个指针去修改那个变量(并不能使得那个变量成为const
    • const int *p = &i;
    • *p = 26; // ERROR !(*p)是const
    • i = 26; //OK
    • p = &i; // OK

这是什么意思?

  1. int i;
  2. const int* p1 = &i; // 定义一个所指是const的地址变量
  3. int const* p2 = &i; // 同上
  4. int *const p3 = &i; // 定义一个指针本身是const的地址变量

注意:判断那个被const的标志是const*的前面还是后面

转换

我们总是可以把一个非const的值转换成const

void f(const int * x);
int a = 14;
f(&a); // OK
const int b = a;

f(&b); // Ok
b = a + a; // Error!
  • 当要传递的参数的类型比地址大的时候,这是常用的手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改

const数组

  • const int a[] = {1, 2, 3, 4, 5, 6};
  • 数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int
  • 所以必须通过初始化进行赋值

指针运算

  • 这些算术运算可以对指针做:
    • 给指针加、减一个整数(+, +=, -, -=
    • 递增递减(++/--
    • 两个指针相减

1+1=2?

  • 给一个指针加1表示要让指针指向下一个变量
    • int a[10];
    • int *p = a;
    • *(p+1) -> a[1]
  • 如果指针不是指向一片连续分配的空间,如数组,则这种运算没有意义
int main()
{
    char target[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    char *p = target;
    printf("p = %p\n", p);
    printf("p+1 = %p\n", p+1);

    return 0;
}

// p = 000000000061FE0F
// p+1 = 000000000061FE10
// sizeof(char) == 1


int main()
{
    int target[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    int *p = target;
    printf("p = %p\n", p);
    printf("p+1 = %p\n", p+1);

    return 0;
}

// p = 000000000061FDF0
// p+1 = 000000000061FDF4
// sizeof(int) == 4

两个指针相减

  • 地址的差除以sizeof(type)
    • 中间有几个这样的type
int main()
{
    char target[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    char *p = target;
    char *p1 = &target[5];
    printf("p1 - p = %p\n", p1 - p);

    return 0;
}

// p1 - p = 0000000000000005

int main()
{
    int target[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    int *p = target;
    int *p1 = &target[5];
    printf("p1 - p = %p\n", p1 - p);

    return 0;
}

// p1 - p = 0000000000000005

*p++

  • 取出p所指的那个数据来,完事之后顺便把p移到下一个位置去
    • 取出p那个位置值,顺便把p1
  • *的优先级虽然高,但是没有++
  • 常用于数组类的连续空间操作
  • 在某些CPU上,这可以直接被翻译成一条汇编指令(非常省事)

指针比较

  • <, <=, ==, >=, !=都可以对指针做
  • 比较它们在内存中地址
  • 数组中的单元的地址肯定是线性递增的

0地址

  • 当然你的内存中有0地址,但是0地址通常是个不能随便碰的地址
  • 所以你的指针不应该具有0
  • 因此可以用0地址表示特殊的事情
    • 返回的指针是无效的
    • 指针没有被真正初始化(先初始化为0
  • NULL是一个预定定义的符号,表示0地址
  • 有的编译器不愿意你用0来表示0地址

指针的类型

  • 无论指向什么类型,所有的指针的大小都是一样的,因为都是地址
  • 但是指向不同类型的指针是不能直接互相赋值的
  • 这是为了避免用错指针

指针的类型转换

  • void *表示不知道指向什么东西的指针
    • 计算时与char *相同(但不相通)
  • 指针也可以转换类型
    • int *p = &i; void *q = (void *)p;
  • 这并没有改变p所指的变量的类型,而是让后人用不同的眼光通过p看它所指的变量
    • 我不再当你是int啦,我认为你就是个void

用指针来做什么

  • 需要传入较大的数据时用作参数
  • 传入数组后对数组做操作
  • 函数返回不止一个结果
    • 需要用函数来修改不止一个变量
  • 动态申请的内存

动态内存分配

输入数据

  • 如果输入数据时,想告诉你个数,然后再输入,要记录每个数据
  • C99可以用变量做数组定义的大小,C99之前呢?
  • int *a = (int *) malloc (n *sizeof(int));

int main()
{
    int number = 10;
    int *p;
    p = (int *)malloc(number * sizeof(int));
    for (int i = 0; i < number; i++)
    {
        scanf("%d", &p[i]);
    }

    for (int i = number - 1; i >= 0; i--)
    {
        printf("%d", p[i]);
    }

    free(p);

    return 0;
}

malloc函数

#include <stdlib.h>
void * malloc(size_t size);
  • malloc申请的空间的大小时以字节为单位的
  • 返回的结果是void*,需要类型转换为自己需要的类型
    • (int *)malloc(n * sizeof(int));

没空间了?

  • 如果申请失败则返回0,或者叫做NULL
  • 你的系统能给你多大的空间?
int main()
{
    void *p;
    int count = 0;
    while (p = malloc(100 * 1024 * 1024))
    {
        count++;
        // free(p);
    }
    printf("分配了%d00MB的空间", count);

    return 0;
}

// 分配了19900MB的空间 (没有还)
// 如果分配之后,再free(),则程序会一直运行下去

free()函数

  • 把申请的来的空间还给“系统”
  • 申请过的空间,最终都应该还
    • 混出来的,迟早要还的
  • 只能还申请过来的空间的首地址
    • free(++p);会出错
  • free(NULL);不会出错

常见问题

  • 申请了没free()->长时间运行内存逐渐下降
    • 新手:玩了
    • 老手:找不到合适的free的时机
  • free过了再free
  • 地址变过了,直接去free

字符串操作

单字符输入输出

putchar

  • int putchar(int c);
  • 向标准输出写一个字符
  • 返回写了几个字符,EOF(-1)表示写失败

getchar

  • int getchar(void);
  • 从标准输入读入一个字符
  • 返回类型是int是为了返回EOF(-1)
    • Windows->Ctrl+Z
    • Unix->Ctrl+D
int main()
{
    int ch;
    while ((ch == getchar()) != EOF)
    {
        putchar(ch);
    }
    printf("EOF");

    return 0;
}

字符串数组

字符串数组

  • char **p;
    • p是一个指针,指向另一个指针,那个指针指向一个字符(串)
  • char a[][];

程序参数

  • int main(int argc, char const *argv[])
  • argv[0]是命令本身
    • 当使用Unix的符号链接时,反映符号链接的名字
int main(int argc, char const * argv[])
{
    for (int i = 1; i < argc; i++)
    {
        printf("%d:%s\n", i, argv[i]);
        printf("%d\n", argc);
    }
    return 0;
}

字符串函数的实现

string.h

  • strlen
  • strcmp
  • strcpy
  • strcat
  • strchr
  • strstr

    strlen

    • size_t_strlen(const char *s);
    • 返回s的字符串长度(不包括结尾的0
int fnStringLength(const char * str)
{
    int count = 0;
    int flag = 0;
    while (str[flag] != '\0')
    {
        flag++;
        count++;
    }
    return count;
}

strcmp

  • int strcmp(const char *s1, const char *s2);
  • 比较两个字符串,返回:
    • 0 : s1 == s2
    • 1 : s1 > s2
    • -1 : s1 < s2
  • 注意:数组的比较永远是false
int main()
{
    char str1[] = "abc";
    char str2[] = "bbc";
    printf("%d\n", str1 == str2);
    printf("%d\n", strcmp(str1, str2));
    return 0;
}

// 0
// -1

自定义strcmp

int fnMyStrCmp(const char *s1, const char *s2)
{
    // int flag = 0;
    // while (s1[flag] == s2[flag] != '\0')
    // {
    //     if (s1[flag] != s2[flag])
    //     {
    //         break;
    //     }
    //     else if (s1[flag] == s2[flag])
    //     {
    //         break;
    //     }
    //     flag++;
    // }
    while (*s1 == *s2 && *s1 != '\0')
    {
        s1++;
        s2++; 
    }
    return *s1 - *s2;
}

strcpy

  • char * strcpy(char *restrict dst, const char *restrict src);
  • src的字符串拷贝到dst
    • restrict表明srcdst不重叠(c99)
  • 返回dst
    • 为了能链起代码来

复制一个字符串

  • char *dst = (char *)malloc(strlen(src) + 1); // '\0'也占用一个空间
  • strcpy(dst, src)
char *fnMyCpy(char *dst, const char *src)
{
    int flag = 0;
    while (src[flag] != '\0')
    {
        dst[flag] = src[flag];
        flag++;
    }
    // while (*dst++ = *src++);
    dst[flag] = '\0';
    return dst;
}

字符串搜索函数

  • char * strchr(const char *s, int c);
  • char * strrchr(const char *s, int c);
  • 返回NULL表示没有找到
    • 如何找第二个?

字符串中找字符串

  • char * strstr(const *s1, const char *s2);
  • char * strcasestr(const char *s1, const char *s2);