一、基础知识
1.数组的本质
数组是多个元素的集合,在内存中分布在地址相连的单元中,所以可以通过其下标访问不同单元的元素。
2.指针
指针也是一种变量,只不过它的内存单元中保存的是一个标识其他位置的地址。由于地址也是整数,在32位平台下,指针默认为32位。
3.指针的指向
指向的直接意思就是指针变量所保存的其他的地址单元中所存放的数据类型。
int * p ;//p 变量保存的地址所在内存单元中的数据类型为整型
float *q;//q 变量保存的地址所在内存单元中的数据类型为浮点型
不论指向的数据类型为那种,指针变量其本身永远为整型,因为它保存的地址。
4.字符数组
C语言中定义一个变量时可以初始化。
char str[10] = {"hello world"};
当编译器遇到这句时,会把str数组中从第一个元素把hello world\0 逐个填入。。
由于C语言中没有真正的字符串类型,可以通过字符数组表示字符串,因为它的元素地址是连续的,这就足够了。
C语言中规定数组代表数组所在内存位置的首地址,也是 str[0]的地址,即str = &str[0];
而printf("%s",str); 为什么用首地址就可以输出字符串?
举例:
char *s ; s = "China";
为什么可以把一个字符串赋给一个指针变量。这不是类型不一致吗???这就是上面提到的关键 。
C语言中编译器会给字符串常量分配地址,如果 "China", 存储在内存中的 0x3000 0x3001 0x3002 0x3003 0x3004 0x3005 .
s = "China" ,意识是什么,对了,地址。其实真正的意义是 s ="China" = 0x3000;
看清楚了吧 ,你把China 看作是字符串,但是编译器把它看作是地址 0x3000,即字符串常量的本质表现是代表它的第一个字符的地址。
那么 %s ,它的原理其实也是通过字符串首地址输出字符串,printf("%s ", s); 传给它的其实是s所保存的字符串的地址。
#include <stdio.h> int main() { const char* s; s = "hello"; printf("%p\n", s);// %p是打印地址(指针地址)的 return 0; }
输出:0x00422020
可以看到 s = 0x00422020 ,这也是"China"的首地址。所以,printf("%s",0x00422020);也是等效的。
所以printf("%s",str); 本质也是 printf("%s", 地址");
C语言中操作字符串是通过它在内存中的存储单元的首地址进行的,这是字符串的终极本质。
二、char * ,char ** ,char a[ ] ,char *a[]区别
1.char * 与 char a[ ]
char*
char*是一个指针,例如:char* s1 = "abc",s1是一个指针,指向一个字符串常量“abc",s1这个指针可以被修改,但是字符串常量不可被修改,如果修改s1的内容,如s1="123",修改的并不是”abc”的内容,而是将s1的指针指向了新的地址。可以通过对char*类型进行解引用获得位于第一个的元素,如*s1=‘a',并且可以用过+对指针的位置进行移位。可以通过strlen函数获取char*类型的长度。
char[]
char s2[10]="abc",s2是数组指针,指向数组第一个元素所在的地址,分配后就不可修改了,但是指针指向的内容是可以修改的,如s2[0]='1',就是把'a'改为'1'。且数组大小是初始化的时候就确定的,不可修改。
char *s; char a[ ] ;
a代表字符串的首地址,而s 这个指针也保存字符串的地址(其实首地址),即第一个字符的地址,这个地址单元中的数据是一个字符,
可以 s = a;但不能 a = s;
这也与 s 所指向的 char 一致。
当然也可以这样:
char a [ ] = "hello"; char *s =a; for(int i= 0; i < strlen(a) ; i++) printf("%c", s[i]); // printf("%c",*s++)
字符指针可以用间接操作符∗ 取其内容,也可以用数组的下标形式 [ ]。
数组名也可以用 *操作,因为它本身表示一个地址 。比如 printf("%c",*a); 将会打印出 ‘h’
char * 与 char a[ ] 的本质区别:
当定义 char a[10] 时,编译器会给数组分配十个单元,每个单元的数据类型为字符。
定义 char *s 时, 这是个指针变量,只占四个字节,32位,用来保存一个地址。
sizeof(a) = 10 ; sizeof(s) = 4;//编译器分配4个字节32位的空间,这个空间中将要保存地址
printf("%p",s);//表示 s 的单元中所保存的地址 printf("%p",&s);//表示变量本身所在内存单元地址
一句话来概括,就是char* s 只是一个保存字符串首地址的指针变量, char a[ ] 是许多连续的内存单元,单元中的元素为char ,之所以用 char* 能达到char a [ ] 的效果,还是字符串的本质,地址,即给你一个字符串地址,便可以随心所欲的操所他。但是,char* 和 char a[ ] 的本质属性是不一样的。
2.char ** 与char * a[ ] ;
char *a [ ]
由于[ ] 的优先级高于* 所以a先和 [ ]结合,他还是一个数组,数组中的元素才是char * ,前面讲到char * 是一个变量,保存的地址。
char *a[ ] = {"China","French","America","German"}; sizeof(a) = 16//32;
32位系统sizeof(char*)=4
64位系统sizoef(char*)=8
字符串常量的本质是地址,a 数组中的元素为char * 指针,64位系统指针变量占8个字节,那么四个元素就是32个字节了
#include <stdio.h> int main() { char *a [ ] = {"China","French","America","German"}; printf("%p %p %p %p\n",a[0],a[1],a[2],a[3]); return 0; }
可以看到数组中的四个元素保存了四个内存地址,这四个地址中就代表了四个字符串的首地址,而不是字符串本身。四个地址不是连续的。
#include <stdio.h> #include<iostream> int main() { char *a [ ] = {"China","French","America","German"}; printf("%p %p %p %p\n",a[0],a[1],a[2],a[3]); //数组元素中保存的地址 printf("%p %p %p %p\n",&a[0],&a[1],&a[2],&a[3]);//数组元素单元本身的地址 std::cout<<sizeof(a); return 0; }
0x7fff2258ff70 0x7fff2258ff78 0x7fff2258ff80 0x7fff2258ff88,这四个是元素单元所在的地址,每个地址相差8个字节,这是由于64位系统每个元素是一个指针变量占8个字节
char s**
二级指针保存的是一级指针的地址,它的类型是指针变量,而一级指针保存的是指向数据所在的内存单元的地址,虽然都是地址,但是类型是不一样的
在 char** s 中,s 是一个指针,这个指针(s)指向一块内存地址,该内存地址中存储的是 char* 类型的数据。指针的加减运算在这里的体现为:s + 1 表示地址加8字节(在32位系统中,地址加4字节)。
char* 也是一个指针,用 *s 表示,这个指针(*s)指向一块内存地址,该内存地址中存储的是 char 类型的数据。指针的加减运算在这里的体现为:(*s) + 1 表示地址加1字节。
char *a [ ] = {"China","French","America","German"}; char **s = a;
为什么能把 a赋给s,因为数组名a代表数组元素内存单元的首地址,即 a = &a[0]
但还是要注意,不能a = s,前面已经说到,a 是一个常量。
再看一个易错的点:
(1)
char **s = "hello world"; 这样是错误的,(str = &str[0] , 也等于 "hello"的首地址。)
因为 s 的类型是 char ** 而 "hello world "的类型是 char *
(2)
char **s;
*s = "hello world";
貌似是合理的,编译也没有问题,但是 printf("%s",*s),就会崩溃(str = &str[0] , 也等于 "hello"的首地址。)
printf("%s",*s); 时,首先得有s 保存的地址,再在这个地址中找到 char * 的地址,即*s;
举例:
s = 0x1000;
在0x1000所在的内存单元中保存了"hello world"的地址 0x003001 , *s = 0x003001;(str = &str[0] , 也等于 "hello"的首地址。)
这样printf("%s",*s); 会先找到 0x1000,然后找到0x003001;
如果直接 char **s;
*s = "hello world";
s 变量中保存的是一个无效随机不可用的地址(就是s不为0x1000,是一个随机的地址), 谁也不知道它指向哪里,*s 操作会崩溃。。
所以用 char **s 时,要给它分配一个内存地址。
char **s ;
s = (char **) malloc(sizeof(char**));// 这样 s 给分配了了一个可用的地址,比如 s = 0x412f;
*s = "hello world";//然后在 0x412f所在的内存中的位置,保存 "hello world"的值。
标签:char,地址,printf,字符串,hello,指针 From: https://www.cnblogs.com/imreW/p/17061780.html