种地 发表于 2023-4-4 14:12:14

关于指针、数组、字符串的恩怨,这里有你想知道的一切

关于指针、数组、字符串的恩怨,这里有你想知道的一切


目录

[*]关于指针、数组、字符串的恩怨,这里有你想知道的一切

[*]内存组成

[*]堆区
[*]栈区
[*]静态存储区
[*]代码区

[*]字符串定义 - 一维

[*]char s = "Hello"
[*]char *s = "Hello"

[*]字符串定义 - 二维

[*]char s = {"Hello","World"}
[*]char *s = {"Hello", "World"}

[*]对二维数组结构的认识

[*]关于二维数组
[*]二维数组中的指针等价关系
[*]数组结构中对“指针常量”的理解

[*]指针 vs 数组 内存结构一图流
[*]One More Thing

[*]

[*]题面
[*]输入格式
[*]输出格式




内存组成

https://img-blog.csdnimg.cn/20210319215715539.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L01RMDUyMg==,size_16,color_FFFFFF,t_70堆区

堆区 (Heap):由程序员手动申请释放的内存空间。

[*]C中:malloc()和colloc()函数申请,用free()释放
若不用free()释放,容易造成内存泄露(即内存被浪费、耗尽)。


[*]ptr = (castType*) malloc(size);
传入参数为内存的字节数,内存未被初始化。
[*]ptr = (castType*)calloc(n, size);
存入参数为内存块数与每块字节数,内存初始化为0。
[*]free(ptr);
释放申请的内存。

[*]C++中:new申请,delete释放。new和delete都是操作符


[*]int *arr = new int;
[*]delete[] arr;
栈区

栈区 (Stack):由系统管理,存放函数参数与局部变量。函数完成执行,系统自行释放栈区内存。
静态存储区

静态存储区 (Static Storage Area):在编译阶段分配好内存空间并初始化。
其中全局区存放静态变量(static修饰的变量)、全局变量(具有全局作用域的变量);常量区存放常量(又称为字面量)。
常量可分为整数常量(如1000L)、浮点常量(如314158E-5L)、字符常量(如'A'、'\n')和字符串常量(如"Hello")。
const关键字修饰的的变量无法修改,但存放的位置取决于变量本身是全局变量还是局部变量。当修饰的变量是全局变量,则放在全局区,否则依然在栈区分配。
static关键字修饰的变量存在全局区的静态变量区。
常变量与宏定义的概念不同。
常变量存储在静态存储区,初始化后无法修改。
宏定义在预处理阶段就被替换。不存在与任何内存区域。
代码区

代码区 (Code Segment):存放程序体的二进制代码。
/*示例代码*/

int a = 0;          //静态全局变量区
char *p1;         //编译器默认初始化为NULL,存在静态全局变量区

void main()
{
    int b;                //栈
    char s[] = "abc";   //栈
    char *p1 = "123";   //"123"在字符串常量区,p1在栈区
   
    p2 = (char *)malloc(10); //堆区
    strcpy(p2, "123");       //"123"放在字符串常量区
   
    const int d = 0;      //栈
    static int c = 0;   //c在静态变量区,0为文字常量,在代码区
    static const int d;   //静态常量区
   
}字符串定义 - 一维

char s = "Hello"

内存:静态存储区上的字面量"Hello"被复制到栈区,数组在栈区上的存储方式为'H''e''l''l''o''\0',可以通过s修改。但这不会影响到静态存储区上的"Hello"。
定义与使用:
#include <stdio.h>

void f(char s) {      //等价于char *s
    printf("%s\n", s);
}

int main() {
    char s = "LeeHero";
    s = 'Z';
    printf("%s\n", s);   //输出:LeeZero
    printf("%s\n", s+1); //输出:eeZero
    printf("%c\n", s);//输出:Z

    f(s); //数组名作为函数参数传递时,会退化成指向数组首元素的指针 !IMPORTANT
    return 0;
} 格式控制符 %s 跟随一个地址,并当做是字符串第一个元素对应的地址.
从该首地址开始解析,直到 '\0' 结束。
在这里指的是 s = 'H' 的地址。
char *s = "Hello"

// 等价于const char *s = "Hello"
内存:s是指向字面量"Hello"的指针,字面量在静态内存区,因此该字符串不可被修改。
定义与使用:
#include <stdio.h>

void f(char s) {       //等价于char *s
    printf("%s\n", s);
}

int main() {
    char *s = "LeeHero";
    //s = 'Z';          //无法执行
    printf("%s\n", s);   //输出:LeeHero
    printf("%s\n", s+1);   //输出:eeHero
    printf("%c\n", s);//输出:H

    f(s);
   
    return 0;
} 字符串定义 - 二维

char s = {"Hello","World"}

内存:静态存储区上的字面量"Hello","World"被拷贝在栈区,与一维定义方式同理,可以通过语法糖s修改字符。
定义与使用:
#include <stdio.h>

void f(char (*s)) {      //形参s是个指针,指向有10个元素的字符数组
                               //把(*s) 改成 s[] ,其他不变,最后效果相同
    printf("%s\n", s);      //输出:Zero
    s = 'H';             //通过语法糖s修改字符
    printf("%s\n", s);      //输出:Hero
    printf("%c\n", s);   //输出:e
}

int main() {
    char s = {"Lee","Hero"};
    //s = "Hey";            //无法执行,这种赋值方式仅在初始化时可用
    s = 'Z';
    printf("%s\n", s);         //输出:Lee
    printf("%s\n", *s+1);      //输出:ee
    printf("%s\n", s+1);    //输出:ee
   
    printf("%c\n", *(s+1)); //输出:e
    printf("%c\n", s);   //输出:e
   
    printf("%s\n", s+1);       //输出:Zero
    printf("%s\n", s);      //输出:Zero
   
    f(s);
   
    printf("%s\n", s);      //输出:Hero 这意味着函数内部的修改不是局部生效的
    return 0;
} 对于打印结果的一些解释:
· 对二维数组进行操作与输出

[*]s 等价于&s,是指向[存储"Lee"的一维数组]的指针
[*]s+1等价于&s,是指向[存储"Zero"的一维数组]的指针
[*]*s+1等价于(*s)+1,s通过*解析首先得到[一维数组"Lee"]
即指向[一维数组"Lee"的第一个元素'L'的地址]的指针s;
对该指针+1,相当于s+1,使得指针指向[一维数组"Lee"第二个元素'e'的地址]
格式控制符%s将该元素看成字符串的首地址,因而打印出"ee"
· 二维数组传参
二维数组主要有两种传参方式(以下两种是函数声明的方式。声明函数后,都是使实参为数组名来调用函数:f(s);)

[*]void f(char (*s)) {} —— 一维数组指针作形参
二维数组名实际上就是指向一维数组的指针。因此这里形参s是个指向行元素的指针,与二维数组名匹配。
[*]void f(char s[]) {} —— 二维数组指针作形参
对于这种方法,仅二维数组的数组列数可以省略,不可省略行数。f(char s[][])是错误的。
也就是说,1.和2.方式中都需要正确指定行数。
[*]f(char **s),f(char *s[])的方式声明函数虽然能编译输出,但编译器可能会出现以下警告信息:
passing argument 1 of 'f' from incompatible pointer type
expected 'char **' but argument is of type 'char (*)'P.S. 当然,如果一定要用二维指针作实参f(char **s),在传参的时候可以将s强制转化:f((char **)s),函数内部操作元素可以通过*((int *)a+i*10+j)的方式……但何必呢。
如果一定要试试,这里也有个例子:
#include <stdio.h>
            
void f(char **s) {                     //形参s是个二维指针
    printf("%c\n", *((char *)s));      //输出:L
    printf("%s\n", ((char *)s));       //输出:Lee
    printf("%c\n", *((char *)s+10));   //输出:H
    printf("%s\n", ((char *)s+10));    //输出:Hero
}
            
int main() {
    char s = {"Lee","Hero"};
    f((char **)s);                     //“我一定要把s看做二维指针去传参!”
    return 0;
}
char *s = {"Hello", "World"}

内存:类比char *s = "Hello",这里s是一个指针数组,s、s是两个指针,分别指向字面量"Hello"、"World"。指向的内容可以访问,无法修改。
定义与使用:
#include <stdio.h>

void f(char **s) {
    printf("%s\n", s);      //输出:Lee
    printf("%c\n", s);   //输出:L
}

int main() {
    char *s = {"Lee","Hero"};
    printf("%s\n", s);      //输出:Lee(等价于*s)
    printf("%c\n", s);   //输出:L(等价于*s)

    f(s);
    return 0;
} 解释:
数组名作为函数参数传递时,会退化成指向数组首元素的指针。
当把s作为参数传递给f()函数时,实际上是把指针数组的首地址传递给了f()函数。这样,f()函数中的s就是一个二级指针,它指向了指针数组的第一个元素,也就是第一个字符串的地址。
f()函数接受一个二级指针作为参数。由此,f()函数中的s和s与主函数中的s和s含义相同。
#include <stdio.h>

int main() {
   
        /* s与*s的对比 */
   
    char *s = {"Lee","Hero"};
    printf("%d %d\n", sizeof(s), &s);            //输出:80 6487488
    printf("%s\n", s);                           //无输出!
   
    printf("%d %d\n", sizeof(s), &s);      //输出:86487488
    printf("%s\n", s);                        //输出:Lee(等价于*s)
   
    printf("%d %d\n", sizeof(s), &s);//输出:14210692
    printf("%c\n\n", s);                   //输出:L(等价于*s)
   
    char t = {"Lee","Hero"};
    printf("%d %d\n", sizeof(t), &t);            //输出:100 6487376
    printf("%s\n", t);                           //输出:Lee
   
    printf("%d %d\n", sizeof(t), &t);      //输出:106487376
    printf("%s\n", t);                        //输出:Lee(等价于*t)
   
        printf("%d %d\n", sizeof(t), &t);//输出:1   6487376
    printf("%c\n", t);                     //输出:L(等价于*t)
   
    /* *s内容无法修改 */
    t = 'Z';         //修改二维数组元素
    printf("%s\n", t);    //输出:Zero
    s = 'Z';         //程序运行到这里崩溃!
    printf("%s\n", s);    //无输出!
   
    return 0;
} 对二维数组结构的认识

关于二维数组

a : 第 \(i\) 行第 \(j\) 列元素
a:一级指针常量,指第 \(i\) 行首元素地址,第 \(i\) 行本质为一维数组,a+j是第 \(i\) 行第 \(j\) 列元素的地址
a:数组指针常量,是二维数组的起始地址,第 \(0\) 行的起始地址。
<img alt="image-20230323214508306" loading="lazy">
二维数组中的指针等价关系

优先级:() \(>\) ++ \(>\) 指针运算符* \(>\) +
二级指针
页: [1]
查看完整版本: 关于指针、数组、字符串的恩怨,这里有你想知道的一切