c语言基础总结

零:
	声明、定义、初始化:

	声明:前面加上 extern, 表示变量或函数在其他文件中定义了。
	extern int a; 	// 前面加上了extern
	extern int func(int a, double b);	
	// extern 可置于变量和函数前面,表示变量或者函数的定义是在其他文件中,
	// 提示编译器遇到这个变量或者函数的时候,在其他文件/模块中寻找它的定义
	// 如果在声明函数的时候,extern 是可以被省略

	定义:只要前面没有 extern 的,就属于定义,定义有存储空间,可以获取地址,但是地址内没有合法的值
	int a;
	int a[5];

	赋值:对已定义的变量进行赋值操作
	a = 10;

	初始化:定义变量的同时进行赋值操作,地址和值都可以被获取。
	int a = 3;
	int a[5] = { 0 };


一、字符串函数

int strlen(const char* str);
	int a = strlen(str):返回不包括字符串结尾'\0'的字符串长度,即有效字符长度

int strnlen(const char* str, int maxlen);
	int a = strlen(str, len) : 返回len个char长度以内,不包括字符串结尾'\0'的字符串长度。

int strcat(char* str1, const char* str2);
	strcat(str1, str2): 将参数str2 追加到 str1里;

int strncat(char* str1, const char* str2, int len);
	strncat(str1, str2, len):字符串有限追加len个char字节长度的字符串

...(此处省略很多字);

char* strtok(char* str, const char* delim);
	strtok(str, delim): 分解字符串str为一组字符串子串,str就是要被分隔的字符串,delim分隔符
						strtok()函数每次会把分隔符所在的位置置为'\0',调用前和调用后的str已经不一样了;
	/*
	char s[20] = "abcsabscccs";

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

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

	strtok(NULL, "s");
	printf("%s\n", s);
	*/



二、函数参数的 进栈顺序 和 参数计算顺序

1. 大端对齐 和 小端对齐
	unsigned int a = 0x12345678;

	大端:就是把数值的高位数值放在内存的低位字节,把数值的低位数值放在内存的高位字节

		地址:	0x00c1		0x00c2		0x00c3		0x00c4
	 
		数值:	  0x12        0x34        0x56        0x78

	小端:就是把数值的高位数值放在内存的高位字节,把数值的低位数值放在内存的低位字节

		地址:	0x00c1		0x00c2		0x00c3		0x00c4
	 
		数值:	  0x78        0x56        0x34        0x12


	大端:IBM、Sun的服务器是大端,初代的苹果电脑PowerMac也是大端。
	小端:x86架构的CPU是小端,ARM(英国)架构大多也是小端。


2.	函数参数的进栈顺序

#include <stdio.h>
void func(int a, int b, int c)
{
	printf("a = %d : [%p]\n", a, &a);
	printf("b = %d : [%p]\n", b, &b);
	printf("c = %d : [%p]\n", c, &c);
}

int main(void)
{
	func(100,200,300);
	return 0;
}

//Win32下的编译结果
a = 100 : [0x5fcad0] +4		
b = 200 : [0x5fcad8] +4
c = 300 : [0x5fcae0]

//Ubuntu Linux下编译结果
a = 100 : [0x5fcad0] +4
b = 200 : [0x5fcad8] +4
c = 300 : [0x5fcae0] 

C程序在执行时:"先入栈的数据在栈底,为高地址;后入栈的数据在栈顶,为低地址。"
				"先进后出,后进先出"

上面的例子可以看出:函数参数的进栈顺序是"从右往左"。

// Mac OS X下的编译结果
a = 100 : [0x5fcae0] -4
b = 200 : [0x5fcad8] -4
c = 300 : [0x5fcad0] 

Mac OS X 使用的编译器是 LLVM Clang,这个编译器会优化一些代码,
导致函数参数的进栈顺序是"从左往右"。


3.	函数参数计算顺序
#include <stdio.h>
int main(void)
{
	int a = 10, b = 20, c = 30;
	printf("%d, %d, %d\n", a + b + c, (b = b * 2), (c = c * 2));
	return 0;
}

//Win32 MSVC 下编译结果是
110, 40, 60

//Ubuntu Linux GUN GCC下编译结果是
110, 40, 60

//Mac OS X LLVM Clang下编译结果
60, 40, 60

由此可知:
在MSVC和GCC编译器下,参数的计算顺序是"从右往左",和其参数进栈顺序相同;
在LLVM Clang编译器下,参数的计算顺序是"从左往右",和其参数进栈顺序相同。

所以,当一个函数参数有多个的时候,C/C++语言设计者没有规定函数参数的进栈顺序和计算顺序是哪一种,
这些是有编译器厂商自己规定的。


4.	函数的默认参数
#include <stdio.h>
void func(int a, int b, int c = 300)
{
	printf("a = %d : [%p]\n", a, &a);
	printf("b = %d : [%p]\n", b, &b);
	printf("c = %d : [%p]\n", c, &c);
}

int main(void)
{
	func(100,200);
	return 0;
}

上面的写法,在Ubuntu Linux和MSVC下是不允许,会报语法错误,实参数量要匹配型参数量,
也不允许函数参数在参数列表内赋值。
但是在LLVM Clang下是允许的,这是由于编译器支持这么干。

C编译器有很多,除了GCC、MSVC、Clang外,
还有一种编译叫ICC,这个编译器是Intel公司做的,会优化代码在Intel CPU上的效率。
还有Turbo C,是美国borland公司的产品,现在估计已经被淘汰了。TC

重点:
由于C语言编译器厂商规范不统一,所以我们在写代码的时候,不要写类似的"UB语句"。
"Undefined Behavior" 代码行为未定义:
如果你的程序违反了C标准的某些规则,那么具体会发生什么,C语言没有定义,
也就是说会得到一些奇怪的结果,都是有可能的。比如说整数溢出就是这种情况。

"Unspeakable Behavior"方案未定义:
C标准可能给你的代码提供了好几种可选方案,但是具体用哪一种,C标准没有定义。
比如说上面的例子,函数参数的计算顺序和进栈顺序不一致,而且任何计算方式都行。


写程序的态度要严谨,我们的代码是给人看的,所以为了将来的代码维护和升级,尽量不要用表达过于笼统的语句。

// 我的代码,只有上帝和我能懂。
// 我是你之前的维护人员,经过测试这里的代码最好不要乱动。

#include <stdio.h>
int main(void)
{
	int i = 0;
	int a = i + ++i;
	printf("%d\n", a);
	return 0;
}
//反面教材,这种情况,最后的值会因为编译器不同而不同。


三、一级指针
1.	指针的基本用法:
		"32位系统下是4个字节,64位系统下是8个字节"

	1) 在配合数据类型定义变量的时候使用*号,是代表这个变量的类型是指针类型
		int a = 10;		// 定义一个整型变量,并赋值10
		int *p = &a;	// 定义一个整型指针变量,指向 a 的地址

	2) 在配合变量进行操作的时候使用*号,是取值运算符,意思是可以取出这块内存空间的值、
	
		printf("%d\n", *p);			//取出p指向的内存空间的值
		printf("%d\n", *p += 1);	//取出p指向的内存空间的值。并自增1
		printf("%d\n", a);			// a的值也会被修改,是 11


2.	指针的几种特殊定义方式:
	
	1) int *const p;
		指针常量:p是 int*类型,const 修饰的是p,所以p是常量。则p指向的地址不可修改,也就是说不能再指向其他地方了。
					但是,可以修改他所指向的地址里的值。
		举例:
		int a = 10;
		int b = 20;
		int *const p = &a;
		p = &b;			//错误
		*p = 100;		//允许


	2) const int *p;
	   int const *p;
	   	常量指针:p是 int*类型,const 修饰的是 *p,所以*p是常量。则p是一个指向常量的指针,也就是说他也可以指向其他地方,
	   				但是,他所指向的地址里的值不能修改。

	   	举例:
	   	int a = 10;
	   	int b = 20;
	   	const int *p = &a;
	   	p = &b;			// 允许
	   	*p = 100;		// 错误


	3) const int *const p;
		常量指针常量:p是 int*类型,const 分别修饰了p 和 *p,所以p和*p都是常量,则p是一个指向常量的指针常量,
					也就是说这个指针指向的地址不可修改,地址里的内容也不可修改。

		举例:
		int a = 10;
		int b = 20;
		const int* const p = &a;
		p = &b;			// 错误
		*p = 100;		// 错误


切记:如果你定义了一个指针,那么就一定要知道这个指针指向了什么地方,而且你要保证你定义的这个指针是正确且有效的,
		如果你乱用,我就用程序崩溃来惩罚你!

C语言之所以强大、自由性、高执行效率,很大部分是体现在指针的灵活运用上的。

 《C Primer Plus》第五版 很多页
  入门书、参考书、

 《C和指针》

 《C专家编程》《C陷阱与缺陷》《C沉思录》

 《The C Programming Language》 K&R C, ANSI C标准出台前,这本书第一版就是当时的C标准。


四、多级指针

#include <stdio.h>
int main(void)
{
	int a = 10;			// 定义一个整型变量 a,值为 10
	int *p = &a;		// 定义一个一级整型指针变量p,值为 a的地址
	int **pp = &p;		// 定义一个二级整型指针变量pp,值是 p的地址
	int ***ppp = &pp;	// 定义一个三级整型指针变量ppp,值是 pp 的地址

	// %p 打印地址, %d 打印整数, %f 打印浮点数, %s 打印字符串,%c 打印字符
	// 在配合数据类型定义变量的时候使用*号,是代表这个变量的类型是指针类型
	// 在配合变量进行操作的时候使用*号,是取值运算符,意思是可以取出这块内存空间的值、

	printf("%p, %p, %p, %p\n", &a, &p, &pp, &ppp);
	// 取出各个变量自身在内存的地址

	printf("%p, %p, %p, %p\n", &a, p, pp, ppp);
	// 除了 &a之外,都是打印各个指针变量存储的值

	printf("%p, %p, %p, %p\n", &a, p, *pp, **ppp);
	// %p打印地址(其实是按十六进制方式打印出)
	// 1. 利用取地址运算符&,打印变量 a 的地址
	// 2. 直接打印p的值,也就是 a 的地址
	// 3. 利用取值运算符*,取出二级指针变量pp的值,也就是一级指针p的地址,在通过%p打印出p的值,也就是 a 的地址
	// 4. 利用取值运算符*,取出三级指针变量ppp的值,也就是二级指针pp的地址,再通过取值运算符*,取出二级指针pp的值
	//		也就是p的地址,再通过%p打印出p的的值,也就是 a 的地址

	printf("%d, %d, %d, %d\n", a, *p, **pp, ***ppp);
	// 如果要取出变量a的值,那么用了多少级指针,就用多少个取值运算符,
	// 在取出最后的值之前,各级指针取出的值都是其上一级指针的地址

	return 0;
}


















發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章