C语言进阶(六):指针数组字符串函数

目录

一、指针的本质分析

1、*号的意义

(1)指针的声明和使用

(2)实践:指针使用示例

2、传值调用与传址调用

(1)什么是传值调用,传址调用?

(2)实践:利用指针交换变量

3、常量与指针

(1)const与指针

(2)实践:const与指针

4、小结

二、数组的本质分析

1、数组的概念

2、数组的大小

(1)数组大小的获取(内存、元素数量)

(2)实践:数组的初始化

3、数组地址与数组名

(1)数组地址与数组名

(2)实践:数组名和数组地址

4、数组名的盲点

(1)数组名的易错点

(2)实践:数组和指针并不相同

5、小结

三、指针和数组分析(上)

1、数组的本质

(1)什么是数组?

(2)实践:a + 1的结果是什么?

2、指针的运算

3、指针的比较

(1)指针的比较

(2)实践:指针运算初探

(3)实践:指针运算的应用

4、小结

四、指针和数组分析(下)

1、数组的访问方式

2、下标形式VS 指针形式

(1)下标形式与指针形式的效率及转化问题

(2)实践:数组的访问方式

(3)实践:数组和指针不同

3、a和&a的区别

(1)首元素地址和数组地址的区别

(2)实践:指针运算经典问题

4、数组参数

(1)数组作为参数入参

(2)实践:虚幻的数组参数

5、小结

五、C语言中的字符串

1、字符串的概念

2、字符数组与字符串

(1)字符串的本质

(2)实践:字符数组与字符串

3、鲜为人知的小秘密

4、字符串字面量

(1)“Hello World ! ”是一个无名的字符数组

(2)实践:字符串字面量的本质

5、字符串的长度

(1)字符串的长度

(2)实践:strlen的使用

6、小结

六、字符串典型问题分析

1、snprintf 函数

2、结束符'\0'

3、strcmp函数

4、字符串循环右移 

七、数组指针和指针数组分析

1、数组类型

2、定义数组类型

3、数组指针

(1)数组指针的定义

(2)实践:数组类型和数组指针 

4、指针数组

(1)指针数组的定义

(2)实践:指针数组的应用

5、小结

八、main函数与命令行参数

1、main函数的概念

(1)main函数的概念

2、main函数的本质

(1)main函数的本质

3、main函数的参数

(1)main函数的参数

(2)实践:main函数的参数

4、小技巧

(1)面试中的小问题

(2)实践:gcc中的属性关键字

5、小结

九、多维数组和多维指针

1、指向指针的指针

2、为什么需要指向指针的指针?

实践:重置动态空间大小

3、二维数组与二级指针

实践:遍历二维数组

4、数组名

(1)一维数组和二维数组及其数组名

(2)实践:如何动态申请二维数组

5、小结

十、数组参数和指针参数分析

1、退化的意义

2、二维数组参数

3、等价关系

4、被忽视的知识点

实践:传递与访问二维数组

5、小结

十一、函数与指针分析

1、函数类型

2、函数指针

实践:函数指针的使用

3、回调函数

实践:回调函数使用示例

4、小结

十二、指针阅读技巧分析

1、笔试中的问题

2、指针阅读技巧解析

3、小结


一、指针的本质分析

1、*号的意义

(1)指针的声明和使用

  • 在指针声明时,*号表示所声明的变量为指针
  • 在指针使用时,*号表示取指针所指向的内存空间中的值
    • *号类似一把钥匙,通过这把钥匙可以打开内存,读取内存中的值。
int i = 0;
int j = 0;   
//指针声明
int* p = &i; 
//取值
j = *p;
//赋值
*p = 10;

图解如下:

(2)实践:指针使用示例

  • 指针变量所占内存都是4字节(32位系统)
  • 指针类型决定访问内存时的长度范围

       内存是由字节组成的,每个字节都有一个编号。指针变量主要是存放相同数据类型的变量的首地址。这里的这个地址其实就是内存的某个字节的编号。而这个编号的确定是与地址总线有关。如果地址总线是32位,则它的寻址范围是0~2^32(0~4G)。那么为一个字节的编址就会由32个0或者1组成。例如第一个字节的编址是32个0,最后一个的编址是32个1。一个字节有8位,32位则需要4个字节。

       简单的说32位的操作系统就是指:地址总线是32位的系统。那么,也就是说操作系统的位数决定了指针变量所占的字节数

#include <stdio.h>

int main()
{
    int i = 0;
    int* pI;
    char* pC;
    float* pF;
    
    pI = &i;   // 将i的地址赋值给pI
    
    *pI = 10;  // 间接将10赋值给i
    
    printf("%p, %p, %d\n", pI, &i, i); // i的地址  i的地址  i的值
    printf("%d, %d, %p\n", sizeof(int*), sizeof(pI), &pI);  // 4  4  pI的地址
    printf("%d, %d, %p\n", sizeof(char*), sizeof(pC), &pC); // 4  4  pC的地址
    printf("%d, %d, %p\n", sizeof(float*), sizeof(pF), &pF);// 4  4  pF的地址
    
    return 0;
}

 

2、传值调用与传址调用

(1)什么是传值调用,传址调用?

  • 指针是变量,因此可以声明指针参数;
  • 当一个函数体内部需要改变实参的值,则需要使用指针参数;
  • 函数调用时实参值将复制到形参;
  • 指针适用于复杂数据类型作为参数的函数中;
  • 传值调用并不改变传入的变量;
  • 传址调用是传入变量地址,从而可以改变传入的地址对应的值。

(2)实践:利用指针交换变量

① 传值调用:这部分代码并不能够实现交换变量的值,这就是传值调用。

// 这部分代码并不能够实现交换变量的值,这就是传值调用
#include <stdio.h>

int swap(int a, int b)
{
    int c = a;
    
    a = b;
    
    b = c;
}

int main()
{
    int aa = 1;
    int bb = 2;
    
    printf("aa = %d, bb = %d\n", aa, bb);
    
    swap(aa, bb);
    
    printf("aa = %d, bb = %d\n", aa, bb);
    
    return 0;
}

 

② 传址调用:利用指针交换变量

#include <stdio.h>

int swap(int* a, int* b) // 传入指针
{
    int c = *a;  
    
    *a = *b; // 利用指针改变变两个值
    
    *b = c; // 利用指针改变变两个值
}

int main()
{
    int aa = 1;
    int bb = 2;
    
    printf("aa = %d, bb = %d\n", aa, bb);
    
    swap(&aa, &bb);
    
    printf("aa = %d, bb = %d\n", aa, bb);
    
    return 0;
}

3、常量与指针

(1)const与指针

const int* p;        // p可变,p指向的内容不可变
int const* p;        // p可变,p指向的内容不可变
int*const p;         // p不可变,p指向的内容可变
const int* const p;  // p和p指向的内容都不可变

方法:不看数据类型,如果是*p代表指向的地址的数值不能变,如果是p代表地址不能变。

口诀∶左数右指

  • const出现在*号左边时指针指向的数据为常量
  • const出现在*后右边时指针本身为常量
     

(2)实践:const与指针

#include <stdio.h>

int main()
{
    int i = 0;
    
    const int* p1 = &i;
    int const* p2 = &i;
    int* const p3 = &i;
    const int* const p4 = &i;
    
    *p1 = 1;    // compile error
    p1 = NULL;  // ok
    
    *p2 = 2;    // compile error
    p2 = NULL;  // ok
    
    *p3 = 3;    // ok
    p3 = NULL;  // compile error
    
    *p4 = 4;    // compile error
    p4 = NULL;  // compile error
    
    return 0;
}

4、小结

  • 指针是C语言中一种特别的变量
  • 指针所保存的值是内存的地址
  • 可以通过指针修改内存中的任意地址内容
     

二、数组的本质分析

1、数组的概念

  • 数组是相同类型的变量的有序集合

2、数组的大小

(1)数组大小的获取(内存、元素数量)

  • 数组在一片连续的内存空间中存储元素
  • 数组元素的个数可以显示或隐式指定

(2)实践:数组的初始化

#include <stdio.h>

int main()
{
    int a[5] = { 1, 2 };
    int b[] = { 1, 2 };

    printf("a[2] = %d\n", a[2]); // 默认为零
    printf("a[3] = %d\n", a[3]);
    printf("a[4] = %d\n", a[4]);

    printf("sizeof(a) = %d\n", sizeof(a));  // 整个数组的所占内存大小 5 * 4 = 20byte 
    printf("sizeof(b) = %d\n", sizeof(b));  // 整个数组的所占内存大小 2 * 4 =  8byte 
    printf("count for a: %d\n", sizeof(a) / sizeof(int));// 数组所包含的元素数量 20 / 4 =  5 
    printf("count for b: %d\n", sizeof(b) / sizeof(*b)); // 数组所包含的元素数量  8 / 4 =  2 

    return 0;
}

3、数组地址与数组名

(1)数组地址与数组名

  • 数组名代表数组首元素的地址
  • 数组的地址需要用取地址符&才能得到
  • 数组首元素的地址值与数组的地址值相同
  • 数组首元素的地址与数组的地址是两个不同的概念
     

(2)实践:数组名和数组地址

#include <stdio.h>

int main()
{
    int a[5] = { 0 }; // 将所有元素赋值为 0

    printf("a     = %p\n", a);       // 数组名 -- 数组首元素的地址
    printf("&a    = %p\n", &a);      // 取数组的地址
    printf("&a[0] = %p\n", &a[0]);   // 数组首元素的地址

    return 0;
}

4、数组名的盲点

(1)数组名的易错点

  • 数组名可以看做一个指针常量(无法改变 )
  • 数组名“指向”的是内存中数组首元素的起始位置
  • 数组名不包含数组的长度信息
  • 在表达式中数组名只能作为右值使用(因为无法改变 )
  • 只有在下列场合中数组名不能看做指针常量
    • 数组名作为sizeof操作符的参数   取的是数组所占得内存
    • 数组名作为&运算符的参数           取的是数组的地址

(2)实践:数组和指针并不相同

#include <stdio.h>

int main()
{
    int a[5] = { 0 };
    int b[2];
    int* p = NULL;

    p = a;  // 将数组的首元素地址 赋值给 p

    printf("a = %p\n", a);  // 打印数组的首元素地址
    printf("p = %p\n", p);  // 打印数组的首元素地址
    printf("&p = %p\n", &p);// 打印指针的地址
    
    printf("sizeof(a) = %d\n", sizeof(a)); //打印数组所占内存  20byte
    printf("sizeof(p) = %d\n", sizeof(*p));//打印数组首元素所占内存   4byte
    printf("sizeof(p) = %d\n", sizeof(*(p + 1)));//打印数第二个元素所占内存   4byte 
       
    printf("sizeof(p) = %d\n", sizeof(p)); //打印指针所占内存   4byte

    
    printf("\n");

    p = b;   // 将数组的首元素地址 赋值给 p

    printf("b = %p\n", b);   // 打印数组的首元素地址
    printf("p = %p\n", p);   // 打印数组的首元素地址
    printf("&p = %p\n", &p); // 打印指针的地址
    printf("sizeof(b) = %d\n", sizeof(b)); // 打印数组所占内存 8byte
    printf("sizeof(p) = %d\n", sizeof(p)); // 打印指针所占内存 4byte

    // b = a; 数组间不可直接赋值

    return 0;
}

5、小结

  • 数组是一片连续的内存空间
  • 数组的地址和数组首元素的地址意义不同
  • 数组名在大多数情况下被当成指针常量处理
  • 数组名其实并不是指针,不能将其等同于指针
     

三、指针和数组分析(上)

1、数组的本质

(1)什么是数组?

  • 数组是一段连续的内存空间
  • 数组的空间大小为sizeof(array_type)* array_size  数组元素的类型大小 x 元素的数量
  • 数组名可看做指向数组第一个元素的常量指针

(2)实践:a + 1的结果是什么?

#include <stdio.h>

int main()
{
    int a[5] = {0};
    int* p = NULL;
    
    printf("a = 0x%X\n", (unsigned int)(a));         // 数组名地址
    printf("a + 1 = 0x%X\n", (unsigned int)(a + 1)); // 数组名地址 + n * 数据类型大小
    
    printf("p = 0x%X\n", (unsigned int)(p));         // 数组名地址
    printf("p + 1 = 0x%X\n", (unsigned int)(p + 1)); // 数组名地址 + n * 数据类型大小
    
    return 0;
}

 

2、指针的运算

  • 指针是一种特殊的变量,与整数的运算规则为

          结论∶
          当指针p指向一个同类型的数组的元素时:p+1将指向当前元素的下一个元素;p-1将指向当前元素的上一个元素。

  • 指针之间只支持减法运算
  • 参与减法运算的指针类型必须相同

      

         注意∶

                1、  只有当两个指针指向同一个数组中的元素时,指针相减才有意义,其意义为指针所指元素的下标差

                2、 当两个指针指向的元素不在同一个数组中时,结果未定义

3、指针的比较

(1)指针的比较

  • 指针也可以进行关系运算(<,<=,>,>=)
  • 指针关系运算的前提是同时指向同一个数组中的元素
  • 任意两个指针之间的比较运算( ==,!= )无限制
  • 参与比较运算的指针类型必须相同

(2)实践:指针运算初探

  • 指针关系运算的前提是同时指向同一个数组中的元素
  • 指针运算没有乘除
#include <stdio.h>

int main()
{
    char s1[] = { 'H', 'e', 'l', 'l', 'o' };
    int i = 0;
    char s2[] = { 'W', 'o', 'r', 'l', 'd' };
    
    char* p0 = s1;      // 数组s1首元素地址赋值给p0
    char* p1 = &s1[3];  // 数组s1第四个元素地址赋值给p1
    char* p2 = s2;      // 数组s2首元素地址赋值给p2
    int* p = &i;        // 变量i的地址赋值给p

    printf("%d\n", p0 - p1);     // -3
    // printf("%d\n", p0 + p2);  // ERROR 不指向同一个数组没意义
    // printf("%d\n", p0 - p2);  // ERROR 不指向同一个数组没意义
    // printf("%d\n", p0 - p);   // ERROR 不指向同一个数组没意义
    // printf("%d\n", p0 * p2);  // ERROR 
    // printf("%d\n", p0 / p2);  // ERROR

    return 0;
}

(3)实践:指针运算的应用

#include <stdio.h>

#define DIM(a) (sizeof(a) / sizeof(*a)) // 计算数组有多少元素

int main()
{
    char s[] = { 'H', 'e', 'l', 'l', 'o' };
    
    char* pBegin = s;        // 数组的首元素地址
    char* pEnd = s + DIM(s); // 数组的最后一个元素的地址+sizeof(*s)
    
    char* p = NULL;

    printf("pBegin = %p\n", pBegin); // 数组的首元素地址
    printf("pEnd = %p\n", pEnd);     // 数组的最后一个元素的地址+sizeof(*s)

    printf("Size: %d\n", pEnd - pBegin);// 5 - 0 = 5

    for (p = pBegin; p < pEnd; p++)     // 打印每个字符
    {
        printf
  • 2
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值