数组和指针
Int powers[8] = {1,2,4,6,8,16,32,64};初始化仅asic C,
常量数组:const int days[months]={31,28,31,30,31,30,31,31,30,31,30,31};
在函数的内部声明,并且没有使用关键字static,不同的存储类具有不同的属性,因此不能把本章的只是推广到其它的存储类。例如:没有进行初始化,一些存储类的变量和数组会把它们的存储单元设置为0;数组则直接读取上面的内容。
如果不初始化数组,数组元素和未初始化的普通变量一样,其中存储的是无用的数值;但是如果部分初始化数组,未初始化的元素则被设置为0;
如果初始化列表中的项目的个数大于数组大小,编译器会毫不留情地认为这是一个错误。但是用另外一种形式可以避免受到编译器的此类奚落:您可以省略括号中的数字,从而让编译器自动匹配数组的大小,和初始化列表中的项目数目。
Sizeof days / sizeof days[0];
C99可以对单个数组元素赋值,
Int arr[6] = {[5] = 212};其它的都为0;
又如:
Int days[MONTHS] = {31,28,[4] = 31,30,31,[1] = 29};
结果为:31,29,0,0,31,30,31,0,0,0,0,0;
声明数组时在方括号内只能使用整数常量表达式,整数常量表达式是由整数常量组成的表达式,sizeof表达式被认为是一个整数常量。而和c++不一样,一个const值却不是整数常量。并且该表达式的值必须大于0;
在形参的形式中
Fun(Int * char para1)等价于 fun(int para1[]);
Int * p1,*p2,*p3;这说明int *不是一个整体,int只是修饰指针的类型,* 是指针类型(猜测),可能定义一个变量要2部分,int是第一部分,*是第二部分,平常只要第一部分。而且第二部分可以和第一部分分离。(纯粹猜测)
表达式 ar[i] 和*(ar+i)的意义是等价的。但是只有是指针的时候才可以 ar++;
指针的8种操作
1.赋值可以把地址赋给指针。
2.求值 *char;
3.取指针地址 &char
4.将一个整数加给指针 ptr+4
5.增加指针的值
6.从指针中减去一个整数。
7.减小指针的值。
8.求差值可以求出两个指针间的差值。
比较比较两个指针的大小。
变长数组 VLA
2维数组的初始化:
一维数组时一行,2维是一个面,3维是一个立方体。
指针和数组
对指针+1的结果就是对该指针增加一个尺存储单元,对于数组而言,地址会增加到下一个元素的地址。而不是下一个字节。
1.指针的数值就是它所指向的对象的地址,指向float的指针,地址的内部表示方式是由硬件来决定的,很多计算机包含多个字节的数据类型,比如double类型的变量,对象的地址通常指的是其首字节的地址。
2.指针前运用运算符* 就可以得到该指针所指向的对象的数值。
3.对指针加1,等价于对指针的值加上它所指向的对象的字节大小。
在函数原型或函数定义头的场合中,可以用int * ar代替int ar[],无论在任何情况下,形式int * ar都表示ar是指向int的指针。形式int ar[]也可以表示ar是指向int的指针,但只是在声明形式参量时才可以这样使用,使用第二中形式可以提醒读者ar不仅指向一个Int数值,而且它指向的这个int是一个数组中的元素。
由于原型允许省略名称因此下面的声明是等价的:
Int sum (int *ar,int n); Int sum (int *,int); Int sum (in tar[],int n); Int sum (int [], int);
定义函数时,名称是不可以省略的,因此,在定义时下面两种形式是等价的:
Int sum(int *ar,int n) { } Int sum (in tar[],int n) { } Int marbles[size];
Sizeof marbles 是整个数组的大小,
Sizeof ar或者sizeof tar 就是单个数组元素的大小。
也可以传递首指针和尾指针
Int sump(int * start,int * end) { Int total = 0; While(start < end ) { Total += *start; Start++; } }
在c中,两个表达式 ar[i] 和*(ar+i) 的意义是等价的,而且不管ar是一个数组名还是一个指针变量,这两个表达式都可以工作,然而只有当ar是一个指针变量时,才可以使用ar++这样的表达式。
指针操作
指针之间的减法结果等于相对于他们的类型的值,比如
Pi+3 –pi=3,这个3相对于他们的类型int而言就是3个4字节的地址长度。
指针可以直接赋值给以个指针。
计算机并不检查指针是否依然指向某个数组元素,C保证指向数组的指针和指向数组后的第一个地址的指针都是有效的,但是如果指针在进行了增量或减量运算后超出了这个范围,后果将是未知的,另外,可以对指向数组元素的指针进行取值运算,但不能对指向数组后的第一个地址的指针进行取值运算,尽管这个指针式合法的。
Int *pt;
*pt = 5;
这是不可行的,因为pt的值是随机的,所以随机地址赋值为5,可能是重要的位置。
指针不能加指针。
由于参数传递数组的时候是传递地址的,所以可以改变原数组的值,但是如果不想改变要保护原数组的话,可以在函数原型和定义的形式参量声明中使用关键字const;
使用const来创建符号常量(const double PI = 3.14159),数组常量(const mars [3] ={1,2,3}),指针常量int * const p=&a;,以及指向常量的指针const int * p=&a;(const int a=3;)。
1)指针变量 int * p = &a;
2)指针常量 int * const a = &b; 这里的a 是个指针常量它指向变量b的地址,不能再修改它的值,a = &c;是错误的。
3)指向常量的指针它是变量指向的是一个常量
它是一个变量,但它指向的内容是常量。
Const int b = 1,c = 2;
Const int * a;
A = &b;a = &c,由于它本身是变量,所以可以改变它的值,再用它指向常量c.
但是它指向的内存是常量,是不能被修改的。如: *a = 20;是错误的。
还有另外一种写法 int const * a;const的位置,还是在*前面;
4)指向常量的指针常量它是常量,但是它指向的内容是常量
Cons int b = 1, c = 2;
Int const * const a = &b;
看关键字const,const后面的不可修改。
Int * const a =&b;//则a不能被修改。
Int const * a=&b; //const后面是*a,则说明*a不能被修改。
把常量或非常量数据的地址赋给指向常量的指针式合法的。
然而只有非常量的数据才可以赋给普通的指针:
所以要分三个点来看指向的是常量还是非常量,还有就是常量指针还是指针常量。
多维数组与指针
Int zippo[4][2]:
数组名zippo同事也是数组首元素的地址,本例中,zippo的首元素本省又是包含两个int的数组,因此zippo也是包含两个int数组的地址,
1)Zippo是数组首元素的地址,所以zippo的值和&zippo[0]相同,另一方面,zippo[0]本身是包含两个整数的数组,因此zippo[0]的值同其首元素(一个整数)的地址&zippo[0][0]相同。简单的说zippo[0]是一个整数大小对象的地址,zippo是两个整数大小对象的地址,因为整数和两个整数组成的数组开始同一个地址,因此zippo和zipp[0]具有相同的数值。
2)对一个指针加1,会对原来的数值加上一个对应类型大小的数值。在这方面,zippo和zippo[0]是不一样的,zippo所指向的对象的大小事两个int,而zippo【0】所指向的大小事一个int,因此zippo+1和zippo[0]+1的结果不同。
3)对一个指针取值(使用运算符*或者带有索引的[]运算符)得到的是该指针所指向对象的数值,因为zipp[0]是其首元素zippo[0][0]的地址,所以 *(zippo[0])代表存储在zippo[0][0]中的数值,即是一个int数值,同样zippo代表其首元素的地址,但是zippo[0]本身就是int数的地址,即&zippo[0][0],因此*zippo是&zippo[0][0]。对这两个表达式同时应用取值运算符将得到 **zippo等价于*&zippo[0][0],后者简化为一个int数 zippo[0][0].简而言之,zippo是地址的地址,需要两次取值才可以得到通常的数值。地址的地址或指针的指针式双重间接地典型例子。
总结:数组名是它本身元素的首地址,所以一维数组中的数组名就是数组单个元素的第一个元素的首地址,而二维数组就是一维数组的数组,所以它的名称就是一维数组的第一组数组的地址,所以他们相加结果是不一样的。相加就相当于他们元素加1,一维数组的元素是一个元素,而二维数组加1就相当于一维数组的一组数组。三维一样是二维数组的数组。它加1相当于加一个二维数组的量。对于取值来说二维是一维的地址,所以要经过两次取值才能取到数。
Int (* pz)[2] // 首先是一个指针,指向一个包含2个int值得数组。
pz指向一个包含2个int值得数组。加()是因为[]优先级高于*
Int说明数组和指针都int型的。
Int * pz[] //是指2个指向int值的指针构成的数组。
看这个名称与谁先结合,与【】它就是一个数组,与*就是一个指针。
最后一行说明指针的指针不能赋给2维数组名。
Int * p1;
Const int *p2;
Const int ** pp2;
P1=p2; // 非法,把const赋值给非const指针
P2=p1;// 合法 把非const指针赋值给const指针
Pp2= &p1; //非法 为什么呢不也是非const赋值给const么
原来是2层间接赋值的时候不可以。1层间接是可以的。
二维数组的形参声明:
Int fun(Const int (* pt) [4])
或者int fun(const pt [][4]);
Typedef int arr4[4]; Typedef arr4 arr3x4[3];
Int sum2(arr3x4 ar,int rows);等价于 Int sum2(in tar[3][4],int rows); Int sum2(in tar[][4],int rows);
一般声明多维数组的形参时可以省略最左边的数目。
传统c向函数传递多维数组的方法是把数组名传递给相应的类型的指针参量。指针生命需要制定各维的大小(除了最左面的不需要明确指出大小);第一个参量的维数大小通常为第二个参量来传递;
Void display(double ar[][12],int rows)… Display(sales,5);
变长数组提供了另一种方法,两个维大小都可以作为参数被传递给函数。因此函数原型和函数调用就可以如下:;
Void display(int rows,int cols,double ar[rows][cols]); …. …. Display(5,12,sales);
变长数组必须是自动存储类,而且声明时不可以进行初始化。
变说的是创建前维数可以是变量,创建后是不可以变的。
省略名称的话
Int sum2d(int,int,ar[*][*]);
二维数组,
复合文字
对参量传值,可以穿变量和常量,但是数组没有数组常量可供传递,c99新增了复合文字,文字是非符号常量,5是int类型的文字,81.3是double类型的文字,’Y’是char类型的文字,”elephant”是字符串文字。如果有能够表示数组和结构内容的复合文字,那么在编写程序时更为方便。相当于java中的匿名数组等等
(int[2]){10,20}
相当于一个数组,可以传递给参量,或者给指针赋值。
字符串和字符串函数
字符串的定义
字符串常量define “XXXXXX”,
char数组 char[]={‘c’,’d’,’\0’},char[] = “cd”;
char指针 char * s=”just a test!”;
和字符串数组char *s[]={”just a test!!”,”just a test too”}。
1)字符串常量:又成字符串文字,是指位于一对双引号中的任何字符。双引号里的文字加上编译器自动提供的结束标志\0字符,作为一个字符串被存储在内存里。
字符串文字中间没有间隔或者间隔是空格符,ansic会将其串连起来。
字符串常量是静态存储类,静态存是指如果在一个函数中使用的字符串常量,即使是多次调用了这个函数,该字符串在程序的整个运行中只存储一份。整个引号中的内容作为指向该字符串存储位置的指针。与数组名作为数组存储位置的指针类似。
1)字符串数组及其初始化
Const char ml[40] = “Limit yourself to one line’s worth.”;
注意标志结束的空字符,如果没有它,得到的就只是一个字符数组而不是一个字符串。指定数组大小时,一定要确保数组元素数比字符串长度至少多1.未被使用的元素均被自动初始化为0,这里的0是char形式的空字符,而不是数字字符0.
Const char *m3[] = “\nEnough about me what’s your name?”;
这个声明和下列声明的作用几乎相同:
Char m3[] = “\nEnough about me what’s your name?”;
都声明了m3是一个指向给定字符串的指针。都是被引用的字符串本身决定了为字符串预留的存储空间大小。
总之:数组初始化时从静态存储区把一个字符串赋值给数组,而指针初始化只是复制字符串的地址。
如:
Char heart[]=”I love Tillie!”;
Char *head =”I love Millie!”;
主要区别是:heart是个常量,而指针head则是个变量。
可以
Putchar(heart[i]); Putchar(head[i]); *(Heart+i) *(Head+i)
只有指针可以
While(*(head)!=’\0’)
Putchar(*(head++));
数组的元素师变量(除非声明数组时带有关键字const)
建议初始化一个指向字符串文字的指针时使用const修饰符:
Const char * p1=”Klingon”;
用一个字符串文字来初始化一个非const得数组,则不会导致此类问题,因为数组从最初的字符串得到一个拷贝。
这个示例说明字符串常量是在静态存储区存储的。
字符指针只是把这个地址赋值过来,
而数组则把内容复制过来。
由于字符指针指向的是字符串常量,所以不能用字符指针来修改字符串常量。
建议指向字符串的字符指针用const修饰。Const char * p2=”Klingon”;
字符串数组
Const char * mytal[5] = {“Adding numbers swiftly”,”Multiolying accurately”,
”Stashing dta”,”Following instructions to
the letter”,”Understanding the c language”};
所以mytal是一个由5个指向char的指针组成的数组。也就是说,mytal是一个数组,每个元素都是一个char类型值得地址。
也可以用字符的二维数组来声明字符串数组。Char mytal[5][20]
两者的不同之处是数组在内存中连续存放,而指针数组指向的字符串不一定是连续存放的。
指针和字符串
字符指针赋值只是把指针的值也就是字符串的地址赋给了另一个指针。也就是2个指针指向了同一个地址。
字符串输入:如果想把一个字符串读到程序中,必须首先预留存储字符串的空间,然后使用输入函数来获取这个字符串。
Char *name;
Scanf(“%s”,name);
这可能会通过编译器,但是在读入name的时候,name会覆盖程序中的数据和代码,并可能导致程序异常终止。
最简单的方法就是在声明中明确指出数组大小。Char name[8];
获取字符串的函数,
1)scanf(),如果是%s字符串读到(但是不包括下一个空白字符比如空格、制表符、换行符)
它遇到换行符,制表符和空格符就结束,不包括但是也不丢弃。
1)gets(),丢弃末尾的换行符,并在末尾添加\0.
说明丢弃了回车符
返回一个字符指针,与参数数组时内容是一样的。如果无措就返回一个地址,如果有误或者遇到结束符,就返回一个NULL
如果是getchar()就要用 getchar()!=EOF,
1)fgets()函数。
Char name[50];//这时候已经为数组分配了内存空间。
Gets(name)!=NULL;
它返回一个字符指针与传入的字符指针是同一个。
Fgets()函数与gets()函数有三点不一样的地方:
1)它需要第二个参数来说明最大读入字符数。
2)Fgets()读取到换行符,就会把它存到字符串里,而不是像gets()那样丢弃它。
3)需要第三个参数来说明读取哪一个文件,键盘上读取,可以使用stdin。
根据特点:gets()从键盘读取文本可能要更好,因为它更容易被使用,更快,而且更简洁。
Scanf主要用于某种标准形式输入的混合类型数据的读取和转换。
字符串输出函数:
1)Puts显示字符串自动在后面加上换行符。Puts遇到空格符就会停下来(、0).
2)fputs()的第二个参数来说明要写的文件。
3)与puts()不同,fputs()并不为输出自动添加换行符。
注意:gets()丢掉输入里的换行符,但是puts()为输出添加换行符。Fgets()存储输入中的换行符,fputs()也不为输出添加换行符。
Printf()不添加换行符。
Gets() puts(); Fgets() fputs();
自定义字符串输出函数
Int i = 0; While(*string) Putchar(*string++);
或记录字符数。
字符串函数
Strlen()不考虑最后的\0字符的字符串长度。
Strcat 将第二个字符串的一份拷贝添加到第一个字符串的结尾,从而使第一个字符串成为一个新的组合字符串,第二个字符串并没有给带便。
Strncat()添加了一个字数,直到加到字数或者遇到空字符为止,由二者中先符合的那一个来终止添加过程。基本上是第一的长度减去字符串的长度再减一就是可以添加的最大值。
Strcmp();比较二个字符串,比较的是二个字符串的内容,小于为负数等于为零,大于为正数。
字符数组可以这样初始化,但是不能这样赋值:char tmp[40] = ”just a test”;
Char tmp[40];
Tmp = ”just a test”;
赋值可以使用strcpy函数来,也可以不在第一位。
如果第一个不是数组而是指针,那么指针指向的位置可以是任何地方。
Strcpy 两个属性:它是char *类型,它返回的是第一个参数的值,即一个字符的地址;其次第一个参数不需要指向数组的开始,这样就可以只复制数组的一部分。
第一箭头说明复制后把第二个的结束符也带过去了,第二个说明函数返回的是第一个参数的地址。
箭头后面部存在说明这个函数不能用来拷贝字符数组,只能用来拷贝字符串。
上面说明如果复制的位置位于目标字符长度之后,它不会去掉源字符串的结束符,并且也不会考虑复制后是不是超出了大小范围。充分说明C语言设计时要求速度和充分相信程序员。
Strncpy函数:要比strcpy多注意由于源字符串被截断复制,可能不带有结束符。所以复制的时候大小要小一个。
字符的比较用关系运算符。
Strncmp(list,”astro”,5);用“astro”与list的前5个字符相比较。
小结:
Char *strcpy(char * s1,const char *s2):
该函数把s2指向字符串复制到s1指向的位置,返回值是s1.
Char *strncpy(char *s1,const char * s2,size_t n);
该函数把s2指向的字符复制到s1指向的位置,复制字符数不能超过n个,返回值是s1,空字符后的字符不被复制。如果源字符串的字符数少于n个,在目标字符串中就以空字符填充,如果源字符串的字符串的字符数大于或等于n个。空字符就不被复制,返回值是s1.
说明只看大小不管是不是字符串的结束。
Sprint()函数是向字符串中打印与printf函数一样。
Char * strcat(char * s1,const char * s2);
S2指向的字符串被复制到S1指向字符串的结尾,复制过来的s2所指字符串的第一个字符覆盖了s1所指字符串结尾的空字符。返回值是s1.程序员自己处理数组的大小。S1如果是数组必须足够大。如果是字符串赋值的指针也是不可以的。
Char * strncat(char * s1,const char * s2,size_t n);
S2字符中的空字符及其后的任何字符都不会被复制,并且追加一个空字符到所得结果后面,返回值是s1.不要求S1的大小大于或等于复制后大小。
要牢记char * ptr;的声明并没有给指针分配空间,所以不能用于strcate的链接。
Int strcmp(const char * s1,const char * s2);
Int strncmp(const char * s1,const char * s2,size_t n);
Char * strchr(const char *s,int c);该函数返回一个指向字符串S中存放字符c的第一个位置的指针(标志结束的空字符时字符串的一部分,因此也可以搜索到它),如果没有找到该字符,函数就返回空指针。
Char *strpbrk(const char *s1,const char *s2);
该函数返回一个指针,指向字符串S1中存放S2字符串中的任何字符的第一个位置,如果没有找到任何字符,函数返回空指针。
排序字符串数组的时候可以用指针指向每条字符串,排序这个指针的数组;
Gets(char[n])!=NULL,遇到EOF字符,
Char[n][0]!=’\0’ 或者第一个是空行。
字符函数和字符串
Ctype.h
Toupper() 转换为大写
Ispunct() 是否为符号
命令行参数
Int main(int argc,char *argv[]) { //argc参数的个数,argv参数的数组 return 0; }
C编译器允许main()没有参数,或者两个参数(有些实现允许更多的参数,但这将是对标准的扩展),两个参数时,第一个参数是命令行中的字符串数,但不是必须的,第二个参数是一个指向字符串指针数组。Argv[0]一般是程序名,argv[1]是第一个参数,argv[2]是第二个参数。很多环境允许使用把多个单词集中在一个参数里。例如:repeat “I am hungry” now
把字符串转换为数字
转换函数 atoi();atof();atol();strtol();strtoul()把一个字符串转换为unsigned long型值,strtod()把一个字符串转换为duble型
如果字符串只是以一个整数作为开头,atoi函数依然可以工作,在这种情况下函数在遇到非整数部分之前一直转换字符。