C语言实例

  • 编译环境:Visual Studio 2012
  • 编程语言:C

目录

  • 1、memcpy与'/0'
  • 2、volatile的使用
  • 3、数字转字符
  • 4、memcpy len 与指针加减 len 的区别
  • 5、sizeof(结构体) 的计算 (结构体对齐)
  • 6、回调函数
  • 7、双向链表
  • 8、for循环赋值:用二维数组与指针

1、memcpy与’/0’

int main(void) {
  char* p1 = "abc";
  char* p2 = (char*)malloc(sizeof(char) * 3);
  char* p3 = (char*)memcpy(p2, p1, 3);
  printf("p2 = %s\np3 = %s\n", p2, p3);
  free(p2);
  p2 = NULL;
  p3 = NULL;
  system("pause");
  return 1;
}

打印结果:乱码

p2 = abc?
p3 = abc?
请按任意键继续. . .


int main(void) {
  char* p1 = "abc";
  char* p2 = (char*)malloc(sizeof(char) * 4);
  char* p3 = (char*)memcpy(p2, p1, 4);
  printf("p2 = %s\np3 = %s\n", p2, p3);
  free(p2);
  p2 = NULL;
  p3 = NULL;
  system("pause");
  return 1;
}

打印结果:正常

p2 = abc
p3 = abc
请按任意键继续. . .

2、volatile的使用

  1. 并行设备的硬件寄存器(如:状态寄存器)

  2. 一个中断服务子程序中会访问到的非自动变量(如下例子)

  3. 多线程应用中被几个任务共享的变量

int time = 0;
int main()
{
	if(time == 10)
	{
		dothing();//不会正确执行
	}
}
void isr_tick()
{
	time = 10;
}

例子中dothing()可能永远不会执行,一直使用的是“tme = 0”的副本i,不是读取i原始地址的值;time在中断中改变后main函数不会读取到。

有位博主说:volatile应该解释为“直接从原始内存地址读值”比较合适,“易变的”这种解释有点误导人。这个理解不错。

3、数字转字符

void itoa ( int n,char s[])
{
	int i,j,sign;//数字太大要使用unsigned long
	char tmp;
	
	if((sign=n)<0)//负数时使用
	{
		n=-n;
	}

	i=0;
	do
	{
		s[i++]=n%10+'0';//从个位开始 ,循环取个位 十位 百位...放进s[0] s[1] s[2]
	}
	while ((n/=10)>0);

	if(sign<0)  //负数时使用
	{
		s[i++]='-';
	}
	s[i]='\0';  //字符串要加终止符

	if(i <= 0) return;
	for(j=0;j<=(i/2);j++)//FROM 0~(i/2),之前是倒序,这里再逆序排一遍
	{
		tmp = s[j];
		s[j] = s[i-j-1];
		s[i-j-1] = tmp; 
	}
}

4、memcpy len 与指针加减 len 的区别

定义int a2[ ],则a2[ ]中的每个元素占一个int,一般机器上也就是4个字节。
而memcpy第三个参数为字节数,所以:
memcpy(a1,a2, sizeof(int) * 10 ); //复制了40个字节,赋值正确
memcpy(a1,a2,10); //只复制了10个字节,赋值异常

void main(void) {
	int i = 0;
	int a2[] = {1,2,3,4,5,6,7,8,9,10};
	int a1[10];
	memcpy(a1,a2, sizeof(int)*10);
	while(i<10)
	{
		printf("%d\n",a1[i]);
		i++;
	}
}

自定义的格式也要用sizeof():

	typedef unsigned short uint16;
	
	uint16 a2[] = {1,2,3,4,5,6,7,8,9,10};
	uint16 a1[10];
	memcpy(a1,a2, sizeof(uint16 )*10);

这里再提一下指针的加减,[a2 + 2]不是代表a2后移多少个字节,而是代表指针后移两个元素,即*a1 = a2[2] = 3。

	uint16 a2[] = {1,2,3,4,5,6,7,8,9,10};
	uint16 *a1;
	a1 = a2 + 2; 
	printf("%d\n",*a1);

下面示例可以帮助理解以上内容:
eg:把二维数组第0行100个元素拷贝给a0,第1行100个元素拷贝给a1:

	uint16 a[2][100] =;
	uint16 a0[100];
	uint16 a1[100];
	//把二维数组第0行100个元素拷贝给a0:
	memcpy(a0,a, sizeof(uint16 )*100);//此时指针a指向a[0][0]元素--//sizeof(uint16 )*100代表字节数
	//把二维数组第1行100个元素拷贝给a1:
	a = a + 100;//此时指针a指向a[1][0]元素--//100代表元素数
	memcpy(a1,a, sizeof(uint16 )*100);

5、sizeof(结构体) 的计算 (结构体对齐)

编译器在编译程序时会遵循以下原则:
(1)结构体变量中成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍)
(2)结构体大小必须是所有成员大小的整数倍,也即所有成员大小的公倍数。
(3)结构体大小等于最后一个成员的偏移量加上其字节大小。

如下: 表示结构体元素的实际偏移量, 之前的数表示填充, 之后的数表示元素占用:

typedef struct _hello
{
	unsigned short b;//(2)  offset:0√ 1
	unsigned int c;//(4) offset:2 3 4√ 5 6 7
	char a;//(1) offset:8√
	//结构体大小=8+1=9,不符合:10 11 12√
}hello;

typedef struct _hello1
{
	unsigned int c;//(4) offset:0√ 1 2 3
	unsigned short b;//(2)  offset:4√ 5
	char a;//(1) offset:6√
	//结构体大小=6+1=7,不符合:8√
}hello1;

typedef struct _hello2
{
	char a;//(1) offset:0√
	unsigned short b;//(2)  offset:1 2√ 3
	unsigned int c;//(4) offset:4√ 5 6 7
	//结构体大小=4+4=8√
}hello2;

typedef struct _hello3
{
	char a;//(1) offset:0√
	unsigned short b;//(2)  offset:1 2√ 3
	unsigned int c;//(4) offset:4√ 5 6 7
	unsigned int d;//(4) offset:8√ 9 10 11
	unsigned short e;//(2)  offset:12√ 13
	char f;//(1) offset:14√ 
	char g;//(1) offset:15√
	unsigned short h;//(2)  offset:16√ 17
	unsigned int i;//(4) offset:18 19 20√ 21 22 23
	//结构体大小=20+4=24√
}hello3;

void main(void) {
	printf("hello = %d\n",sizeof(hello));
	printf("hello1 = %d\n",sizeof(hello1));
	printf("hello2 = %d\n",sizeof(hello2));
	printf("hello3 = %d\n",sizeof(hello3));
}

输出结果如下:

hello = 12
hello1 = 8
hello2 = 8
hello3 = 24

6、回调函数

@斗趣:
“ 以上回答都对,但是个人觉得非常有必要再做点补充。回调函数跟普通函数没有任何区别。只是在调用函数时略有区别。一般调用普通函数时,直接写函数名即可。但是在调用所谓“回调”函数(这个名字逼格相当的高)时,是把它(或者说它的指针)作为参数传递给另一函数。关键就在于“参数”这两个字。为什么要把某个东西参数化?只要写过一点点程序的人都知道,道理很简单,就是它存在变化的可能性。既然可以把变量做成参数,那么函数也同样可以做成参数,只要它们有变化的可能。对于一成不变的东西,显然直接嵌入便可。 ”

把函数A(的指针)作为参数传入另一个函数B,A就是回调函数。

typedef  void (*funcpointer)(char*) ;  //funcpointer就是我们自定义的【函数指针】类型

void callbackfunc(char* ch) //回调函数
{
   static int usedtimes = 0;
   usedtimes ++;
   printf("%s\n",ch);
}
 
void directfunc(char *s,funcpointer a) //直接调用的函数
{
	a(s);
	printf("直接使用directfunc,directfunc通过函数指针回调callbackfunc\n");
}

void directfunc2(funcpointer a) //直接调用的函数
{
	char *data = "new directfunc running";
	a(data);
	printf("可以用不同的函数 调用 同一个回调函数\n");
}
//main:起始函数--应用层函数:固定调用中间函数
//directfunc:中间函数--应用层函数或者库函数:通过不同的回调函数实现不同的功能
//callbackfunc:回调函数--应用层函数
int main(void) {
	char *s1 ="hello world";
	char *s2 ="p hello world";
	directfunc(s1,callbackfunc);
	directfunc(s2,&callbackfunc);//这里也证明【函数名】也是【指针】
	directfunc2(callbackfunc);
	directfunc2(&callbackfunc);
}

运行结果:

hello world
直接使用directfunc,directfunc通过函数指针回调callbackfunc
p hello world
直接使用directfunc,directfunc通过函数指针回调callbackfunc
new directfunc running
可以用不同的函数 调用 同一个回调函数
new directfunc running
可以用不同的函数 调用 同一个回调函数
请按任意键继续. . .

第一个函数callbackfunc:回调函数,我们写好这个函数后不会直接去调用它,只会使用它的函数指针(callbackfunc或者&callbackfunc)。
第二个函数directfunc:直接使用的函数,我们把回调函数的指针传给它,它自己去调用回调函数。
第三个函数directfunc2:直接使用的函数,我们把回调函数的指针传给它,它自己去调用回调函数。

当然,所有的函数具体要执行什么功能都是程序员自己写的。回调的好处是这两个函数可以由不同的程序员在不同的时空去写,写好了只需告知回调函数的接口就行。

7、双向链表

typedef struct _pnode_
{
	int data;
	struct _pnode_ *pre;
	struct _pnode_ * next;
} pnode;
int main(void) {
	int i=0;
	pnode *p1;
	pnode *p2;
	pnode *p3;
	pnode *pp;

	p1 = (pnode *)malloc(sizeof(pnode));
	p2 = (pnode *)malloc(sizeof(pnode));
	p3 = (pnode *)malloc(sizeof(pnode));
	pp = (pnode *)malloc(sizeof(pnode));

	p1->data = 1;
	p2->data = 2;
	p3->data = 3;
	//p1 p2 p3
	p1->next = p2;
	p1->pre  = p3;

	p2->next = p3;
	p2->pre  = p2;

	p3->next = p1;
	p3->pre  = p2;
	
	pp = p1;
	for(i=0;i<3;i++)
	{
		if(pp->data == 2)
		{
			printf("2\n");
			break;
		}
		else
		{
			pp = pp->next;
			printf("no 2\n");
		}
	}
}

打印结果

no 2
3
请按任意键继续. . .

8、for循环赋值:用二维数组与指针

#define WIDTH 12
#define HEIGHT 10
typedef unsigned long uint32_t;
typedef unsigned short uint16_t;
void main(void) 
{
	uint16_t shuzu[10][12]; //10行 12列 的数组
	uint16_t X = 0;
	uint16_t Y = 0;
	uint16_t *ps;
	uint16_t ONE[12]={1,2,3,4,5,6,7,8,9,10,11,12};
	uint16_t TWO[12]={100,100,100,100,100,100,100,100,100,100,100,100};

	memset(&shuzu[0][0], 0, sizeof(uint16_t)*120);

	ps = &shuzu[0][0];
//方法一:memcpy,按行拷贝数据 进行赋值;
//方法二:因为二维数组地址连续,所以可用一级指针ps 进行赋值;
//方法三:直接对地址 进行赋值;
//方法四:二维数组方式 进行赋值
	for(Y=0; Y<10; Y++)
	{
		if((Y%2) == 0)
		{
			//memcpy((&shuzu[0][0] + WIDTH*Y ), ONE,sizeof(uint16_t)*12);//方法一
			for(X=0; X<12; X++)
			{
				ps[Y*WIDTH + X] =X+1;//方法二
				//*(&shuzu[0][0] + WIDTH*Y + X)= X+1;//方法三
				//shuzu[Y][X] = X+1;//方法四
			}
		}
		else
		{
			//memcpy((&shuzu[0][0] + WIDTH*Y ), TWO,sizeof(uint16_t)*12);//方法一
			for(X=0; X<12; X++)
			{
				ps[Y*WIDTH + X] =100;//方法二
				//*(&shuzu[0][0] + WIDTH*Y + X)= 100;//方法三
				//shuzu[Y][X] = 100;//方法四
			}
		}
	}

	for(Y=0; Y<10; Y++)
	{
		for(X=0; X<12; X++)
		{
			printf("%d\t",*(ps + WIDTH*Y + X));//使用方法三,进行打印输出
		}
		printf("\n");
	}
	printf("end of shuzu\n");
printf("\n");

打印结果:

1       2       3       4       5       6       7       8       9       10      11      12
100     100     100     100     100     100     100     100     100     100     100     100
1       2       3       4       5       6       7       8       9       10      11      12
100     100     100     100     100     100     100     100     100     100     100     100
1       2       3       4       5       6       7       8       9       10      11      12
100     100     100     100     100     100     100     100     100     100     100     100
1       2       3       4       5       6       7       8       9       10      11      12
100     100     100     100     100     100     100     100     100     100     100     100
1       2       3       4       5       6       7       8       9       10      11      12
100     100     100     100     100     100     100     100     100     100     100     100
end of shuzu

请按任意键继续. . .

你可能感兴趣的:(C语言)