关于指针、数组、字符串的恩怨,这里有你想知道的一切
关于指针、数组、字符串的恩怨,这里有你想知道的一切目录
[*]关于指针、数组、字符串的恩怨,这里有你想知道的一切
[*]内存组成
[*]堆区
[*]栈区
[*]静态存储区
[*]代码区
[*]字符串定义 - 一维
[*]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]