C语言复习笔记系列(一)- 易错点总结

C语言复习笔记(一)

  • 变量和数据类型
    • 内存分区
    • if语句中各种值和零值比较
    • 数据类型长度
    • 整型数和字符型数运算
    • 有符号数和无符号数运算
  • 运算符、表达式和语句
    • 运算符优先级
    • sizeof关键字
    • 自增自减运算符
  • 字符串
    • 字符串定义
    • 字符型指针数组与字符型数组指针
    • 转义字符
  • 指针
    • void空指针
    • 指针运算
    • 立即数转换为指针
    • 各种类型指针
    • 指针与多维数组

变量和数据类型

内存分区

程序的局部变量存在于栈区,全局变量存在全局区(静态区),动态申请的数据存在于堆。

  • 栈区:由编译器自动分配释放的内存区域,用于存放函数的参数值、局部变量的值等,随着函数的调用和返回,栈区的数据将动态变化。
  • 堆区:程序员向系统申请和释放,程序结束时还没有被释放,则可能由操作系统回收。
  • 全局区(静态区):用来保存全局变量和静态变量,在进入程序时分配区域,程序结束时释放。
  • 文字常量区:用来保存常量字符串的区域,程序结束后由系统释放。
  • 程序代码区:用来保存函数体的二进制代码。

if语句中各种值和零值比较

类型 =
bool类型 if(flag) if(!flag)
int类型 if(val == 0) if(val != 0)
浮点数类型 if(x >= 0) if(x <= 0)
指针类型 if(ptr == NULL) if(ptr != NULL)

数据类型长度

数据类型 16位机 32位机 64位机
bool 1 1 1
char 1 1 1
short 2 2 2
int 2 4 4
long 4 4 4
long long / 8 8
float / / 4
double / / 8
long double / / 12
指针 2 4 8

注:标注“/”符号表示我目前还不清楚

整型数和字符型数运算

当字符型数向整型数赋值时,是将十进制数值赋值过去,而不是二进制位赋值

	int main(){
    	int i;
    	char c=0x80; //c=-128;
    	i=c;//i=-128
    	if(i>0) printf("%s",">0");
    	else printf("%s","<=0");
    	return 0;
	}

该程序运行后输出:

	<=0

有符号数和无符号数运算

根据C/C++语言中的整数自动转换原则:当表达式中存在有符号类型和无符号类型时,所有的操作数都自动转换为无符号类型

	void foo(void) 
	{ 
		unsigned int a = 6; 
		int b = -20; 
		(a+b > 6) ? puts("> 6") : puts(" <= 6"); 
	} 

运行程序后输出:

	> 6//结果输出

运算符、表达式和语句

运算符优先级

算术运算符 > 关系运算符 > 逻辑运算符 > 赋值运算符
逻辑运算符中“逻辑非 !”除外

优先级问题 表达式 误认结果 实际结果
++等于* *p++ *(p++) (*p),p++
++等于* *++p (*p)++ *(p++)
*高于+ *p+4 *(p+4) (*p)+4
[] 高于 * int *ap[] int (*ap)() int *(ap[])
. 高于 * *p.f (*p).f *(p.f)

在实际编程中,记不住优先级高低也没关系,加个括号()就行了,因为括号优先级最高

	int main(){
		char *p = {"964ltdswa"};
		printf("%c ",*p+4);
		printf("%c ",*p++);
		printf("%c ",*p);
		return 0;
	}

运行程序后输出:

	= 9 6

sizeof关键字

功能:返回对象所占用的内存空间,但不能返回动态分配内存空间的大小

  • 结构体的sizeof
    内存对齐:在64位机中默认4字节或最大数据类型长度对齐(在codeblocks中测试)
    当整体空间小于4字节时,返回值为实际空间类型长度。
    当整体空间大于4字节时,返回值按照内存对齐准则计算。
    struct size{
    	int a;
    	char b;
    	double c;
    	} 
    sizeof() = 16;
    struct size{
		char digit:4;//位域,仅用4个位表示,位域不能跨字节,最多为8位
		char index:4;
		unsigned short data:8;
		unsigned short;
    	} 
    sizeof() = 4;
    struct size{
    	char a;
    	char b;
    	char c;
    	} 
    sizeof() = 3;
    struct size{
    	double a;
    	short b;
    	int c;
    	char d;
    	} 
    sizeof() = 20;
    struct size{
    	} 
    sizeof() = 1;
  • 联合体的sizeof
    返回最大长度数据类型的所占的内存空间
    union size{
    int a;
    double b;
    	} 
    sizeof() = 8;
  • 数组的sizeof
    返回数组所占内存空间的大小,字符串数组包括’\0’计算在内
	char str[] = "abc";
    sizeof(str) = 4;
    //
    char a[10];
    sizeof(a) = 10;
  • 指针的sizeof
    数组指针返回指针所指向内容的大小,其余返回指针大小
	char a = 8;
	char *p = &a;
	sizeof(p) = 4;
	//
	char **str = "abcdef";
    sizeof(str) = 4;
    //
    char a[10];
   	char *pt = a;
    sizeof(a) = 10;
    sizeof(pt) = 4;
    //
    int *a = (int*)malloc(10*sizeof(int));
    sizeof(a) = 4;//返回指针大小

自增自减运算符

  • a++:先将a的值代入表达式,在表达式运算完毕后,再将a进行++处理
  • ++a:先将a进行++处理,再将a的值代入表达式
  • a–:先将a的值代入表达式,在表达式运算完毕后,再将a进行- -处理
  • –a:先将a进行- -处理,再将a的值代入表达式

示例程序如下:

	int main(void)
	{
		int a,b,c,d;
		a = 9;
		b = a++;		//b=a=9,a++
		c = ++a;		//a++;c=a=11;
		d = 10*a++;		//d=10*a=10*11,a++;
   		printf("%d, %d, %d",b, c, d);
		return 0;
	}

运行程序后结果如下:

	9 11 110

特别情况:

	int main(void)
	{
		int a,b,c;
		a = 2;
		b = 3;
		c = a+++++b;
   		printf("%d, %d, %d",b, c, d);
		return 0;
	}

程序运行后,会出现这样的结果:

	error: lvalue required as increment operand

因为“a+++++b”这一段根本就无法解析,编译系统从左至右扫描整条语句,首先遇到a++,判断出来是一个a的后缀自加运算,然后接着扫描,遇到一个+,+是一个二目运算符,它的左边已经有一个运算数a++了,系统就继续向右搜索,然后又遇到一个+,++比+的运算级别要高,这时,编译系统就将两个+看成一个整体来处理,既然是++,编译系统就认定,肯定它的左边或右边有一个变量,编译系统先搜索左边,发现++,不是变量,再搜索右边,发现+b,+b是什么怎么回事呢?编译系统是无法搞明白的,因此它就认为++是一个缺少左值的自增运算符,于是提示提示用户:error!
但是以下情况是可以计算出结果的:

	int main(void)
	{
		int a,b,c,d,e,f,g,h,i;
		a = c = e = 3;
		b = d = f = 2;
		g = a++ + ++b;
		h =(c++)+(++d);
		i = e+++f;
    	printf("%d, %d, %d",g, h, i);
		return 0;
	}

程序运行后结果输出:

	6 6 5

字符串

字符串定义

字符串定义有三种方式,字符串字面量,char类型数组,指向char类型的指针;

  1. 字符串字面量(字符串常量)
    用双引号括起来的内容称为字符串字面量,也叫作字符串常量。双引号中的字符和编译器自动加入末尾的\0字符,都作为字符串储存在内存中,所以如下所示都是字符串字面量,字符串常量属于静态存储类别(static storage class),说明如果在函数中使用字符串常量,该字符串只会被储存一次,在整个程序的生命期内存在,即使函数被调用多次
	"I am a symbolic stringconstant."
	"I am a string in an array."
	"Something is pointed at me."
	"Here are somestrings:"
  1. 指向char类型的指针
	const char * pt1 = "Something is pointing at me.";
  1. char类型数组
	const char ar1[] = "Something is pointing at me.";
  1. 数组形式和指针形式区别
    以上面的声明为例,通常字符串都作为可执行文件的一部分储存在数据段中。当把程序载入内存时,也载入了程序中的字符串,字符串储存在静态存储区中。当程序在开始运行时会为该数组分配内存。此时将字符串拷贝到数组中。注意,此时字符串有两个副本。一个是在静态内存中的字符串字面量,另一个是储存在ar1数组中的字符串。然后,编译器便把数组名ar1识别为该数组首元素地址(&ar1[0])的别名。在数组形式中,ar1是地址常量。不能更改ar1,如果改变了ar1,则意味着改变了数组的存储位置(即地址)。可以进行类似ar1+1这样的操作,标识数组的下一个元素。但是不允许进行++ar1这样的操作。递增运算符只能用于变量名前,不能用于常量。
    指针形式(*pt1)也使得编译器为字符串在静态存储区预留29个元素的空间。一旦开始执行程序,它会为指针变量pt1留出一个储存位置,并把字符串的地址储存在指针变量中。该变量最初指向该字符串的首字符。但是它的值可以改变。因此,可以使用递增运算符。例如,++pt1将指向第2 个字符(o)。字符串字面量被视为const数据。由于pt1指向这个const数据,所以应该把pt1声明为指向const数据的指针。这意味着不能用pt1改变它所指向的数据,但是仍然可以改变pt1的值(即pt1指向的位置)。
    总结起来就是,初始化数组把静态存储区的字符串拷贝到数组中,而初始化指针只把字符串的地址拷贝给指针,两者都可以使用数组表示法,都可以进行指针加法操作,但数组名作为常量不能使用递增运算符,字符数组可以修改字符串数据(修改的是字符串常量的副本),指向字符串字面量的指针无法修改指向的数据
	#define MSG "I'm special"
	#include 
	int main()
	{
		char ar[] = MSG;
		const char *pt = MSG;
		printf("address of \"I'm special\": %p \n", "I'm special");
		printf("         address ar: %p\n", ar);
		printf("         address pt: %p\n", pt);
		printf("       address of MSG: %p\n", MSG);
		printf("address of \"I'm special\": %p \n", "I'm special");
	return 0;
	}

运行该程序后的输出:

	address of "I'm special": 0x100000f10
	address ar: 0x7fff5fbff858
	address pt: 0x100000f10
	address of MSG: 0x100000f10
	address of "I'm special": 0x100000f10

该程序的输出说明:第一,pt和MSG的地址相同,而ar的地址不同。第二,字符串字面量"I’m special"在程序的两个 printf()函数中出现了两次,但是编译器只使用了一个存储位置,而且与MSG的地址相同,说明编译器可以把多次使用的相同字面量储存在一处或多处。第三,静态数据使用的内存与ar使用的动态内存不同,不仅值不同,特定编译器甚至使用不同的位数表示两种内存。

字符型指针数组与字符型数组指针

转义字符

在C语言中,一个字符除了可以用它的实体(也就是真正的字符)表示,还可以用编码值表示,每个字符都有其唯一对应编码值。这种使用编码值来间接地表示字符的方式称为转义字符。转义字符以\或者\x开头,以\开头表示后跟八进制形式的编码值,以\x开头表示后跟十六进制形式的编码值。对于转义字符来说,只能使用八进制或者十六进制。

	int main(){
    	puts("\x68\164\164\x70\x73://csdn.\x6e\145\x74");
	    return 0;
	}
	//运行结果
	https://csdn.net

转义字符取值范围

  • 八进制形式的转义字符最多后跟三个数字,也即\ddd,最大取值是\177
  • 十六进制形式的转义字符最多后跟两个数字,也即\xdd,最大取值是\x7f

指针

void空指针

void指针可以指向任意数据类型,也可以接受任意类型的指针对void指针幅值,但是void指针接受一种类型的指针赋值后,不能再接受其他类型的指针赋值

	int *a;
	void *p;
	char *s;
	p = a;//正确
	p = s;//错误

指针运算

形如"ptr+x"的结果等于"ptr+x*sizeof(ptr)"

	unsigned char *p1;
	p1 = (unsigned char*)0x08000100;
	p1+5 = 0x08000105;
	//
	long int *p2;
	p2 = (long int*)0x81000000;
	p2+5 = 0x81000014;//0x代表十六机制

立即数转换为指针

  • 普通数据类型指针
	(int*)0x81001254
	(char*)0x80254618
	//设置一绝对地址为0x67a9的整型变量的值为0xaa66
	*((int*)0x67a9) = 0xaa66;
  • 函数指针
	//让程序跳到绝对地址0x100000去执行
	//有以下两种格式
	(*(void(*)(void))0x100000();
	((void(*)(void))0x100000();
1、首先void(*)(void)是指向函数的指针,可用于强制类型转换
2、把0x100000转换为函数指针,(void(*)(void))0x100000
3、进行函数调用(跳到地址去执行)(*(void(*)(void))0x100000)();
4、设void func(void){},func为函数名,也是函数体首地址
5、令void(*func_p)(void) = func;
6、(*func_p)();和(func_p)();的执行结果是一样的,看到其他人有说第二种属于历史遗留问题;
7、可知第二种不加*号也可

各种类型指针

	int a;				//一个整型数
	int *a;				//一个指向整型数的指针
	int **a;			//一个指向指针的指针,它指向的指针指向一个整型数
	int a[10];			//一个有十个整型数的数组
	int* a[10];			//一个有十个指针的数组,每个指针指向一个整型数
	int (*a)[10];		//一个指向有十个整型数的数组的指针
	int (*a)(int);		//一个指向函数的指针,该函数有一个整型参数,并返回一个整型数
	int (*a[10])(int)	//一个有十个指针的数组,每个指针指向一个函数,该函数有一个整型参数,并返回一个整型数 

上述指针同样适用于其他数据类型,并且对于字符型指针数组某一元素还可以这样表示索引:

	main() { 
  		char *str[]={"ab","cd","ef","gh","ij","kl"}; 
  		char *t; 
  		t=(str+4)[-1]; 
  		printf("%s",t); 
 	}

程序运行后输出:

	gh

指针与多维数组

假设有如下声明:

	int *pt;			// pt一个指向指针的指针
	int (*pa)[3];		// pa一个指向内含3个int类型元素数组的指针
	int ar1[2][3];		// ar1一个指向内含3个int类型元素数组的指针
	int ar2[3][2];		// ar3一个指向内含2个int类型元素数组的指针
	int **p2; 			// p2一个指向指针的指针
	int a3[3];			// ar3一个指向int的指针

有如下的语句:

	pt = &ar1[0][0];	// 都是指向int的指针
	pt = ar1[0];		// 都是指向int的指针
	pt = ar1;			// 无效,类型不匹配
	pt = ar3;			// 都是指向int的指针
	pa = ar1;			// 都是指向内含3个int类型元素数组的指针
	pa = ar2;			// 无效,类型不匹配
	pa = &a3;			// 都是指向内含3个int类型元素数组的指针
	p2 = &pt;			// 都是指向指针的指针
	*p2 = ar2[0];		// 都是指向int的指针
	p2 = ar2;			// 无效,类型不匹配

进一步地,若有以下地址:

	ar1 = 0x68f438;
	ar3 = 0x79d536;

则可得:

	ar1[0] = 0x68f438;
	ar1+1 =  0x68f444;
	ar1[0]+1 = 0x68f43c;
	ar3[0] = 0x79d536;
	ar3+1 = 0x79d53a;
	&ar3+1 = 0x79d542;

你可能感兴趣的:(C语言复习笔记系列(一)- 易错点总结)