嵌入式C语言基础学习笔记

1. 经典入门:hello world

#include
int main()
{
	printf("hello world!\n"); //打印hello world!
}

1.1 概念

源文件:源文件即源代码文件,C语言源文件后缀名是.c。
头文件:头文件后缀名为.h(head,头),C语言代码由源文件和头文件组成。
关键字:关键字是C语言征用了的一些字,这些字在C语言中代表特殊含义,已经被C语言定义好了。每个关键字都有自己特定的含义,我们必须搞懂每个关键字的所有含义和用法,否则就看不懂C语言程序。
注释:C语言中注释以//开头,或者/* */,注释是给程序员看的,不是给机器看的。也
就是说,程序员写注释给其他人看,以让人明白自己为什么要这么写。但是编译器编译程序的时候是忽略注释内容的,所以机器根本看不见注释,也不理注释。
符号:C语言程序中包含很多符号,如; : , + () { } * 等等,各种符号都有自己的含义,必须搞明白各种符号的含义,才能看懂C语言程序。
变量:会变化的量。C语言程序中用变量来进行计算。
函数:函数是C语言的一个基本组成单位,一个C语言程序其实就是由很多函数组成的,每个函数用来完成一定的功能,函数可以调用别的函数来完成功能。函数的标志是().
C语言中有一个特殊的函数叫main,这个函数是整个程序的内定的入口,也就是说整个C语言程序是从main函数开始执行的,其他的函数都是直接或者间接被main调用。

1.2 C语言写代码步骤

第一步:编辑源代码(使用vi或者其他编辑器)
第二步:编译。编译就是用编译器把源程序转化成可执行程序的过程,编译要用到编译器。我们在linux中使用编译器一般是gcc。
譬如:gcc hello.c 把当前目录下hello.c文件编译,得到的可执行文件名字叫a.out
也可以自己指定编译后生成的可执行程序的名字,使用gcc hello.c -o hello
第三步:执行编译生成的可执行程序,执行方式是./hello
第四步:调试。当你执行后发现程序结果不对,不是自己想要的,这时候就是返回来看源代码哪里不对。然后修改,再编译执行,再看结果。如此循环直接结果正确。

1.3 C语言关键字

include(包含) 头文件包含
int(integer,整数) 用来表示一个整数的类型叫整形。
float(浮点型) 用来表示小数的类型
char(character,字符) 字符型数据类型
return(返回) 函数返回

1.4 习题

1.4.1 打印下面图形
  	  *
     ***
    *****
     ***
      *

程序代码

#include 

int main ()
{
printf("\n  *     \
        \n ***    \
		\n*****   \
		\n ***    \
		\n  *     \n");
return 0}
	
1.4.2 打印下面的图形
***********************
*****Hello, world!*****
******************Donke

程序代码

#include 

int main ()
{
	printf("***********************\
	        \n*****Hello, world!*****\
	        \n******************Donke\n");
return 0}
	

总结
1、使用到的技术主要是printf中\n换行和\接续符。
2、题目目的主要是为了熟悉C语言程序的编辑、编译、执行、调试过程。
3、C语言中的注释,短的用//,多行的用/* */。

2. VMWare共享文件夹使用

2.1 为什么使用共享文件夹

当我们裸机安装了Windows,并且在Windows中安装了虚拟机软件VMWare,并且在虚拟机中安装了ubuntu后。我们一般在Windows中编辑源代码,而在linux中编译、执行源代码。这时候就需要在Windows和linux之间进行交互。
之前通过smb服务器,现在通过VMWare的共享文件夹就可以轻松实现。

2.2 怎么建立Windows的共享文件夹

第一步:先在Windows中创建一个文件夹,主要要使用英文名称。
第二步: VMWare中,菜单栏 VM -> Settings -> Options -> Shared Folders选项卡,右边上侧选择Always Enabled,下面点击Add,next,在打开的选项卡中Host Path项目中浏览选择刚才第一步中创建的文件夹,下面Name中会自动弹出一个相同的名字,这个名字是将来Windows中的文件夹在linux虚拟机中的映射文件夹,名字可以改也可以不改。然后一直OK,完成即可。
第三步:在linux中,直接到 /mnt/hgfs目录下,即可找到刚才第二步中Name相同的名字的
文件夹,这个目录即是第一步中Windows中目录在linux下的映射。

3. C语言数据类型

3.1 整形

C语言中的整形对应数学中的整数,整形变量是用来描述一个整数值的,整形变量经过计算
后也只能是整数(整型),不可能出现小数(浮点型).
要求:要学会整形变量的定义,赋值,计算。要学会使用printf函数打印出一些整形变量的值,作为输出和调试。
C语言中整形有三种:
(1)int 整形
(2)short int,又叫做short,短整形(<=整形)
(3)long int,又叫做long,长整型(>=整形)

3.2 浮点型

C语言中浮点型对应数学中的小数。浮点型有float和double两种。使用方式相同,不同在于表示范围和精度。
float表示的范围小,精度低(小数点后6位);
double表示范围大,精度高。(小数点后16位)
范围是说表示的数有多大,精度是指这个数的分辨率有多细
注意:printf中打印float或double类型,要是用%f,不能用%d。

3.3 字符型

字符型对应ASCII字符。ASCII字符是一种编码,就是用数字编码来表示一个符号的一种方法
本质上说,字符型其实也是整形,只是这些整形数被用来表示一些字符的ASCII编码值,所以叫做字符型。
字符型一般用8位二进制表示,无符号字符型范围是0~255.
字符型其实是一种比short还短的整形,所以它可以和int相运算。

3.4 有符号数和无符号数

数学中数是有符号的,有整数和负数之分。所以计算机中的数据类型也有符号,分为有符号数和无符号数。

有符号数:
整形:signed int(简写为 int)
signed long,也写作signed long int,(简写为long)
signed short,也写作signed short int(简写为short)
signed(表示signed int)
浮点型:
signed float(简写为float)
signed double(简写为double)
字符型:
signed char(简写为char)

无符号数:
整形:整形有无符号数,用来表示一些编码编号之类的东西。譬如身份证号,房间号
unsigned int(没有简写)
unsigned long int(简写unsigned long)
unsigned short int(简写为unsigned short)

浮点数:没有无符号浮点数。也就是说,小数一般只用在数学概念中,都是有符号的。
字符型:字符型有无符号数
unsigned char(没有简写)

注意:对于整形和字符型来说,有符号数和无符号数表示的范围是不同的。
譬如字符型,有符号数范围是-128~ 127,无符号数的范围是0~255.

4. C语言常用运算符

4.1 数学运算符号

4.1.1 常见数学运算符号,跟数学中理解相同

+加号
-减号
*乘号
/ 除号,相除以后的商
% 取余符号,相除以后余数是几

4.1.2 跟数学中意义不同的运算符

= 赋值运算符,与数学中的等号完全不同。赋值运算符作用是经过运算后符号左边的部分(
左值,一般是一个变量),的值就等于右边部分(右值,一般是常数或变量)了。
+= a = a + b; 等同于 a += b;
-= a = a - b; 等同于 a -= b;
*= a = a * b; 等同于 a *= b;
/= a = a / b; 等同于 a /= b;
%= a = a % b; 等同于 a %= b;
eg:
bianliang1 = bianliang1 + bianliang2;
bianliang1 += bianliang2;

4.2 判断运算符

==	等于
!=	不等于
> 	大于
<	小于
>=	大于等于
<=	小于等于

4.3 逗号运算符

, 逗号运算符的主要作用是用来分割

4.4 ++与- -

++		a++; 等同于 ++a; 	等同于 a = a + 1;	等同于 a += 1;
--	    a--; 等同于 --a;	等同于 a = a - 1;	等同于 a -= 1;

4.5 作业

1、自己查找资料或写代码测试,总结++放在前面和后面的区别(a++和++a区别)

#include 

int main(void)
{
	int a;
	a = 12;
	printf("a = %d\n", a++);
	printf("a = %d\n", a);
	printf("a = %d\n", ++a);
	
	return 0;
}

执行结果

a = 12
a = 13
a = 14

注意:强调程序风格。
所谓程序风格,主要是指写代码的格式,譬如空格,空行,缩进,注释,文件头,函数头等
越是大公司,越看重程序风格。软件界公认的准则:程序风格不良好的程序是垃圾代码,是废品;写出程序风格不良好的代码的人,是垃圾程序员。

5. 程序结构

在C语言程序里,一共有三种程序结构:顺序结构、选择结构(分支结构)、循环结构

5.1 顺序结构

顺序结构:按照实物本身特性,必须一个接着一个来完成。
对于顺序结构来说,不需判断,因为下一句指令就是你要执行的。对与循环与选择结构来说,都需要进行判断。然后根据判断结果来决定怎么办。

逻辑上有一种类型,叫bool类型(又写作boolean类型,中文叫布尔类型)。布尔类型只有两个值,真和假。

5.2 选择结构

选择结构:到某个节点后,会根据一次判断结果来决定之后走哪一个分支。

C语言中有以下一些判断运算符
==	等于
!=	不等于
>	大于
<	小于
>=	大于等于
<=	小于等于

使用这些判断运算符,可以写出一个判断表达式,这个判断表达式最终的值就是一个bool类型。这个判断表达式的bool值就决定了选择结构如何选择,循环结构如何循环。

C语言中选择结构一共有两种
第一种:if else
引入关键字:if else else if

if (bool值)				// 如果bool值为真,则执行代码段1,否则执行代码段2
{
	代码段1
}
else
{
	代码段2
}

if (bool值1)				// 如果bool值1为真,则执行代码段1
{							// 否则则判断bool值2是否为真,若为真则执行代码段2
	代码段1					// 否则直接执行代码段3
}
else if (bool值2)			// 开头的if和结尾的else都只能有一个,但是中间的
{							// else if可以有好多个。
	代码段2
}
else
{
	代码段3
}

第二种:switch case

#include

int main ()
{
	int num;
	num = 3;
	switch (num)
	{
		case 0:
			printf("Sunday\n");
			break;
		case 1:
			printf("Monday\n");
			break;
		case 2:
			printf("Tuesday\n");
			break;
		case 3:
			printf("Wednesday\n");
			break;
		case 4:
			printf("Thursday\n");
			break;	
		case 5:
			printf("Friday\n");
			break;
		case 6:
			printf("Saturday\n");
			break;
			
		default:
			break;
	}
	
	return 0;
}

注意
1、case中必须是常数,而且必须是整形(不能是float double,可以是int char)
2、一般来说,每个case中代码段后都必须有一个break;如果没有,结果可能会让你大吃一惊
3、case之后一般都会有default。语法上允许没有default,但是建议写代码时一定要写。

switch case和if else对比:
1、if else适合对比条件比较复杂,但是分支比较少的情况;switch case适合那种对比条件
不复杂,但是分支数很多的情况。
2、所有的选择结构,其实都可以用if else来实现。但是只有部分才可以用switch case实现。
一般的做法是:在适合使用switch case的情况下会优先使用switch case,如果不适合使用
switch case,则不得不使用if else。

5.3 循环结构

循环结构:循环结构有一个循环体,循环体是一段代码。对于循环结构来说,关键在于根据判断的结果,来决定循环体执行多少次。
C语言中常用的循环结构有三个:for循环、while循环、do while循环。

5.3.1 for循环

for (循环控制变量初始化; 循环终止条件; 循环控制变量增量)
{
循环体
}

for循环的执行步骤
1、先进行循环控制变量初始化
2、执行循环终止条件,如果判断结果为真,则进入第3步;如果为假则循环终止,退出。
3、执行循环体。
4、执行循环控制变量增量,转入第2步。

注意
1、for循环中()中三部分可不可以省略?
标准的for循环,应该把循环控制变量的初始化,增量都放在()当中,并且在循环体中绝对不应该更改循环控制变量(可以引用它的值,但不应该改变它)。

5.3.2 while循环
i = 1;
sum = 0;					// 循环初始化
while (i < 100)				// 终止条件
{
	printf("i = %d.\n", i);
	sum += i;				// 循环体
	i += 2;					// 循环控制增量,属于循环体的一部分
}

while循环的执行步骤:
0、首先是循环初始化。这一部分其实不属于while循环本身。
1、先判断终止条件是否满足。如果是真,则进入第2步;否则直接退出。
2、执行循环体,然后转入第1步。

5.3.3 do while循环
i = 1;
sum = 0;					// 初始化条件
do
{
	printf("i = %d.\n", i);
	sum += i;
	i += 2;					// 增量,循环体的一部分
}while (i < 100);			// 终止条件

do while循环的执行步骤:
0、首先是循环初始化。这一部分其实不属于do while循环本身。
1、执行循环体(循环控制变量的增量是循环体的一部分)
2、判断终止条件。若成立,则转入1;若不成立则退出

总结:不管哪种循环结构,都不能缺少一些要素:
循环控制条件初始化,终止条件,循环控制变量增量,循环体。
不同的循环方式(for和while和do while)都有这些,只是格式不同,表现形式不同,放的地方不同,可读性不同,看起来和设计起来难度不同。

while循环和do while循环哪里不同?
while循环是先判断后执行,do while循环是先执行后判断
等循环开始转了之后,其实是一样的。

5.4 习题

1、给三个数,打印出其中最大的数
2、规则:90分及以上为优,80分及以上为良,60分及以上为及格,以下为不及格,然后输入一个学生分数,然后printf打印“you” “liang” “jige” “bujige”
3、计算100以内所有奇数的和
4、算一下10!(10987····1)
5、打印一张ASCII码表(提示:数字用%d,字符用%c打印)

程序源码
1、给三个数,打印出其中最大的数

int main()
{
	int a, b, c, max;
	a =0;
	b =0;
	c =0;
	max = 0;
	printf ("please enter three numbers:");
	scanf("%d,%d,%d\n", &a,&b,&c);
	printf ("a=%d,b=%d,c=%d\n",a,b,c);
	
	if (a>b)
	{
		if(a>c)	
		{
			max = a;
			printf("the max of three number is : a = %d\n",max);
		}
		else
		{
			max = c;
			printf("the max of three number is : c = %d\n",max);
		}
	}
	else if(c>b)
	{
		max = c;
		printf("the max of three number is : c = %d\n",max);
	}
	else 
	{
		max = b;
		printf("the max of three number is : b = %d\n",max);
	}
	return 0;
}

2、规则:85分及以上为good,60分及以上为pass,60分及以下为don’t pass,然后输入一个学生分数,然后printf打印“good” “pass” “don’t pass” 源代码

int main(void)
{
	float a = 0.0 ;
	printf("please enter your score :");
	scanf("%f",&a);
	if (a>=85.0)
	{
		printf("\n score =%.1f    \
		        \n your score is good\n",a);			
	}
	else if (a>=60.0)
	{
		printf("\n score =%.1f    \
		        \n your score is pass\n",a);
	}
	else if(a<0)
	{
		printf("\n score =%.1f    \
		        \n please re-enter your score,limit 0~100\n",a);
	}
	else
	{
		printf("\n score =%.1f    \
		        \n your score is Don't Pass\n",a);
	}
}

3、计算100以内所有奇数的和

int main(void)
{

	while (1)
	{ 
		int a = 0;
		int sum = 0;
		int input_value;
		printf ("please enter an odd number(limit :1~99) :");
		scanf("%d",&a);
		input_value  = a;
		if ((a<=99)&&(a%2))
		{
			for (a=1;a<=input_value;a+=2)   
			{
				sum += a;
				printf("a=%d\n",a);
			}
		printf ("the odd number 1 to %d sum is : %d\n",input_value,sum);		
		}
		else
		{
			printf ("enter odd number error(limit :1~99) !\n");
		}
		
	}
	return 0;
}

4、算一下10!

int main()
{
	while (1)
	{ 
		int a = 0;
		int sum_product = 1;
		int input_value;
		printf ("please enter one number(limit :1~10) :");
		scanf("%d",&a);
		input_value  = a;
		if((input_value>0)&&(input_value<=10))
		{
			for (a=input_value;a>=1;a--)   
				{
					sum_product=sum_product*a ;
				}
				printf("the %d factorial result is %d\n",input_value,sum_product);		
		}
		else
		{
			printf("enter number error ,please re-enter!\n");
		}
	}
	return 0;
}

5、打印一张ASCII码表(提示:数字用%d,字符用%c打印)

int main ()
{
	int i;
	printf("********************ASCII**********************\n");
	for (i=0;i<128;i++)
	{
		printf("%d--------------%c\n",i,i);
	}
	printf("********************END**********************\n");	
	return 0;
}

6. 函数

函数是C语言代码的基本组成部分,它是一个小的模块,整个程序由很多个功能独立的模块(函数)组成。这就是程序设计的基本分化方法。

当程序简单的时候,一个人可以用一个main函数搞定功能。当程序变成的时候,超出了人的大脑承受范围,这时候逻辑不清了。这时候就需要把一个大程序分成许多小的模块来组织,于是乎出现了概念叫做函数。

6.1 使用函数来写程序时的关键部分

函数定义:函数定义是关键,是这个函数的实现。
函数定义中包含了函数体
函数体中的代码段决定了这个函数的功能

函数声明:函数声明实际上是叫函数原型声明。
函数的原型包含三部分函数名,返回值类型,函数参数列表
通俗讲,函数原型就是这个函数叫什么,接收什么类型的几个参数,返回一个什么样的返回值。
函数声明的作用,在于告诉使用函数的人,这个函数使用时应该传递给他什么样的参数,它会返回什么样类型的返回值。

函数调用:函数调用就是使用函数名来调用函数完成功能。调用时必须参照原型给函数传参,然后从函数得到适当的返回值作为结果。

6.2 函数参数

形参:形式参数的简称。在函数定义和函数声明中的参数列表中的参数,都是形参。
实参:实际参数的简称。函数调用中,实际传递的参数才是实参。

函数调用的过程,其实就是实参传递给形参的一个过程。这个传递实际是一次拷贝。实际参数的时候,实参(本质是一个变量)本身并没有进入到函数内,而是把自己的值复制了一份传给了函数中的形参,在函数中参与运算。这种传参方法,就叫做传值调用

6.3 返回值:(关键字return)

当函数执行完之后,会给调用该函数的地方返回一个值。这个值的类型就是函数声明中返回值类型,这个值就是函数体中最后一句return xxx;返回的那个值。

6.4 函数名,变量名

第一点:起名字时候不能随意,要遵守规则。
这个规则有两个层次:第一层就是合法,第二层是合理。
合法就是符号C语言中变量名的命名规则。合理就是变量名起的好,人一看就知道什么意思,一看就知道这个函数是干嘛的,而且优美、好记。
第二点:C语言中,所有的符号都是区分大小写的。也就是说abc和Abc和aBc都是不同的符号。
第三点:C语言函数名变量名的命名习惯。没有固定的结论,有多种使用都很广泛的命名方式。介绍两种这里,
一种是linux的命名习惯 student_age str_to_int
另一种是骆驼命名法 studentAge StrToInt

7. 数组

到目前为止,我们已经学习了C语言的基本数据类型:整形、浮点型、字符型。再往后就是复合数据类型。
所谓复合数据类型,是指由简单数据类型,经过一定的数据结构封装,组成而成的新的数据类型。譬如数组、譬如结构体等

7.1 为什么需要数组

数组就是数组成一个组,数就是一个特定数据类型的变量,组就是说好多数放在了一起。

7.2怎么定义数组

数组中的所有元素必须是同一种数据类型,不可能在一个数组中存储两种数据类型的数。

eg: int a[4];
数组中元素类型 数组名[数组元素个数];

7.3 怎么使用数组

数组定义的时候作为整体定义。但是使用的时候不能作为整体使用,使用时必须拆开使用数组中的各个元素。
譬如数组int a[4],使用其中的四个元素,分别用a[0] ~ a[3],其中[]是数组的标志,[]中的数字叫做数组下标(index,索引),下标是我们访问数组中各个元素的指引。下标是0代表数组中第一个元素,如果数组长度为n,下标中最后一个是n-1。访问数组时要特别注意下标,下标是从0开始的,如果下标超出了n-1,会产生越界访问,结果是不可预期的。

7.4 数组的初始化问题

初始化(initinalize,简写为init),是为了让对象有一个预定的初始状态。
譬如说:

7.4.1 (1)简单变量的初始化

当一个局部变量定义时没有初始化,它的值是随机的。这个如果没有注意,可能会导致程序出错。怎么办?解决方案有两个:
第一个,在定义过后明确给它赋值,使用=运算符。
第二个,定义该变量时,同时进行初始化。
总结
1、一般来讲,只要你记得显示赋值,则两种方式并无优劣差异。但是人会犯错,会不小心,所以还是定义同时初始化好一点,因为这个定义的时候就有了固定值,即使之后忘记显示赋值也不会造成结果是随机的。
2、一般情况下,定义的同时都将变量初始化为0。局部变量定义同时初始化为0,这是一个写代码好习惯。

7.4.2 (2)数组的初始化

第一种:完全初始化。依次赋值
eg: int a[4] = {1,2,3,4};
第二种:不完全初始化。初始化式中的值从a[0]开始,依次向后赋值,不足的默认用0填充赋值
eg: int a[7] = {1,2,3,4};

7.5 不同数据类型数组

int a[5];			// 整形数组
float a[5];			// 浮点型数组
double a[5];		// 双精度浮点型数组
char a[5];			// 字符数组

C语言程序中,变量的实质就是内存中的一个格子。当我们定义(创造一个变量)了一个变量后,就相当于在内存中得到了一个格子,这个格子的名字就是变量名,以后访问这个内存格子就使用该变量名就行了。这就是变量的本质。

数据类型的实质是内存中格子的不同种类。譬如在32位机器上
短整形格子(short) 占用2字节空间 16位
整形格子(类型是int)、 占用4字节空间 32位
单精度浮点型格子(float)、 占用4字节空间
双精度浮点型格子(double)、 占用8字节空间 64位
字符型格子(char)。 占用1字节空间 8位

7.6 sizeof运算符

作用:返回一个变量或者一个数据类型的内存占用长度,以字节为单位。

sizeof(a)/sizeof(a[0]) 测试一个数组中究竟有多少个元素

7.7 字符数组及它初始化

char a[5] = {‘a’, ‘b’, ‘c’, ‘d’,‘e’};

基础知识
1、在C语言中引用一个单个字符时,应该用单引号’‘括起来,譬如’a’。
2、定义数组同时初始化,则可以省略数组定义时[]中的长度。C语言编译器会自动推论其长度,推论依据是初始化式中初始化元素的个数。
3、在C语言中引用一个字符串时,应该用"“括起来,譬如"abcde”
"abcde"实际上有6个字符,分别是’a’ ‘b’ ‘c’ ‘d’ ‘e’ ‘\0’

char a[5] = "abcde";
printf("a size is %d\n",a);

‘\0’ 这个字符是ASCII码表中的第一个字符,它的编码值是0,对应的字符是空字符(不可见
字符,在屏幕上看不见,没法显示,一般要用转义字符方式来显示。譬如’\n’表示回车符,’\t’表示Tab,’\0’代表空字符)

‘\0’是C语言中定义的字符串的结尾标志。所以,当c语言程序中使用"abcde"这种方式去初始化时,编译器会自动在字符’e’后面添加一个’\0’。于是乎变成了6个字符。

8. 指针

指针全称是指针变量,其实质是C语言的一种变量。这种变量比较特殊,通常它的值会被赋值为某个变量的地址值(p = &a),然后我们可以使用*p这样的方式去间接访问p所指向的那个变量。

8.1 为什么需要指针

指针存在的目的就是间接访问。有了指针之后,我们访问变量a不必只通过a这个变量名来访问。而可以通过p = &a; *p = xxx;这样的方式来间接访问变量a。

8.2 两种重要运算符:&和*

&:取地址符,将它加在某个变量前面,则组合后的符号代表这个变量的地址值。
eg:

int a; 
int *p; 
p = &a; 

则将变量a的地址值赋值给p。
就在上面的例子中,有以下一些符号:

  • a 代表变量a本身

  • p 代表指针变量p本身

  • &a 代表变量a的地址值

  • *p 代表指针变量p所指向的那个变量,也就是变量a

  • &p 代表指针变量p本身的地址值。符号合法,但对题目无意义

  • *a 把a看作一个指针,*a表示这个指针所指向的变量。该符号不合法

* : 指针符号。指针符号在指针定义和指针操作的时候,解析方法是不同的。
int * p; 定义指针变量p,这里的 * p含义不是代表指针变量p所指向的那个变量,在定义时这里的*含义是告诉编译器p是一个指针。
eg:

int p;		// p是一个整形变量
int *p;		// p是一个指针变量,该指针指向一个整形数

使用指针的时候,*p则代表指针变量p所指向的那个变量。

8.3 指针的定义和初始化

指针既然是一种变量,那么肯定也可以定义,也可以初始化
第一种:先定义再赋值

int *p;		// 定义指针变量p
p = &a;		// 给p赋值	

第二种:定义的同时初始化

int *p = &a;	// 效果等同于上面的两句

8.4 各种不同类型的指针

指针变量本质上是一个变量,指针变量的类型属于指针类型。int *p;定义了一个指针类型的变量p,这个p所指向的那个变量是int型。

int *pInt;				// pInt是指针变量,指向的变量是int类型
char *pChar;			// pChar是指针类型,指向的变量是char类型
float *pFloat;
double *pDouble;

各种指针类型和它们所指向的变量类型必须匹配,否则结果不可预知。

8.5 指针定义的两种理解方法

int * p;
第一种:首先看到p,这个是变量名;其次,p前面有个*,说明这个变量p是一个指针变量;最后,*p前面有一个int,说明这个指针变量p所指向的是一个int型数据。

第二种:首先看到p,这个是变量名;其次,看到p前面的int *,把int *作为一个整体来理解,int *是一种类型(复合类型),该类型表示一种指向int型数据的指针。

总结:第二种方法便于理解,但是不够本质;建议用第一种方法来理解,因为这种思维过程可以帮我们理解更复杂的表达式。

8.6 指针与数组的初步结合

数组名:做右值时,数组名表示数组的首元素首地址,因此可以直接赋值给指针。
如果有 int a[5];
则 a和&a[0]都表示数组首元素a[0]的首地址。
而&a则表示数组的首地址。

注意:数组首元素的首地址和数组的首地址是不同的。前者是数组元素的地址,而后者是数组整体的地址。两个东西的含义不同,但是数值上是相同的。

根据以上,我们知道可以用一个指针指向数组的第一个元素,这样就可以用间接访问的方式去逐个访问数组中各个元素。这样访问数组就有了两种方式。

int a[5];  
int *p; 
p = a;
数组的方式依次访问:a[0] 	a[1]	a[2]	a[3]	a[4]
指针的方式依次访问:*p	  *(p+1)   *(p+2)  *(p+3)	*(p+4)

8.7 指针与++ --符号进行运算

指针本身也是一种变量,因此也可以进行运算。但是因为指针变量本身存的是某个其他变量的地址值,因此该值进行* / %等运算是无意义的。两个指针变量相加本身也无意义,相减有意义。指针变量+1,-1是有意义的。+1就代表指针所指向的格子向后挪一格,-1代表指针所指向的格子向前挪一格。

1、(* p++) 就相当于 * (p++) ,p先与++结合,然后p++整体再与 * 结合.

(* p++)解析:++先跟p结合,但是因为++后置的时候,本身含义就是先运算后增加1(运算指的是p++整体与前面的 * 进行运算;增加1指的是p+1),所以实际上 * p++符号整体对外表现的值是 * p的值,运算完成后p再加1.
所以*p++等同于:*p; p += 1;

2、*++p等同于 p += 1; *p;

3、( * p)++,使用()强制将 * 与p结合,只能先计算 * p,然后对*p整体的值++。

4、++( * p),先*p取值,再前置++,该值+1后作为整个表达式的值。

总结:++符号和指针结合,总共有以上4种情况。–与++的情况很类似。

8.8 函数传参中使用指针

int add(int a, int b) 函数传参使用了int型数,本身是数值类型。实际调用该函数时,实参将自己拷贝一份,并将拷贝传递给形参进行运算。实参自己实际是不参与的。
所以,在函数中,是没法改变实参本身的。
我们通过如下的案例可以更加深入了解函数传参中使用指针
经典案例:两个数交换
程序源码

# include 
int main (void)

{
	int a = 12;
	int b = 34;
	printf ("before swap a = %d , b = %d\n " ,a , b );
	swap(a,b);             //不能实现交换
	printf ("after swap a = %d , b = %d\n " ,a , b );
	swap_pointer(&a,&b);    //可以实现两个数的交换。
	printf ("after swap a = %d , b = %d\n " ,a , b );
	return 0;
}

//使用指针变量进行传参
int swap_pointer(int *p1,int *p2)

{
	int temp;
	temp = *p1;
	*p1= *p2 ;
	*p2 = temp ;
	
}

//传值调用子函数
int swap(x,y)
{
	int temp;
	temp = x;
	x = y ;
	y = temp ;
	
}

实际运行结果

before swap a = 12 , b = 34
 after swap a = 12 , b = 34
 after swap a = 34 , b = 12

结果分析
通过实际调试我们发现子函数swap(x,y)并没有实现两个数的交换,因为在C语言中实参(a,b)传给形参(x,y),是传值调用,意思就是a和b分别将自己的值复制了一份给x,y。x,y利用swap(x,y)函数实现了交换,但是没有办法返回去改变实参a和b的值。
我们使用指针变量实际访问的是实参(a,b)的地址,所以在swap_pointer(int *p1,int *p2)中可以对其值进行更改。

9. 结构体、宏定义、共用体、枚举

9.1 结构体

9.1.1 为什么需要结构体

没有结构体之前,在C语言中,数据的组织依靠:变量+数组。
最初最简单的时候,只需要使用基本数据类型(int char float double)来定义单个变量,需要几个变量就定义几个。
后来情况变复杂了,有时需要很多意义相关的变量(譬如需要存储及运算一个班级的学生分数)这时候数组出现了。数组解决了需要很多类型相同、意义相关的变量的问题。
但是数组是有限制的。数组最大的不足在于,一个数组只能存储很多个数据类型相同的变量。
所以碰到需要封装几个类型不同的变量的时候,数组就无能为力。
譬如对于题目:使用一个数据结构来保存一个学生的所有信息:姓名 学号 性别
这时候就需要结构体。

9.1.2 什么是结构体

结构体是一个集合,集合中包含很多个元素,这些元素的数据类型可以相同,也可以不相同。所以结构体是一种数据封装的方法。结构体存在的意义就在于,把很多数据类型不相同的变量封装在一起,组成一个大的新的数据类型。

数据结构:把庞大复杂的数据用一定的方式组织管理起来,便于操作(查找,增加,删除等)这就叫数据结构。

9.1.3 结构体和数组的关联

数组是一种特殊的结构体,特殊之处在于封装内的各个元素类型是相同的。结构体和数组都是对一些子元素的封装,因此定义的时候都是封装作为整体定义,但是使用的时候,都是使用封装中的子元素。一般结构体变量和数组变量都不会作为一个整体操作。

9.1.4 使用结构体的步骤

第一步:定义结构体类型。结构体类型的定义是在函数外面(函数外面 == 全局)的。
第二步:使用第一步定义的类型来定义结构体变量。
第三步:使用变量。实际上使用结构体变量的时候,使用的是结构体变量中封装的各个子元素,而不是结构体变量本身。

9.1.5 结构体的初始化

结构体变量和普通变量一样,作为局部变量时,如果定义的时候无初始化也无显式赋值,则结构体变量中的子元素的值是随机的。

发现2种C语言接受的结构体初始化方式。
第一种,完全初始化。{xx, xx, xx, xx, xx};
第二种,部分初始化。
{
.a = xx,
.b = xx,
.c = xx,
.d = xx,
};

eg:


#include

//定义结构体类型
struct my_struct
{
	char name[10];
	int age;
	char phone_num[15];
	
};

int main ()
{
	struct my_struct s;      //定义结构体变量
	s.name[0] = 'd';
	s.name[1] = 'o';
	s.name[2] = 'n';
	s.name[3] = 'k';
	s.name[4] = 'e';
	s.name[5] = '\0';
	s.age = 27;
	s.phone_num[0] = '1';
	s.phone_num[1] = '3';
	s.phone_num[2] = '8';
	s.phone_num[3] = '6';
	s.phone_num[4] = '7';
	s.phone_num[5] = '5';
	s.phone_num[6] = '2';
	s.phone_num[7] = '4';
	s.phone_num[8] = '6';
	s.phone_num[9] = '4';
	s.phone_num[10] = '9';
	s.phone_num[11] = '\0';

	printf ("name =%s , age = %d, phone_num = %s\n"
	,s.name,s.age,s.phone_num);
	
	return 0;
}

执行结果

name =donke , age = 27, phone_num = 13867524649

9.2 宏定义

#define N (321)			//宏定义的格式
9.2.1 宏定义要注意的问题

1、宏定义一般是在函数的外面
2、宏定义必须要先定义,再使用宏。如果先使用就会编译报错。
3、宏定义中宏名一般用大写。不是语法规定的,是一般约定俗成的。

9.2.2 为什么使用宏定义

在C语言中,一般使用常数的时候,都不是直接使用,而是先把该常数定义为一个宏,然后在程序中使用该宏名。这样做的好处是,等我们需要修改这个常数时,只需要在宏定义处修改一次即可。而不用到代码中到处去寻找,看哪里都用过该常数。

9.2.3 经典的宏定义案例
//这是一个经典的宏定义案例,表示一年中有多少秒,后面的UL
//表示unsigned long ,防止数据溢出
#define SEC_PER_YEAR (365 * 24 * 60 * 60)UL

9.3 共用体(union,联合,联合体)

共用体union在定义和使用形式上,和结构体struct很相似。但是两种数据结构是完全不同的两类东西。
结构体,是对多个数据的组合与封装。
共用体,共用体中只有一个东西,只是它被好几个名字(和类型)共用。

#include 

union my_union
{
	int a ;
	char c;
	
};

int main ()
{
	union my_union u;
	u.a = 45;
	printf ("u.a = %d\n u.b = %d\n", u.a,u.c);
	return 0;
}

执行结果

u.a = 45
 u.b = 45

9.4 枚举

枚举(enum)
枚举在C/C++/c#中,是一个被命名的整型常数的集合, 枚举在日常生活中很常见。
例如表示星期的SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,SATURDAY, 就是一个枚举。
枚举的说明与结构和联合相似, 其形式为:

enum 枚举名
{
标识符[=整型常数],
标识符[=整型常数],
...
标识符[=整型常数],
} 枚举变量;

注:以上内容来自朱老师物联网大讲堂之C语言基础篇

你可能感兴趣的:(c语言学习)