C语言 07. 指针和字符串

1.指针和内存单元

指针: 地址。

内存单元: 计算机中内存最小的存储单位。——内存单元。大小一个字节(8位)。 每一个内存单元都有一个唯一的编号(数)。 称这个内存单元的编号为 “地址”。

指针变量:存地址的变量。
在这里插入图片描述
在这里插入图片描述

    int a;
    printf("打印a变量的地址:%p\n", &a);     // 打印a变量的地址:0000007B5476F5C4      16位*4=64位

2.指针定义和使用:

int a = 10;
int *p = &a;            int* p;--- windows;    int *p ---Linux       int * p ;   "*"  是一个类型描述符,描述整型指针
*p = 250;              指针的 解引用( 间接引用)

*p :

将p变量的内容取出,当成地址看待,找到该地址对应的内存空间。
    如果做左值(在等号左边): 存数据到空间中。
    如果做右值(在等号右边): 取出空间中的内容。

    int a = 10;
    printf("原来:   &a = %p  , a = %d\n", &a,a);
    int* p = &a;
    printf("原来:   &p = %p  , p = %p\n", &p, p);
    *p = 2000;       // a = 2000;
    printf("变化后: &a = %p  , a = %d\n", &a, a);
    printf("变化后: &p = %p  , p = %p\n", &p, p);
    /*
    原来:   &a = 0000001B68DDF654  , a = 10
    原来:   &p = 0000001B68DDF678  , p = 0000001B68DDF654
    变化后: &a = 0000001B68DDF654  , a = 2000
    变化后: &p = 0000001B68DDF678  , p = 0000001B68DDF654
    */
    */
    printf("sizeof(int *) = %u\n", sizeof(int *));        //  8
    printf("sizeof(float *) = %u\n", sizeof(float *));    // 8
    printf("sizeof(double *) = %u\n", sizeof(double *));  // 8

在这里插入图片描述

int a, *p, *q, b;

任意“指针”类型大小: 指针的大小与类型 无关。 只与当前使用的平台架构有关。 32位:4字节。 64位: 8字节。

野指针:

1) 没有一个有效的地址空间的指针。

   int *p;
   *p = 1000;

2)p变量有一个值,但该值不是可访问的内存区域。
int *p = 10; 0-255地址是确定留给操作系统使用的,所以这里是不可用的地址。随便一个数字即使超过255,比如1000,但是你不知道它是否被使用,所以不可以在定义时将值直接赋给指针变量。所以在使用时都是利用另外一个变量进行初始化。
*p = 2000;
【杜绝野指针】

空指针:

int *p = NULL;     #define NULL ((void *)0)     
*p 时 p所对应的存储空间一定是一个 无效的访问区域。

万能指针/泛型指针(void *):

可以接收任意一种变量地址。但是,在使用【必须】借助“强转”具体化数据类型。
    int a = 345;
    void* p;        // 万能指针
    p = &a;
    printf("%d\n", *((int *)p));     // 345, (int *)p:把p强转成为int * 指针类型,*[(int *)p]:取p指针的内容
/*--------------------------------------*/
 char ch = 'R';
        void *p;  // 万能指针、泛型指针
        p = &ch;
        printf("%c\n", *(char *)p);

3.const关键字:

3.1修饰变量:

/*
const int a = 20;
a = 5;           // 这一句话会报错,因为a值被确定了
*/
    const int a = 20;
    int *p = &a;
    *p = 650;
    printf("%d\n", a);

3.2修饰指针:

const int *p;  	可以修改 p ,不可以修改 *p。
int const *p;	同上。
int * const p;	可以修改 *p,不可以修改 p。
const int *const p;	 不可以修改 p ,不可以修改 *p。

总结:const 向右修饰,被修饰的部分即为只读(不可修改)。
常用:在函数形参内,用来限制指针所对应的内存空间为只读。

    int a = 10;
    int b = 30;
    //const int* p = &a;
    //*p = 500;      // 报错
    //p = &b;        
    //int const * p = &a;
    //*p = 500;      // 报错
    //p = &b;
    //int * const p = &a;
    //*p = 500;      
    //p = &b;         // 报错
    const int * const p = &a;
    *p = 500;      // 报错
    p = &b;        // 报错

4.指针和数组:

4.1数组名是地址常量】

— 不可以被赋值。 ++ / – / += / -= / %= / /= (带有副作用的运算符,这些变量会使变量改变)

int a[] = {1,2,3};
int b[3];
b = a;                    // 这句话会报错,因为数组名b是常量

4.2 指针是变量。

可以用数组名给指针赋值。 ++ –

int a[] = {1,2,3};
int b[3];
int *p = b;
p = a;                      // 这句话是可以的,p是变量

4.3 取数组元素:

在这里插入图片描述

    int arr[] = {1,3, 5, 7, 8};
    int *p = arr;  
    arr[0] == *(arr+0) == p[0] == *(p+0)
    int a[] = { 1,2,3,4,5,6,7,8,9,0 };
    int n = sizeof(a) / sizeof(a[0]);
    int* p = a;
    for (size_t i = 0; i < n; i++)
    {
        //printf("%d ", a[i]);          // a[i]== *(a+i)
        //printf("%d ", *(a + i));
        //printf("%d", p[i]);              // p = a
        printf("%d", *(p + i));
    }

4.4 指针和数组区别:

    1. 指针是变量。数组名为常量。
    2. sizeof(指针) ===》 4字节 / 8字节
         &emsp; sizeof(数组) ===》 数组的实际字节数。

指针++ 操作数组:

    // 使用指针++操作数组元素
    int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
    int* p = arr;
    int n = sizeof(arr) / sizeof(arr[0]);
    printf("开始时:p = %p\n", p);
    for (size_t i = 0; i < n; i++)
    {
        printf("%d", *p);
        p++;         // 一次加过一个Int大小
    }                   // p指针指向一块无效的内存区域,p为野指针
    printf("\n");
    printf("结束时:p = %p\n", p);
    /*
    开始时:p = 000000872757FB48
    1234567890
    结束时:p = 000000872757FB70
    */

p的值会随着循环不断变化。打印结束后,p指向一块无效地址空间(野指针)。

4.5 指针加减运算:

数据类型对指针的作用
1)间接引用:决定了从指针存储的地址开始,向后读取的字节数。 (与指针本身存储空间无关。)
在这里插入图片描述

   2)加减运算: 决定了指针进行 +1/-1 操作向后加过的 字节数。
    int a = 0x12345678;
    short* p = &a;
    printf("p  = %p\n", p);
    printf("p+1=%p\n", p + 1);
    /*
    p  = 0000005250F7FBD4
    p+1= 0000005250F7FBD6
    */

5.指针运算

5.1 指针 * / % : error!!! 因为指针是地址

5.2 指针 ± 整数:

1) 普通指针变量±整数

   char *p; 打印 p 、 p+1  偏过 1 字节。
   short*p; 打印 p 、 p+1  偏过 2 字节。
   int  *p; 打印 p 、 p+1  偏过 4 字节。         

2)在数组中± 整数

            short arr[] = {1, 3, 5, 8};
            int *p = arr;
//         int *p = &a[5];           // 指向第五个元素的地址
            p+3;            // 向右(后)偏过 3 个元素
            p-2;            // 向前(左)偏过 2 个元素

3)&数组名 + 1

   加过一个 数组的大小(数组元素个数 x sizeof(数组元素类型))
    int a[] = {1,2,3,4,5,6,7,8,9,0};
    printf("a     = %p\n", a);
    printf("&a[0] = %p\n", &a[0]);
    printf("a+1   = %p\n", a + 1);
    printf("&a = %p\n", &a);
    printf("&a+1  = %p\n", &a + 1);
    /*
    a     = 000000616B3DFA78
    &a[0] = 000000616B3DFA78
    a+1   = 000000616B3DFA7C
    &a = 000000616B3DFA78
    &a+1  = 000000616B3DFAA0 
    8+2*16 = 40 字节
    */

5.3 指针 ± 指针:

指针 + 指针: error!!!
指针 - 指针:
1) 普通变量来说, 语法允许。无实际意义。【了解】
2) 数组来说:偏移过的元素个数。

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

【指针实现 strlen 函数】:

    char str[] = "hello";
    char *p = str;
    while (*p != '\0')
    {
        p++;
    }
    p-str; 即为 数组有效元素的个数。

5.4指针比较运算:> < =

1) 普通变量来说, 语法允许。无实际意义。
2) 数组来说: 地址之间可以进行比较大小。
可以得到,元素存储的先后顺序。
3)空指针

        int *p;
        p = NULL;        // 这两行等价于: int *p = NULL;
        if (p != NULL)
        printf(" p is not NULL");
        else 
        printf(" p is NULL");

5.指针数组:

一个存储地址的数组。数组内部所有元素都是地址。

    1) 
       int a = 10;
       int b = 20;
       int c = 30;
		* p1 = &a;
		*p2 = &b;
		*p3 = &c;
       int *arr[] = {p1,p2,p3}; // 数组元素为 整型变量 地址        // 从低地址向高地址存储

在这里插入图片描述

    2) 

        int a[] = { 10 };
        int b[] = { 20 };
        int c[] = { 30 };
        int *arr[] = { a, b, c }; // 数组元素为 数组 地址。  
    int a = 10;
    int b = 20;
    int c = 30;
    int* p1 = &a;
    int* p2 = &b;
    int* p3 = &c;
    int* arr[] = { p1,p2,p3 };  // 整型指针数组arr,存的都是整形地址。
    printf("*(arr[0])=%d\n", **arr);
    printf("**(*(arr + 0))=%d\n", *(*(arr + 0))); //arr[0] == * (arr+0)
    /*
    *(arr[0])=10
    *(*(arr + 0))=10
    */

指针数组本质,是一个二级指针。
二维数组, 也是一个二级指针。

6.多级指针:

int a = 0;
int *p = &a;                  一级指针是 变量的地址。
int **pp = &p;                二级指针是 一级指针的地址。
int ***ppp = &pp;            三级指针是 二级指针的地址。          // int **p[]
int ****pppp = &ppp;            四级指针是 三级指针的地址。    【了解】
......
多级指针,不能  跳跃定义!

对应关系:
ppp == &pp;            三级指针
*ppp == pp == &p;             二级指针
**ppp == *pp == p == &a                一级指针
***ppp == **pp == *p == a                普通整型变量

*p : 将p变量的内容取出,当成地址看待,找到该地址对应的内存空间。
    如果做左值: 存数据到空间中。
    如果做右值: 取出空间中的内容。
    int a = 10;
    int* p = &a;
    int** pp = &p;
    // int **pp = &(&a);      不允许!!!
    int*** ppp = &pp;
    printf("***ppp = %d\n", ***ppp);
    printf("**pp   = %d\n", **pp);
    printf("*ppp   = %d\n", *p);
    printf("a      = %d\n", a);
/*
        ***ppp = 10
        **pp   = 10
        *ppp   = 10
        a      = 10
*/

7.指针和函数:

栈 帧:

当函数调用时,系统会在 stack 空间上申请一块内存区域,用来供函数调用,主要存放 形参 和 局部变量(定义在函数内部)。

当函数调用结束,这块内存区域自动被释放(消失)。

传值和传址:

传值:函数调用期间,实参将自己的值,拷贝一份给形参。
在这里插入图片描述

传址:函数调用期间,实参将地址值,拷贝一份给形参。 【重点】
(地址值 --》 在swap函数栈帧内部,修改了main函数栈帧内部的局部变量值)
在这里插入图片描述

指针做函数参数: 调用时,传有效的地址值。

  int swap2(int *a, int *b);
  int swap2(char *a, char *b);

数组做函数参数:

    ```void BubbleSort(int arr[10]) == void BubbleSort(int arr[])  == void BubbleSort(int *arr) ```
    传递不再是整个数组,而是数组的首地址(一个指针)。
    所以,当整型数组做函数参数时,我们通常在函数定义中,封装2个参数。一个表数组首地址,一个表元素个数。

void BubbleSort (int arr[], int n)

指针做函数返回值:指针做函数返回值,不能返回【局部变量的地址值】。

int *test_func(int a, int b);

int m = 100;         // 全局变量,对应空间消失==》程序结束
int* test_func(int a, int b)
{
    return &m;
    /*int p = 1234;       // 局部变量,这个函数结束后,p的地址会释放,所以不能用这种方式来return
    return &p;*/
}
int main(void)
{
    int* ret = NULL;
    ret = test_func(10, 20);
    printf("ret = %d\n", *ret);       // 100
}

数组做函数返回值:
C语言,不允许!!!! 只能写成指针形式。int* test_func(int a, int b)会报错

8.指针和字符串:

1)字符串的定义方式:

   char str1[] = {'h', 'i', '\0'};            变量,可读可写
   char str2[] = "hi";                      变量,可读可写
   char *str3 = "hi";                       常量,只读 ,这里存的是字符串的地址
   char *str4 = {'h','i','\0'};              // 错误!!!
    str3变量中,存储的是字符串常量“hi”中首个字符‘h’的地址值。
     str3[1] = 'H';    // 错误!!
    char str1[] = "hello";           // {'h','e','l','l','o','\0'};
    char* str2 = "hello";            // "hello"是一个字符串常量。不能修改
    str1[0] = 'R';
    /*str2[0] = 'R';*/               // 这一句话会报错
    printf("str1 = %s\n", str1);     // Rello
    printf("str2 = %s\n", str2);     // hello

2)当字符串(字符数组), 做函数参数时, 不需要提供2个参数。 因为每个字符串都有 ‘\0’。

【 练习:比较两个字符串: strcmp();实现】

比较 str1 和 str2, 如果相同返回0, 不同则依次比较ASCII码,str1 > str2 返回1,否则返回-1,按位比ACSSI码

//数组方式
int cmp(char* str1, char* str2)
{
    int i = 0;
    while (str1[i] == str2[i])     // *(str1+i)==*(str2+i)
    {
        if (str1[i] == '\0')
        {
            return 0;                // 2字符串一样
        }
        i++;
    }
    return str1[i] > str2[i] ? 1 : -1;
}
//指针方式
int cmp_point(char* str1, char* str2)
{
    while (*str1==*str2)     // 这里面取的是首地址的值
    {
        if (str1 == '\0')
        {
            return 0;                // 2字符串一样
        }
        str1++;
        str2++;
    }
    return *str1 > *str2 ? 1 : -1;
}
int main(void)
{
    char *str1 = "hello";        // 这个里面一定要写 *str1,不能写str1
    char *str2 = "helll";
    //int ret = cmp(str1,str2);    // 这个里面不需要写&str1,因为str1本身就是代表地址,不需要再加取地址符
    int ret = cmp_point(str1, str2);
    if (ret == 0)
        printf("str1=str2\n");
    else if (ret == 1)
        printf("str1>str2\n");
    else if (ret == -1)
        printf("str1<str2\n");
    else
        printf("异常\n");
}

【 练习:字符串拷贝:】

//src: 源 dst:目标
// 数组实现方式
void copy(char* src,char *dst)
{
    int i = 0;
    while (*(src + i))
    {
        *(dst + i) = *(src + i);
        i++;
    }
    *(dst + i + 1) = '\0';
    return 0;
}
// 指针实现方式
void copy_point(char* src, char* dst)
{
    while (*src)
    {
        *dst = *src;
        dst++;
        src++;
    }
    *dst = '\0';   // 因为上面赋值之后指针都会向右移动,所以这里不需要写  *(dst+1)='\0';
    return NULL;
}
int main(void)
{
    char* src = "hello0";
    char* dst[100] = { 0 };
    //copy(src, dst);
    //printf("char* dst = %s\n", dst);
    copy_point(src, dst);
    printf("char* dst = %s\n", dst);
}

【 练习:在字符串中查找字符出现的位置:找到字符串第一次出现时字符串和以及字符串后面的数字的值】

char* myStrch(char* str, char ch)
{
    while (*str)
    {
        if (*str == ch)
        {
            return str;
        }
        str++;
    }
    return NULL;
}
// hellowrld --- 'o'
char* myStrch2(char* str, char ch)
{
    int i = 0;
    while (str[i])
    {
        if (str[i] == ch)
        {
            return &str[i];
        }
        i++;
    }
    return NULL;
}
int main(void)
{
    char str[] = "hello world";
    char ch = 'l';
    char* ret = NULL;
    ret = myStrch2(str, ch);
    printf("ret = %s\n", ret);
}

【 练 习:字符串去空格】

// 指针
void qkg(char* str1, char* str2)
{
    while (*str1)
    {
        if (*str1 != ' ')
        {
            *str2 = *str1;
            str2++;
        }
        str1++;
    }
    *str2 = '\0';
}
// 数组
void qkg_array(char* str1,char* str2)
{
    int i=0,j=0;
    while (str1[i])
    {
        if (str1[i] != ' ')
        {
            str2[j] = str1[i];
            j++;
        }
        i++;
    }
}
int main(void)
{
    char* str1 = "Hello World";
    char str2[100];    // 字符串的定义,有*就没有[],有[]就没有*
    /*qkg(str1, str2);*/
    qkg_array(str1, str2);
    printf("str2 = %s\n", str2);  
    // 如果字符串用char * 定义,后面取字符串的值就需要*,如果字符串用char []定义,后面取字符串就只需要字符串名字
}

带参数的main函数:

无参main函数: int main(void) == int main()
带参数的main函数:int main(int argc, char *argv[]) == int main(int argc, char **argv) 写法固定
参1:表示给main函数传递的参数的总个数。
参2:是一个数组!数组的每一个元素都是字符串 char * (字符串)
在这里插入图片描述

int main(int argc,char* argv[])
{
    for (size_t i = 0; i < argc; i++)
    {
        printf("%s\n", argv[i]);
    }
}

在这里插入图片描述

测试1(终端): 
    命令行中的中,使用gcc编译生成 可执行文件,如: test.exe
    test.exe abc xyz zhangsan nichousha 
    -->
    argc --- 5
    test.exe -- argv[0]
    abc -- argv[1]
    xyz -- argv[2]
    zhangsan -- argv[3]
    nichousha -- argv[4]
测试2(VS):
    在VS中。项目名称上 --》右键--》属性--》调试--》命令行参数 --》将 test.exe abc xyz zhangsan nichousha 写入。
    -->
    argc --- 5
    test.exe -- argv[0]
    abc -- argv[1]
    xyz -- argv[2]
    zhangsan -- argv[3]
    nichousha -- argv[4]

【str 中 substr 出现次数】:

strstr函数: 在 str中,找substr出现的位置。
char *strstr(char *str, char *substr) -- #include <string.h>
参1: 原串
参2: 子串
返回值: 子串在原串中的位置。(地址值);
如果没有: NULL

    char* ret = strstr("hellollolllolllo", "llo");
    printf("ret = %s\n", ret);  // ret = llollolllolllo

在这里插入图片描述

实 现:

int str_times(char* ch, char* substr)
{
    int count = 0;
    char *p = strstr(ch, substr);              // llollolllollo
    while (p!=NULL)
    {
        count++;
        p += strlen(substr);                   // llolllollo
        p = strstr(p, substr);                     // 
    }
    return count;
    
}
int main(int argc,char* argv[])
{
    int ret;
    char* str = "hellollolllollo";
    char sbustr[] = "llo";
    ret = str_times(str, sbustr);
    printf("ret = %d\n", ret);
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值