第8章 指针与字符

8.1 指针

8.1.1 取地址运算

sizeof

  • 是一个运算符,给出某个类型或变量在内存中所占据的字节数
    • sizeof(int)
    • sizeof(i)
  • 输出:printf("%ld", sizeof(double))
    • ld是长整数型

运算符&

  • scanf("%d", &i);里的&

  • 获得变量的地址,它的操作数必须是变量

    int i=0;
    printf("0x%x", &i);//不对
    printf("%p, &i");//得到一个地址
    
    //但是
    int i = 0;
    int p;
    p=(int)&i;//int是强制转化
    printf("0x%x", p);
    
    
  • 还有另一个事情,在不同位数的架构下,地址也不一样

  • 地址的大小是否与int相同取决与编译器

    • int i; printf("%p", &i);

内存存储

  • i和p都存在一个叫堆栈里的地方,是从自顶向下来排
    • 先写的会更高

试试这些&

  • 变量的地址

  • 相邻变量的地址

  • &的结果的sizeof

  • 数组的地址

  • 数组的单元的地址

  • 相邻的数组单元的地址

  • 试验代码

    int i[10];
    
    printf("%p\n", &i);//0xbff8dd44
    printf("%p\n", i);//0xbff8dd44
    
    printf("%p\n", i[0]);//0xbff8dd44
    printf("%p\n", i[1]);//0xbff8dd48
        
    
    

8.1.2 指针

scanf

  • 如果能够将取得的变量的地址传递给一个函数,能否通过这个地址咋那个函数内访问这个变量?
    • scanf("%d", &i);就是一个例子
  • scanf()的原型应该是怎样的?我们需要一个参数能保存别的变量的地址,如何表达能够保存地址的变量?
  • 是否可以运用到赋值数组呢?

指针

  • 就是保存地址的变量

    int i;
    int* p = &i;//p是一个指针,*表示了出来,表示指向了是一个int,用来存储i的地址
    //可以说“p指向了i”,也就是p得到了i的地址了
    
    int* p, q;//这里,p是指针,但q是一个普通的int类型变量
    
    int *p, q;//可以靠近也可以不靠近,这个*不是给int的
    
    //想表达q也是指针怎么写?
    int *p, *q;
    

指针变量

  • 变量的值是内存的地址
    • 普通变量的值是实际的值
    • 指针变量的值是具有实际值变量的地址
75

作为参数的指针

  • void f(int *p);
  • 在被调用的时候得到了某个变量的地址:
    • int i=0; f(&i);
void f(int *p);

int main() {
    int i = 6;
    printf("%p\n", &i);
    f(&i);
    
    return 0;
}

void f(int *p) {
    printf("%p\n", p);
}
  • 在函数里面可以通过这个指针访问外面的这个i
    • 但不知道它叫做i,只知道地址
    • 不像和普通函数一样只能传值进去而无法做到访问那个函数外面的变量
    • 而当我们可以访问这个地址的时候,这就意味着我们可以读,可以写了

访问那个地址上的变量

  • *是一个单目运算符,用来访问指针的值所表示的地址上的变量
  • 可以做右值也可以做左值
    • int k = *p;
    • *p = k+1;
#include <stdio.h>

void f(int *p);
void g(int k);

int main() {
    int i = 6;
    printf("%p\n", &i);
    f(&i);
    g(i);
    
    return 0;
}

void f(int *p) {
    printf("%p\n", p);
    printf("%d\n", *p);//输出值
	*p=36; //改变了外面变量的值了
}

void g(int k) {
	printf("%d\n", k);
}

传入地址

  • 为什么?
    • int i; scanf("%d", i);
  • 编译没有报错?
    • 但运行会出错

传入函数的数组成了什么?

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

数组参数

  • 以下四种函数原型是等价的

    int sum(int *ar, int n);
    int sum(int *, int);
    int sum(int ar[], int n);
    int sum(int [], int);
    

访问函数外数组案例

#include <stdio.h>

int maxmin(int a[], int len, int *max, int *min);

int main() {
    int a[] = {1, 2, 3, 4, 5, 6, 7, 18, 11, 10};
    int max, min;
    maxmin(a, sizeof(a)/sizeof(a[0]), &max, &min);
    printf("%d %d", max, min);
    return 0;
}


int maxmin(int a[], int len, int *max, int *min) {
    int i;
    *max = *min = a[0];
    for(i=0;i<len;i++) {
        if(a[i]>*max) *max=a[i];
        if(a[i]<*min) *min=a[i];
    }
    printf("%d", sizeof(a));
    printf("%d", sizeof(int*));
}

数组变量是特殊的指针

  • 数组变量本身表达地址,所以

    • int a[10]; int*p=a;//无需用&取地址
    • 但是数组的单位表达式变量,需要用&取地址
    • a==&a[0]
  • []运算符可以对数组做,也可以对指针做;

    • p[0]<==>a[0]

      int *p = &min;
      printf("%d\n", *p);
      printf('%d\n', p[0]);//得到min的值
      
  • *运算符可以对指针做,也可以对数组做

    • *a=25;
  • 数组变量是const的指针,所以两个数组不能被互相赋值

    • int a[] <==> int * const a = ...
    int b[];=====int * const b;//所以数组是个常量指针,所以不能被赋值
    b=a;//这样的赋值数组是错误的
    
    int *q = a;//这个可以
    

8.2 字符

8.2.1 字符类型

字符类型

  • char是一种整数,也是一种特殊的类型:字符。这是因为:
    • 用单引号表示的字符字面量:'a', '1'
    • "也是一个字符
    • printf和scanf里用%c来输入输出字符

字符的输入输出

  • 如何输入‘1’这个字符给char c?
int main() {
    char c;
    char d;
    c = 1;
    d = '1';
    if(c==d) {
        printf("相等\n");
    } else {
        printf("不相等\n");
    }
    printf("c=%d\n", c);//1
    printf("d=%d\n", d);//49,因为是ASCII编码
    printf("%c", d);
    
    int code = 49;
    printf("%c", code);//'1'
    
    
    49=='1';//正确
    
    return 0;
}

混合输入

int i;
char c;
scanf("%d %c", &i, &c);
printf("i=%d, c=%d, c='%c'", i, c, c);

int i;
char c;
scanf("%d%c", &i, &c);//输入“12 1”,会得到i=12,c=32,c=' ',空格就是32
printf("i=%d, c=%d, c='%c'", i, c, c);
//如果是12a,就得到i=12, c=97, c='a'

字符计算

char c = 'A';
c++;
printf("%c\n", c);//B, 而如果是c+=2,就是C


iny i = 'Z'-'A';
printf("%d\n", i);
  • 一个字符加一个数字得到ASCII码表中那个数之后的字符
  • 两个字符的减,得到它们在表中的距离

大小写转换

  • 字母在ASCII表中是顺序排序的
  • 大写字母和小写字母是分开排列的,并不在一起
  • 'a'-'A'可以得到两段之间的距离,于是
    • a+'a'-'A'可以把一个大写字母变成小写字母
    • a+'A'-'a'可以把一个小写字母变成大写字母

8.2.2 逃逸字符

制表位

  • 每行的固定位置

  • 一个\t使得输出从下一个制表位开始

  • 用\t才能使得上下两行对齐

    printf("123\t456\n");
    printf("12\t456\n");
    
76

回车换行

  • 源自打字机的动作
  • 所以就是为什么会有两个换行回车

8.3 字符串

8.3.1 字符串

字符数组

  • char word[] = {'H', 'e', 'l', 'l', 'o', '!'};

    77
  • 但这不是C语言的字符串,因为不能用字符串的方式做计算

  • 这个只是C语言的一个字符数组(因为它没有字符串的标志-后面的整数0)

字符串

78
  • 你后面去掉反斜杠,只放0也可以,它就是个整数
  • '\0'我们只要留下空格,它就能自动放入进去

字符串

  • 以0(整数0)结尾的一串字符
    • 0或'\0'是一样的,但是和'0'不同
  • 0是标志字符串的结束,但它不是字符串的一部分
    • 计算字符串长度的时候不包含这个0
  • 字符串以数组的形式存在,以数组或指针的形式访问
    • 更多的是以指针的形式
  • string.h里有很多处理字符串的函数

字符串变量

char *str = "Hello";
char word[] = "Hello";
char line[10] = "Hello";//占6个

字符串常量

  • "Hello"这个叫做字符串的常量或者叫字符串的字面量
  • "Hello"会被编译器变成一个字符数组放在某处,这个数组的长度是6,结尾还有表示结束的0

字符串

  • C语言的字符串是以字符数组的形态存在的
    • 不能用运算符对字符串做运算
    • 通过数组的方式可以遍历字符串
  • 唯一特殊的地方是字符串字面量可以用来初始化字符数组
  • 以及C语言标准库提供了一系列字符串函数

8.3.2 字符串变量

字符串常量

char* s = "Hello, world!";
s[0] = 'B';

printf("Here! s[0]=%c\n", s[0]);//输出错误,这是因为我们在输出前就已经错了

//-----------
char *s = "Hello, world!";
char *s2 = "Hello, world!";
printf("s=%p\n", s);//输出一段地址
printf("s2=%p\n", s2);//输出一段地址
//它们两个的地址相同

int i = 0;
printf("&i=%p", &i);//输出的地址很大
79

我们程序会对其进行保护,不会再允许你修改,也就是只能读,不能改

  • s是一个指针,初始化为指向一个字符串常量

    • 由于这个常量所在的地方,所以实际上s是const char* s,但是由于历史的原因,编译器接受不带const的写法
    • 但是试图对s所指的字符串做写入会导致严重的后果
  • 如果需要修改字符串,应该用数组:

    char s[] = "Hello, world!";
    
  • 指针s与数组s的区别就是:指针s会指向某个字符串,但数组s会告诉你,字符串就在我这里

  • 测试

    char s3[] = "Hello World";
    s3[0] = 'B';
    printf("s3[0]=%c", s3[0]);
    printf("s3=%s", s3);
    

指针还是数组?

char *str = "Hello";
char word[] = "Hello";
  • 数组:这个字符串在这里
    • 作为本地变量空间自动被回收
  • 指针:这个字符串不知道在哪里
    • 处理参数
    • 动态分配空间
  • 如果要构造一个字符串用数组;如果要处理一个字符串用指针

char*是字符串?

  • 字符串可以表达为char*的形式
  • char*不一定是字符串
    • 本意是指向字符的指针,可能指向的是字符的数组(就像int*一样)
    • 只有它所指的字符数组有结尾的0,才能说它所指的是字符串

8.4 字符串的一些方法函数

8.4.1 字符串输入输出

字符串赋值?

char *t = "title";
char *s;
s = t;
  • 其实这段代码没有产生新的字符串,只是让指针s指向了t所指的字符串,对s的任何操作就是对t做的
80
  • 在接下来学了字符串函数后就能够让s产生出新的字符串

字符串输入输出

  • char string[8];
  • scanf("%s", string);
  • printf("%s", string);
  • scanf读入一个单词(到空格,tab或回车为止)

测试代码

char s1[8], s2[8];
scanf("%s", s1);
scanf("%s", s2);
printf("%s##%s##", s1, s2);
  • 这个scanf是不安全的,因为不知道要读入的内容的长度

安全的输入

char s1[8], s2[8];
scanf("%7s", s1);
scanf("%7s", s2);
printf("%s##%s##", s1, s2);
  • 这个常数说明最多读7个字符,小于7没有问题
  • 这个数字应该比数组的大小小一
  • 将会按字符数量来区分单词,下一个内容将会交给下一个scamf

常见错误

  • char *string;
  • scanf("%s", string);

空字符串

  • char buffer[100] = "";
    • 这是一个空的字符串,buffer[0]=='\0',但依旧有效
  • char buffer[] = "";
    • 这个数组的长度只有1,也就说除了0,放不下任何东西了

8.4.2 字符串函数

string.h是字符串函数库

strlen

  • size_t strlen(const char *s);
  • 返回s的字符串长度(不包括结尾的0)
char line[] = "Hello";
printf("strlen=%lu\n", strlen(line));
printf("sizeof=%lu\n", sizeof(line));
  • lu输出无符号长整型整数

strcmp

char s1[] = "abc";
char s2[] = "abc";
printf("%d", strcmp(s1, s2));//0


char s1[] = "abc";
char s2[] = "Abc";
printf("%d", strcmp(s1, s2));//32

char s1[] = "abc";
char s2[] = "bbc";
printf("%d", strcmp(s1, s2));//-1
  • s1-s2

strcpy

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

strcat

  • char *strcat(char *restrict s1, const char *restrict s2);
  • 把s2拷贝到s1的后面,练成一个长的字符串
  • 返回s1
  • s1必须具有足够的空间

安全问题

  • strcpy和strcat都可能出现安全问题
    • 如果目的地没有足够的空间?

安全版本

  • char *strncpy(char *restrict dst, const char *retrict src, size_t n);
  • char *strncat(char *restrict s1, const char *restrict s2, size_t n);
  • int strncmp(const char *s1, const char *s2, size_t n);

字符串中找字符

  • char *strchr(const char *s, int c);从左边找第一次出现的字符
  • char *strrchr(const char *s, int c);从右边找第一次出现的字符
  • 返回NULL表示没有找到
  • 如何寻找第2个
赞赏