深入 char

深入 char * ,char ** ,char a[ ] ,char *a[] 内核
分类: c语言 2013-02-23 15:34 15176人阅读 评论(8) 收藏 举报
Charcharcharchar aC语言c语言
C语言中由于指针的灵活性,导致指针能代替数组使用,或者混合使用,这些导致了许多指针和数组的迷惑,因此,刻意再次深入探究了指针和数组这玩意儿,其他类型的数组比较简单,容易混淆的是字符数组和字符指针这两个。。。下面就开始剖析一下这两位的恩怨情仇。。。

 1 数组的本质

   数组是多个元素的集合,在内存中分布在地址相连的单元中,所以可以通过其下标访问不同单元的元素。。

 2 指针。

   指针也是一种变量,只不过它的内存单元中保存的是一个标识其他位置的地址。。由于地址也是整数,在32位平台下,指针默认为32位。。

 3 指针的指向?

   指向的直接意思就是指针变量所保存的其他的地址单元中所存放的数据类型。

   int * p ;//p 变量保存的地址所在内存单元中的数据类型为整型

float *q;// ........................................浮点型

不论指向的数据类型为那种,指针变量其本身永远为整型,因为它保存的地址。

4 字符数组。。。

字面意思是数组,数组中的元素是字符。。确实,这就是它的本质意义。

char str[10];

定义了一个有十个元素的数组,元素类型为字符。

C语言中定义一个变量时可以初始化。

char str[10] = {"hello world"};

当编译器遇到这句时,会把str数组中从第一个元素把hello world\0 逐个填入。。

由于C语言中没有真正的字符串类型,可以通过字符数组表示字符串,因为它的元素地址是连续的,这就足够了。

C语言中规定数组代表数组所在内存位置的首地址,也是 str[0]的地址,即str = &str[0];

而printf("%s",str); 为什么用首地址就可以输出字符串。。

因为还有一个关键,在C语言中字符串常量的本质表示其实是一个地址,这是许多初学者比较难理解的问题。。。

举例:

char *s ;

s = "China";

为什么可以把一个字符串赋给一个指针变量。。

这不是类型不一致吗???

这就是上面提到的关键 。。

C语言中编译器会给字符串常量分配地址,如果 "China", 存储在内存中的 0x3000 0x3001 0x3002 0x3003 0x3004 0x3005 .

s = "China" ,意识是什么,对了,地址。

其实真正的意义是 s ="China" = 0x3000;

看清楚了吧 ,你把China 看作是字符串,但是编译器把它看作是地址 0x3000,即字符串常量的本质表现是代表它的第一个字符的地址。。。。。。。。。。

s = 0x3000

这样写似乎更符合直观的意思。。。

搞清楚这个问题。。

那么 %s ,它的原理其实也是通过字符串首地址输出字符串,printf("%s ", s); 传给它的其实是s所保存的字符串的地址。。。

比如

#include <stdio.h>

int main()
{

char *s;
s = "hello";
printf("%p\n",s);
return 0;
}

可以看到 s = 0x00422020 ,这也是"China"的首地址

所以,printf("%s",0x00422020);也是等效的。。

字符数组:

char str[10] = "hello";

前面已经说了,str = &str[0] , 也等于 "hello"的首地址。。

所以printf("%s",str); 本质也是 printf("%s", 地址");

C语言中操作字符串是通过它在内存中的存储单元的首地址进行的,这是字符串的终极本质。。。

5 char * 与 char a[ ];

char *s;

char a[ ] ;

前面说到 a代表字符串的首地址,而s 这个指针也保存字符串的地址(其实首地址),即第一个字符的地址,这个地址单元中的数据是一个字符,

这也与 s 所指向的 char 一致。

因此可以 s = a;

但是不能 a = s;

C语言中数组名可以复制给指针表示地址, 但是却不能赋给给数组名,它是一个常量类型,所以不能修改。。

当然也可以这样:
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[ ] 的本质属性是不一样的。。

6 char ** 与char * a[ ] ;

先看 char *a [ ] ;

由于[ ] 的优先级高于* 所以a先和 [ ]结合,他还是一个数组,数组中的元素才是char * ,前面讲到char * 是一个变量,保存的地址。。

所以 char *a[ ] = {"China","French","America","German"};

同过这句可以看到, 数组中的元素是字符串,那么sizeof(a) 是多少呢,有人会想到是五个单词的占内存中的全部字节数 6+7+8+7 = 28;

但是其实sizeof(a) = 16;

为什么,前面已经说到, 字符串常量的本质是地址,a 数组中的元素为char * 指针,指针变量占四个字节,那么四个元素就是16个字节了

看一下实例:

#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;
   }

    

可以看到数组中的四个元素保存了四个内存地址,这四个地址中就代表了四个字符串的首地址,而不是字符串本身。。。

因此sizeof(a)当然是16了。。

注意这四个地址是不连续的,它是编译器为"China","French","America","German" 分配的内存空间的地址, 所以,四个地址没有关联。

#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]); //数组元素中保存的地址
   printf("%p %p %p %p\n",&a[0],&a[1],&a[2],&a[3]);//数组元素单元本身的地址

   return 0;
   }

可以看到 0012FF38 0012FF3C 0012FF40 0012FF44,这四个是元素单元所在的地址,每个地址相差四个字节,这是由于每个元素是一个指针变量占四个字节。。。

char **s;

char **为二级指针, s保存一级指针 char *的地址,关于二级指针就在这里不详细讨论了 ,简单的说一下二级指针的易错点。

举例:

char *a [ ] = {"China","French","America","German"};

char **s = a;

为什么能把 a赋给s,因为数组名a代表数组元素内存单元的首地址,即 a = &a[0] = 0012FF38;

而 0x12FF38即 a[0]中保存的又是 00422FB8 ,这个地址, 00422FB8为字符串"China"的首地址。

即 *s = 00422FB8 = "China";

这样便可以通过s 操作 a 中的数据

printf("%s",*s);

printf("%s",a[0]);

printf("%s",*a);

都是一样的。。。

但还是要注意,不能a = s,前面已经说到,a 是一个常量。。

再看一个易错的点:

char **s = "hello world";

这样是错误的,

因为 s 的类型是 char ** 而 "hello world "的类型是 char *

虽然都是地址, 但是指向的类型不一样,因此,不能这样用。,从其本质来分析,"hello world",代表一个地址,比如0x003001,这个地址中的内容是 ‘h‘

,为 char 型,而 s 也保存一个地址 ,这个地址中的内容(*s) 是char * ,是一个指针类型, 所以两者类型是不一样的。 。。

  如果是这样呢?
  char **s;

*s = "hello world";

貌似是合理的,编译也没有问题,但是 printf("%s",*s),就会崩溃

why??

咱来慢慢推敲一下。。

printf("%s",*s); 时,首先得有s 保存的地址,再在这个地址中找到 char * 的地址,即*s;

举例:

s = 0x1000;

在0x1000所在的内存单元中保存了"hello world"的地址 0x003001 , *s = 0x003001;

这样printf("%s",*s);

这样会先找到 0x1000,然后找到0x003001;

如果直接 char **s;

*s = "hello world";

s 变量中保存的是一个无效随机不可用的地址, 谁也不知道它指向哪里。。。。,*s 操作会崩溃。。

所以用 char **s 时,要给它分配一个内存地址。

char **s ;

s = (char **) malloc(sizeof(char**));

*s = "hello world";

这样 s 给分配了了一个可用的地址,比如 s = 0x412f;

然后在 0x412f所在的内存中的位置,保存 "hello world"的值。。

再如:

#include <stdio.h>

void buf( char **s)

{

*s = "message";

}

int main()

{

char *s ;

buf(&s);

printf("%s\n",s);

}

二级指针的简单用法。。。。,说白了,二级指针保存的是一级指针的地址,它的类型是指针变量,而一级指针保存的是指向数据所在的内存单元的地址,虽然都是地址,但是类型是不一样的。。。

时间: 2024-08-07 11:13:10

深入 char的相关文章

C++ String 及其与char[]的比较

在学习C++之前  一般都是学过了C语言了 在C语言中   我们对字符串进行保存操作  使用的是char[] 但是在C++中    string比char[]的使用更为频繁  常见    下面稍微讲一下我对于string的认知 1.与其他的标准库类型一样   用户程序需要使用String类型对象  就必须包含相关的头文件   (为了编写方便   需要提供合适的using声明) #include <string> using std::string; 2.string对象的定义与初始化 stri

string8 string16 char*

jstring ifaceStr, jstring opPackageNameStr String16(opPackageName.c_str()),client, String8(iface.c_str()) 2.  String16 name16 = String16("HellOThEWrolD"); String8 name8 = String8(name16); 3.  string8 -> char* String8& initParam  initParam

C/C++中各种类型int、long、double、char表示范围(最大最小值)(转)

1 #include<iostream> 2 #include<string> 3 #include <limits> 4 using namespace std; 5 6 int main() 7 { 8 cout << "type: \t\t" << "************size**************"<< endl; 9 cout << "bool: \t

SQL Server再学习(1)——varchar、nvarchar、char的区别

SQL Server不能丢,复习一下: varchar(n) 长度为 n 个字节的可变长度且非 Unicode 的字符数据.n 必须是一个介于 1 和 8,000 之间的数值.存储大小为输入数据的字节的实际长度,而不是 n 个字节. nvarchar(n) 包含 n 个字符的可变长度 Unicode 字符数据.n 的值必须介于 1 与 4,000 之间.字节的存储大小是所输入字符个数的两倍. 两字段分别有字段值:我和coffee 那么varchar字段占2×2+6=10个字节的存储空间,而nva

char *与char []深度刨析

编译不能通过 #include "iostream" using namespace std; int _tmain(int argc, _TCHAR* argv[]) { char *p= "hello" ; //不是把一个字符串赋给了一个字符型的指针,而是把一个字符型的指针指向了字符串的首地址. strcpy(p,"hel"); cout << p << endl; return 0; }//编译通不过 解析:char

C++ String 转 char*

最近一直用惯了C#,陡然间改回C++都有些不习惯了!吐槽一下,C#太方便了!!! 言归正传,C++里有些时候会用到String转char*这个功能,于是进来搜到了一些方法: 如果你申请了这样一个字符串:string str = "This is a test!",那么: 1.str.data()可以返回该字符串对应的char *,如char *p = str.data(); 2.str.c_str()也可以返回该字符串对应的char *,如char *p = str.data(); 3

getDat(char *val)获得某一天是这一年中的第几天

获得某一天是这一年中的第几天如:./g 20117/2/132 #include <time.h>#include <string.h>#include <stdio.h> const char SPLIT1[2]="-";const char SPLIT2[2]="/";const char SPLIT3[2]=" "; int getDay(char * val); int main(int argc,ch

数据库中char与varchar类型的区别

在建立数据库表结构的时候,为了给一个String类型的数据定义一个数据库的数据库类型,一般参考的都是char或者varchar,总结一下它们两者的区别,明确一下两者的区别. 1.char的长度是不可变的,而varchar的长度是可变的.也就是说,定义一个char[10]和varchar[10],如果存进去的是'abcd',那么char所占的长度依然为10,除了字符'abcd'外,后面跟六个空格,而varchar就立马把长度变为4了;取数据的时候,char类型的要用trim()去掉多余的空格,而v

char类型变量二进制形式输出

C语言char类型(1字节)以二进制形式输出: #include<stdio.h> void chrtobit(char chr)/* chr是要以二进制形式输出的char值*/{ char tchr=chr; int i; for(i=7;i>=0;i--) { char tmpc=tchr; tmpc=tchr&(1<<i); printf("%d",tmpc>>i); } printf("\n");}

String,CString,TCHAR,char之间区别和联系

char是类型TCHAR也是!不过他可以通过是否定义了UNICODE宏来判断到底是char还是w_char; TCHAR是一种字符串类型,它让你在以MBCS和UNNICODE来build程序时可以使用同样的代码,不需要使用繁琐的宏定义来包含你的代码,而char代表ASCII的字符 #ifdef UNICODE   typedef wchar_t TCHAR;   #else   typedef char TCHAR;   #endif 所以用MBCS来build时,TCHAR是char,使用UN