POINTERS ON C学习笔记

第1章 快速上手

(1)数组参数是以引用(reference)形式进行传递的,也就是传址调用,而标量和常量则是按值(value)形式传递的,在函数中对标量参数的任何修改都会在函数返回时丢失,因此,被调用函数无法修改调用函数以传值形式传递给它的参数。
(2)在函数声明的数组参数中,并未指定数组长度。这个是正确的,可以使函数可以处理任何长度的一维数组,但是如果要知道数组的长度的话就必须将数组长度作为参数传递给函数。

int read_column_numbers(int columns[],int max)
{}

(3)当scanf函数对输入值进行转换时,它只读取需要读取的字符。这样,该输入行包含了最后一个值的剩余部分仍会留在那里,等待被读取。它可能只包含作为终止符的换行符,也可能包含其他数字。不论如何,while循环将读取并丢弃这些剩余的字符,防止他们被解释为第一行数据。

while((ch = getchar()) != EOF && ch != '\n')
;

(4)

int ch;
ch = getchar();

在这里ch被声明为int型,但是我们事实上却用它来读取字符。EOF是一个整形值,它的位数比字符类型要多,把ch声明为整形可以防止输入读取的字符意外地被解释为EOF,字符只是小整型数而已,所以用一个整型变量容纳字符值并不会引起任何问题。(如果输入中不再有任何字符,函数就会返回常量EOF)。

第2章 基本概念

2.2.1 字符

(1)三字母词(trigrph)
??( [ ??< { ??= #
??) ] ??> } ??/
??! | ??’ ^ ??- ~
(2)特殊字符
? 在书写连续多个问号时使用,防止它们被解释为三字母词
" 以下情况大多在字符串常量中表示\后面的字符

\
(3)转义符
\a 警告字符。响铃或者产生一些其他可以看见的信号。
\b 退格键
\f 进纸字符
\n 换行符
\r 回车符
\t 水平制表符
\v 垂直制表符
\ddd ddd表示1~3位八进制数字。这个转义符就是给定的八进制数值所代表的字符。
\xddd 与上面类似这是十六进制。

例子:\x100:超出字符表示的范围(字符为八位,此十六进制数为十进制256,为9位二进制,所以\x99就可以)
\0123: 被分为\012 和3两部分,因为\ddd只能由1~3位数字
\x0123:这个超出了范围1

2.2.2注释

(1)所有的注释都会被预处理器拿掉,取而代之的是一个空格。因此,注释可以出现于任何空格可以出现的地方。

2.2.4标识符

(1)标识符(identifier)由大小写字母、数字和下划线组成,但不能以数字开头。
(2)标识符长度没有限制,但标准允许编译器忽略第31个字符以后的字符。标准同时允许编译器对用于表示外部名字2(也就是链接器操纵的名字)的标识符进行限制,只识别前六位不区分大小写的字符。

第3章 数据

3.2.2 声明简单数组

(1)如果下标值是从那些已知是正确的值计算得来的,,那么就u需要检查它的值。如果一个用作下标的值是根据某种方法从用户输入的数据产生而来的,那么在使用它之前必需进行检测,确保它们位于有效的范围之内。

3.2.3 声明指针

(1)指针声明

#define pointer int*
int* b,c,d; 	//这里声明了两个int型变量和一个int型指针
int *b,*c,*d;	//这里声明了三个int型指针
pointer b,c;	//只声明了一个int型指针变量b和一个int型变量c,类似第二行

3.4 常量

(1)常指针:

int const *pci;		//是一个指向整型常量的指针。你可以修改指针的值,但是你不能修改它所指向的值。
int *const pci;		//是一个指向整型的常量指针。此时指针是常量,它的值(地址)无法修改,但是你可以修改它所指向的整型的值。
int const * const pci;	//指针和指针所指向的值都是常量。

(2)const和#define

#define MAX_ELEMENTS 50
int const max_elements = 50;

允许使用字面值常量的地方都可以使用前者,比如声明数组的长度。const变量只能用于允许使用变量的地方。

3.5.1 代码作用域

(1)当代码处于嵌套状态时,声明于内层代码块的标识符的作用域到达该代码块的尾部便告终止。如果内层代码块有一个标识符的名字与外层代码块的一个标识符同名,内层那个标识符就将隐藏外层的标识符——外层的那个标识符无法在内层代码块中通过名字访问。

3.5.2 文件作用域

(1)任何在所有代码块之外声明的标识符都具有文件作用域(file scope),它表示这些标识符从他们的声明之处直到它所在的源文件结尾处都是可以访问的。

3.6 链接属性

1———> typedef har *a;
2———> int b;
3———> int a(int d);//int d 为 4
	   {
	   		5———>int e;
	   		6———>int f (int g);//int g	为 7
	   		……
	   }

(1)在缺省的条件下,标识符b、c、和f的链接属性为external,其余的标识符的链接属性为none。如果另一个源文件也包含了标识符b的类似声明并调用函数c。它们实际上访问的事这个源文件所定义的实体。f的链接属性之所以是external是因为它是个函数名。在这个源文件中调用函数f,它实际上将链接到其他源文件所定义的函数,甚至这个函数的定义可能出现在某个函数库。
(2)static
如果第二个声明改为:

static int b;//则变量b就将成为这个源文件所私有。它其他源文件中如果也链接到一个叫做b的变量,那么他所引用的是另一个不同的变量。
static int c(int d){}  //这可以防止它被其他源文件调用

static只对缺省链接属性为external的声明才改变链接属性。
(3)extern

static int i;
int func()
{
	int j;
	extern int k;
	extern int i;
	……
}

extern比较复杂。
1⃣️:如上面代码第五行对k的声明,函数就可以访问在其他源文件声明的外部变量了。
2⃣️:如果extern用于标识符的第二次或以后的声明它并不会更改第一次的声明。如上面的代码第六行对i的声明不会改变第一行的声明链接属性。

3.7 存储类型

(1)register
register可以用于自动变量的声明,提示它们应该存储于机器的硬件寄存器而不是。通常,寄存器变量比存储于内存的变量访问起来更高效。
(2)初始化
静态变量将初始化为0.自动变量的初始化需要更多的开销。
1⃣️:在声明变量的同时进行初始化和先声明后赋值语句效率并无提高。
2⃣️:可能会重复运行
3⃣️:优点:由于初始化在运行时执行,你可以用热和表达式作为初始化值。
4⃣️:除非你对自动i按量进行显式的初始化,否则当自动变量创建时,它们的值总是垃圾。

3.13 问题

3.假定你正在编写一个程序,它必须运行于两台机器之上。这两台机器的缺省整型长度并不相同,一个是16位另一个是32位。而这两台机器的长整型长度分别为32位和64位。程序所使用的有些变量的值并不太大,足以保存于任何一台机器的缺省整型变量中,但有些变量的值却有些大,必须是32位的整型变量才能容纳。但在1.位机器上,对于那些用16位足以容纳的值而言,时间和空间的浪费不可小视。在32位机器上也存在时间和空间的浪费问题。
如果想让这些变量在任何一台机器上的长度都合适的话,你该如何声明他们呢?(包含一个头文件,里面包含每台机器特定的声明)
在这里插入图片描述

第4章 语句

4.5.1 break和continue语句

(1)在while循环中可以使用break语句,用于永久终止循环。
(2)在while循环中也可以使用continue语句,它用于永久停止当前的那次循环。在执行完continue语句之后,执行流接下来就是重新测试表达式的值,决定是否继续执行循环。
(3)这两条语句的任何一条如果出现在嵌套的循环内部,它只对最内层的循环起作用,你无法使用break和continue语句影响外层的循环。(连续两个break;没有意义,第二个无法被执行)(要想直接退出多重循环可以在循环条件里加一个标志量,在应该用break;前,改变这个标志量,就可以退出多重循环。也可以用goto语句)

4.8 switch语句

switch( expression )
	case constant-expression:  statement;   //没有break的话,程序会继续向下运行
	case constant-expression:  
		/*FALL THRU*/   //如果一个case没有break的话最好加上这个,方便以后阅读和维护,因为大多数case中都有break;
	default:    ;//如果所有case都没有被执行,则这部分将被执行。

(1)其中expression的结果必须是整数。
(2)每个case标签必须有一个唯一的值。常量表达式(constant-expression)是指在编译期间进行求值的表达式,它不能是任何变量。
(3)case标签并不把语句列表划分为几个部分,只是确定语句列表的进入点。

4.9 goto语句

	i = 0;
outer_next:
	statement;
	goto outer_end;
inter_next:
	statement;
outer_end:
	;

(1)如上述程序,在语句前面加上标签。就是标识符后面加上冒号。
(2)goto语句是一种危险的语句,尽量不要使用,但是可以用来退出深层嵌套。

第5章 操作符和表达式

5.1.1 算数操作符

/   *   -   +   %
除了%,其余几个操作符都是既适用于浮点类型又适用于整型类型。当/操作符的两个操作数都是整数时,它执行整除运算,在其他情况下则执行浮点数除法。

5.1.2 移位操作符

(1)左移位:值最左边的几位被丢掉,右边多出来的几个空位则由0补齐。
(2)右移位:逻辑移位(左边移入的位用0填充),算术移位(左边移入的位由符号位决定是什么移入什么,保持原来的正负号)。

5.1.3 位操作符

&   |   ^
分别为AND OR 和 XOR(异或,两个相同则为0,不同则为1)
例子:value & 1 << bit number //左移符号优先级高,所以先算后面的,这条语句的功能是对value的第bit number位测试,如果为1,则表达式结果为非零值。

5.1.4 赋值

(1)警告例子:

	a = x = y + 3;		//如果x是一个字符型变量,那么y+3的值就会被截去一段,以便容纳于字符类型的变量中。

这个问题在下面的例子中会造成错误。

	char ch;
	...
	while((ch = getchar()) != EOF ) ...

EOF 需要的位数比字符型值所提供的位数要多,这也是getchar返回一个整形值而不是字符值的原因。但是返回的时候被ch所截短,就会出现错误。

5.1.5 单目操作符

!  ++   -   &   sizeof   ~   - -   +   *   (类型)
(1)~操作符对整形的操作数进行求补操作,所有位1变为0,0变为1.
(2)+操作符和-操作符在这里表示正负的意思。
(3)*操作符是间接访问操作符,与指针一起使用。
(4)sizeof在判断表达式的长度时不需要对表达式进行求值,所以执行sizeof(a=b+1)并没有向a赋任何值。
(5)++a的结果是a值的拷贝,而不是a变量。

5.1.6 关系操作符

>   >=   <   <=   !=   ==
这些操作符产生的结果都是一个整型值,而不是布尔值。

5.1.8 条件操作符

expression1   ?   expression2   :   expression3
条件操作符的优先级非常低,所以各个操作数即使不加括号,一般也不会有问题,但是还是要加上的;
例子:

b[2*c+d(e/5)] = a>5 ? 3 : -20;//如果a>5的话前面的表达式=3

5.1.9 逗号操作符

可以用来简化程序
例子:

a = get_value();
count_value(a);
while( a>0 )
{
	...
	a = get_value();
	count_value( a );
}

/*可以简化成如下代码*/
while( a = get_value(),count_value(a),a>0)
{
	...
}

5.3 左值和右值

a = b + 25
(1)左值:就是能放到表达式的左面的值(表达式或者变量,说是值是因为上述两个本质是一个地址),它标示了一个可以存储结果的地点。
(2)右值:就是能放到表达式右面的值,可以是一个具体的值,也可以是一个地点,视情况而定。

5.4.2 算术转换

有下面层次(寻常算数转换(usual arithmetic conversion))

long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名比较低,那么它首先将转换为另外一个操作数的类型然后执行操作。
下面的代码包含了一个潜在的问题:

int a = 5000;
int b = 25;
int c = a * b;
//问题在于表达式a*b是以整型进行计算,在32位整数的机器上没有问题,但是在16位的机器上,这个乘法运算会产生一处,这样c就会被初始化为错误的值。
//修改如下
long c = ( long ) a * b;
//当整型值转换为float型值时,也有可能损失精度。float型值仅要求6位数字的精度。如果将一个超过6位数字的整型值赋值给一个float型变量时,其结果可能只是该整型值的近似值。

当float型值转换为整形值时,小数部分被舍弃(并不进行四舍五入)。如果浮点数的值过于庞大,无法容纳于整型值中,那么其结果将是未定义的。

5.4.3 操作符的属性

优先级,结合性以及操作符是否控制执行的顺序。

第6章 指针

6.5未初始化和非法的指针

(1)如果你运气好,a的初始值会是一个非法地址,这样赋值语句将会出错,从而终止程序。在UNIX系统上,这个错误呗称为“段违例(segmentation violation)”或“内存错误(memory fault)”。它提示程序试图访问一个并未分配给程序的内存位置。在一台运行Windows的PC上,对未初始化或非法指针进行间接的访问操作是一般保护性异常(General Protection Exception)的根源之一。

int *a;
...
*a = 12;

6.6 NULL指针

(1)如果函数需要寻找某个特定值并需要返回一个指针指向寻找到的数组元素。一种更为安全的策略是让函数返回两个独立的值:首先是一个状态值,用于提示查找是否成功;其次是个指针,当状态值提示查找成功时,它指向的就是查找到的元素。
(2)对一个NULL指针进行解引用操作是非法的。

6.9 指针常量

假定变量a存储于为位置100.

*100 = 25; 			//看上去是把25赋值给a,但是这个是非法的,如果你想达到此目的,你需要改为如下。
*(int *)100 = 25;	//需要指出100为指向整型的地址类型

6.10 指针的指针

int a = 12;
int *b = &a;
int **c = &b;		//*又从右到左的结合性,相当于*(*c)。

6.11 指针表达式

char ch = ‘a’;
char *cp = &ch;
++*cp++;			//++的优先级高于*,那么这个作为左值的右值的话,是返回一个‘b’,因为cp++是在用了之后再+1,所以cp++返回的是一个cp的拷贝,*cp++就是返回‘a’,++(*cp++)就是将‘a’+1然后返回。

6.13 指针运算

(1)当一个指针和一个整数量执行算术运算时,整数在执行加法运算前始终会根据合适的大小进行调整。(根据指针所指向类型的大小)
(2)两个指针相减的结果的类型时ptrdiff_t,它是一种有符号整数类型。减法运算的值是两个指针在内存中的距离(以数组元素的长度为单位)。
(3)大多数编译器都不会检查指针表达式的结果是否位于合法的边界之内。程序员需要确认这一点。编译器将不会阻止你取一个标量变量的地址并对它执行指针运算,即使它无法预测运算结果所产生的指针将指向哪个变量。越界指针和指向未知值的指针是两个常见的错误根源。
(3)指针也可以进行比较
<   <=   >   >=
不过前提是他们都指向同一个数组中的元素。(如果不是同一个数组的话,它们在内存中的相对位置不确定,尽管你可能在后面声明的数组)

#define N_VALUES  5
float values[N_VALUES];
float *vp;
for(vp = &values[N_VALUES-1];vp>=&values[0]; vp— —)
	*vp = 0;
/*这部分存在问题在数组第一个元素被清除之后,vp仍然—-但是,这时候vp指向的是数组元素前一个地址,这个是未定义的,当比较vp>=values[0]的时候,就不一定了。标准允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针进行比较,但不允许指向数组第一个元素之前的那个内存位置的指针进行比较*/

第7章

7.1 函数定义

(1)函数的return语句必须包含一个表达式(数字,变量等)。通常,表达式的类型就是函数声明的返回类型。只有当编译器可以通过寻常算术转换把表达式的类型转换为正确的类型时,才允许返回类型与函数声明的返回类型不同的表达式。

7.2 函数声明

Void a()
{
	int *func(int  *value,int len);
	...
}
Void b()
{
	int func(int len,int  *value);		//仔细观察这两个函数参数顺序和返回类型都不一样,问题在于这两个函数原型都写于函数体的内部,都有代码块作用域,所以编译器在每个函数结束前会把它记住的原型信息丢弃,这样它就无法发现她们之间存在的不匹配的情况。
	...
}

(1)标准表示,在同一个代码块中,函数原型必须与同一个函数的任何先前原型匹配,否则编译器应该产生一条错误信息。但是在这个例子中,无法被检测。
(2)为了与各种版本所兼容,一个没有参数的函数的原型应该被写成:

int *func(void);		//上面的函数

7.2.2函数的缺省3认定

(1)如果没有原型,则函数返回一个整型值。
(2)所有的函数都应该具有原型,尤其是那些返回值不是整型的函数。记住,值的类型并不是值的内在本质,而是取决于它被使用的方式。如果编译器认定函数返回一个整型值,它将产生整数指令操纵这个值。如果这个值实际上不是非整型值,比如说是个浮点数,其结果通常将是不准确的。(值都是一堆二进制数进行存储,编译器的方法决定按照什么形式来取这堆二进制数)

7.3 函数的参数

(1)C函数的所有参数均以“按值调用”方式进行传递,这意味着函数将获得参数值的一份拷贝而不是本身。这样函数可以放心修改这个拷贝值,而不必担心会修改调用程序实际传递给他的参数 。(按址传递是指针的值是地址,函数不会修改这个指针指向的地址,所以还是按值调用了)如果传递的参数是一个数组名,并且在函数中使用下标引用该数组的参数,那么在函数中对数组元素进行修改实际上修改的是调用函数中的数组元素。

7.5 递归

//接受一个整型值(无符号),把它转换为字符并打印它,前导零被删除。
#inlcude<stdio.h>
void binary_to_ascii(unsigned int value)
{
	usigned int quotient;
	quotient = value / 10;
	if(qutient != 0)
	{
		binary_to_asscii(quotient);
	}
	putchar(value % 10 + '0');
}

7.5.2 递归与迭代

(1)递归函数调用将涉及一些运行时开销——参数必须压到堆栈中,为局部变量分配内存空间(所有递归均如此,并非特指这个例子),寄存器的值必须保存等。当递归函数每次调用返回时,上述这些操作必须还原,恢复成原来的样子。所以基于这些开销,对于这个程序而言,它并没有简化问题的解决方案。

/* 用递归方法计算n的阶乘 */
long factorial(int n)
{
	if(n <= 0)
		return 1;
	else
		return n * factorial( n - 1);
}
/* 用迭代方法计算n的阶乘 */
long factorial(int n)
{
	int result = 1;
	while( n > 1 )
	{
		resutl *= n;
		n -= 1;
	}
	return result;
}

许多问题是以递归的形式进行解释的,这只是因为它比非递归形式更为清晰。但是这些问题的迭代实现往往比递归实现效率更好,虽然代码的可读性可能稍微差一些。

(2)另一个在效率上差别更明显的例子

/*用递归的方式计算第n个斐波那契数的值*/
long fibonacci(int n)
{
	if(n<=2)
		return 1;
	return fibonacci(n-1) + fibonacci(n-2);
}//这里的代价远不止一个冗余计算,每个递归调用都会触发另外两个递归调用,然后滚雪球一样进行了多次冗余计算。例如:在调用Fibonacci(10)时会触发调用9和8 而在调用9时又会调用一次9本身和一次8和7。在调用fibonacci(30)时,fibonacci(3)被调用了317811次。
/* 用迭代方法计算第n个斐波那契数的值 */
long fibonacci( int n )
{
	long result;
	long previous_result;
	long next_older_result;
	result = previous_result = 1;
	while(n>2)
	{
		n -= 1;
		next_older_result = previous_result;
		presvious_result = result;
		result = previous_result + next_older_sult;
	}
	return result;
}

7.6 可变参数列表

float average(int n_values,int val,int v2,int v3,int v4,int v5)
{
	float sum = v1;
	if(n_values >= 2)
		sum +=v2;
	if(n_values >= 3)
		sum +=v3;
	....
	return sum / n_values;
}//计算表量参数的平均值:差的版本

avg1 = average(3,x,y,z);	//这种行为是未定义的,这样第一个参数可能会与n_values对应,也可能与形参v2对应

7.6.1 stdarg宏

可变参数列表是通过宏来实现的,这些宏定义于stdarg.h头文件,这是标准库的一部分。声明了一个类型va_list和三个宏——va_start、va_arg和va_end。

/*计算指定数量的值的平均值*/
#include<stdarg.h>
float average(int n_values,....)	//注意省略号,表示此处可能传递数量和类型未确定的参数
{
	va_list var_arg;		//用于访问参数列表未确定的部分
	int count;
	float sum = 0;
	/*
	**准备访问可变参数。
	*/
	va_start(var_arg,n_values);		//用va_start()来初始化,第一个参数是va_list类型的变量,第二个参数是省略号前最后一个确定的参数。初始化过程把va_arg变量设置为指向可变参数部分的第一个参数。
	/*
	**添加取自可变参数列表的值。
	*/
	for(count = 0;count < n_values; count += 1)
	{
		sum += va_arg(va_arg,int); 		//接受两个参数,va_list类型变量和参数列表中下一个参数的类型,这里指定下一个访问的变量的类型,并且使va_arg指向下一个参数
	}
	/*
	**完成处理可变参数
	*/
	va_end(var_arg);		//	这里用来结束,暂时理解为free掉这个va_list类型的变量
	return sum / n_values;
}

(1)这里的参数列表至少要有一个命名参数。如果没有将没办法进行初始化。
(1)这些宏没有办法判断实际存在的参数的数量,和类型。

第8章 数组

8.1一维数组

(1)在两种场合下,数组名并不用指针常量来表示——就是当数组名作为sizeof操作符或单目操作符&的操作数时。sizeof返回整个数组的长度(以字节为单位),而不是指向数组的指针的长度。取一个数组名的地址所产生的是一个指向数组的指针,而不是一个指向某个常量值的指针。
(2)数组的指针是常量

 int a[10];
 int *c;
 a = c;	//这里是违法的
 char *str = "asdlkfj";
 *(str+1) = c;	//也是违法的,数组和指针最根本的区别是在内存中的存储区域不一样,字符数组存储在全局数据区或栈区,字符指针的字符串存储在常量区。全局数据区和栈区的字符串(也包括其他数据)有读取和写入的权限,而常量区的字符串(也包括其他数据)只有读取权限,没有写入权限。内存权限的不同导致的一个明显结果就是,字符数组在定义后可以读取和修改每个字符,而对于第二种形式的字符串,一旦被定义后就只能读取不能修改,任何对它的赋值都是错误的。

8.1.2 下标引用

(1)下标检查在C中是一项困难的任务。下标可以作用于任意的指针,而不仅仅是数组名。作用于指针的下标引用的有效性既依赖于该指针当时恰好指向什么内容,也依赖于下标的值。但是这项检查的开销很大,想要正式下标表达式的结果所引用的元素和指针表达式所指向的元素属于同一个数组。需要程序中所有的数组的位置和长度方面的信息,程序运行时这些信息又得实时更新。
(2)

2[array] = 2;	//这个是合法的相当于
*(2+(array)) = 2;	//相当于
*(array + 2) = 2;

8.1.3指针与下标

(1)对于数组用指针或者下标进行使用,下标绝对不会比指针更有效率,但指针优势会比下标更有效率。(当在数组中一次一步地移动时指针更有效)

8.1.10自动计算数组长度

 int vector[] = {1,2,3,4,5};
int len = sizeof(vector)/sizeof(vector[0]);		//得到整型及其他类型数组长度的方法(strlen()只适用于字符型数组)

8.2多维数组

 int  a;
 int b[10];
 int c[6][10];
 int d[3][6][10];		//这里看懂不难,思想比较重要:可以把c看成一个包含6个元素的响亮,只不过塔每个元素的本身是一个包含10个整型元素的向量,依次类推,此思想也能解释 int *p[10]和 int (*p)[10],后者为指向有10个整型元素数组的指针,前者是10个整型指针组成的数组
 

8.2.4指向数组的指针

int (*p)[10];		//指向整型数组的指针
int matrix[3][10];
p = matrix;

int (*q)[] = matrix;		//如果你打算在指针上整型任何指针运算,应该避免此种类型的声明。p仍然是一个指向整型数组的指针,但数组的长度却不见了,当整个整数与这种类型的指针执行指针运算时,它的值将根据空数组的长度进行调整(也就是说,与零相乘)

8.2.5 作为函数参数的多维数组

/*对于一维数组*/
void func1_1(int *vec);
void func1_2(int vec[]);

int vector[10];
func1(vector);

/*对于二维数组*/
void func2_1(int (*mat)[10]);
void func2_2(int mat[][10]);	//注意要有一维数组长度
void func2_3(int **mat);		//这个也能传过去,但是没有了一维数组长度,没办法进行操作

int matrix[3][10];
func2(matrix);
 

8.2.6 初始化


 int matrix[2][3] = {1,2,3,4,5,6};
 int matrix_1[2][3] = 
 {
 	{1,2,3},
 	{4,5,6}
 };				//与上面是等价的,但是更直观,也可以缺少中间的某一个元素,这里的作用将在下面的例子中得到更好的显现
int three_dim[][3][5] = 
{
	{
		{1,2,3,4,5},
		{6,7,8,9,10},
		{11,12,13,14,15}
	},
	{
		{16,17},			//这里就是表示three_dim[1][0][2],three_dim[1][0][3],three_dim[1][0][4]没有数字
		{18,19,20,21,22},
		{23}
	}
};

8.2.7 数组长度自动计算

在多维数组中只有第一维才能根据初始化列表缺省的提供。(如8.2.6第二个程序所示)

8.3 指针数组

#include<string.h>
#include<stdio.h>
//查找c源程序中含有多少个关键字的一部分——比对所传单词是否为关键字
char const *keyword[] =			//这里书上写错了
{
	"so",
	"for",
	"if",
	"register",
	"return",
	"switch",
	"while",
	NULL
};
#define N_KEYWORD   \
	 ( sizeof(keyword) / sizeof(keyword[0]) )

int lookup_keyword(char const * const desired_word, char const *keyword_table[], int const size)
{
	char const **kwp;
	for (kwp = keyword_table; kwp < keyword_table + size; kwp++)
	{
		//printf("kwp:%s\n",*kwp);
		if (strcmp(desired_word, *kwp) == 0)
			return kwp - keyword_table;		//这是返回了找到的词的相对于数组头的相对地址
	}
	return -1;		//没找到的话返回-1
}


int main()
{
	char *word = "while";
	int result;
	result = lookup_keyword(word, keyword, N_KEYWORD);
	//printf("result:%d\n", result);
	if (result != -1)
		printf("the keyword:%s\n", *(keyword + result));

	rewind(stdin);
	getchar();
	return 0;
}

第9章 字符串、字符和字节

9.3不受限制的字符串函数

char *strcat(char *dst,char const *src);  //将src指向的字符串连接在dst的字符串后面(构成的字符串末尾有NULL)
int strcmp(char const *s1,char const *s2);	//如果s1小于s2返回负值,相等0,大于则返回正数

9.3.3函数的返回值

(1)strcpy和stract都返回它们第一个参数的一份拷贝,就是一个指向目标字符数组的指针。由于它们返回这种类型的值,所以你可以嵌套地调用这些函数,如下面这个例子:

strcat(strcpy(dst,a),b);

9.4长度受限的字符串函数

char *strncpy(char *dst ,char const *src,size_t len);	//函数不负责添加末尾表示字符串结束的null需要自己去添加
char *strncat(char *dst,char const *src,size_t len);		//这个会自动添加NULL
int strncmp(char const *s1,char const *s2,size_len);

9.5 字符串查找基础

9.5.1查找一个字符

char *strchr(char const *str,int ch);	//在字符串str中,找到ch第一次出现的位置并返回一个指向该位置的指针。如果不存在就返回一个NULL。
char *strrchr(char const *str,int ch);	//与上个函数差不多,但是找最后一次出现的位置。
//注意第二个参数的类型是int类型,但是实际送来的参数是字符型,

9.5.2查找任何几个字符

 char *strpbrk( char const *str,char const *group);		//返回一个指向字符串str中第一个匹配group中任何一个字符的字符位置。如果未找到,返回NULL。 

9.5.3 查找一个子串

char *strstr(char const *s1,char const *s2);	//返回一个指向在字符串s1中查找整个s2第一次出现的起始位置,如果没有完整的出现过就返回NULL,如果s2是一个空字符串,则返回s1。

9.9 内存操作

memset(char *buffer,char ch,int size);		//将buffer开始的size个字节都初始化位ch。

  1. 注意,任何十六进制都有可能包括在\xddd中,但如果结果值大小超出了表示字符的范围,其结果就是未定义。 ↩︎

  2. 外部名字指在连接过程中所涉及的标识符,其中包括文件间共享的函数名和全局变量名。 ↩︎

  3. 缺省就是缺少定义 ↩︎

发布了18 篇原创文章 · 获赞 1 · 访问量 997

猜你喜欢

转载自blog.csdn.net/weixin_43773038/article/details/103075091