C语言:知识点及实例

@

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

请按任意键继续. . .

9、C语言地址增加规律

typedef unsigned long uint32_t;
typedef unsigned short uint16_t;
typedef unsigned char uint8_t;

int main(void) {

	uint32_t a[3]={1,2,3};
	uint16_t b[3]={0,1,2};
	uint8_t  c[3]={0,1,2};
	
	printf("0x%08x\n",&a[0]);
	printf("0x%08x\n",&a[1]);
	printf("0x%08x\n",&a[2]);
	
	printf("0x%08x\n",&b[0]);
	printf("0x%08x\n",&b[1]);
	printf("0x%08x\n",&b[2]);
	
	printf("0x%08x\n",&c[0]);
	printf("0x%08x\n",&c[1]);
	printf("0x%08x\n",&c[2]);
}

结果如下:每次结果值不同,但是差值是相同的

0x1a9ffe68
0x1a9ffe6c
0x1a9ffe70

0x1a9ffe58
0x1a9ffe5a
0x1a9ffe5c

0x1a9ffe4c
0x1a9ffe4d
0x1a9ffe4e
请按任意键继续. . .

即:地址是按字节增加的,初始地址为addr,则
写一个32位(4字节)的数据,addr+4
写一个16位(2字节)的数据,addr+2
写一个8位(1字节)的数据 , addr+1

10、.H文件的宏定义

为了防止一个.h文件被多次包含,导致重复声明等,需要让头文件只编译一次,使用如下代码:

#ifndef _FILE_NAME_H_
#define _FILE_NAME_H_   
...
#endif

11、参数传递分为传送值和传送地址

传送值:void change(int a,int b)
操作a、b,是操作形参,不会影响原函数的a、b!

传送地址(指针):void change(int a,int b)
操作(
a),是操作形参地址指向的内容,因为实参和形参地址一样,所以改变(
a),实参也会变。
但是,如果是操作(a),还是在操作形参,原函数的指针a地址不变!!!!!!!

12、解析C语言的复杂声明

以前学习的这篇文章:https://segmentfault.com/a/1190000000505065

总体规则:①括号内优先解析。②先右后左解析。
不管多复杂的声明都这样一步步来就可以解析出来了。

示例:void ((checkout)[])();

第一步:

(*checkout)

checkout是一个指针。

第二步:

(*checkout)[]

checkout是一个指针,指向一个数组。

第三步:

(checkout)[]

checkout是一个指针,指向一个数组,每个数组元素都是一个指针。

第四步:

((checkout)[])()

checkout是一个指针,指向一个数组,每个数组元素都是一个指向函数的指针。

第五步:

void ((checkout)[])()

checkout是一个指针,指向一个数组,每个数组元素都是一个指向返回void的函数的指针。

PS:声明的checkout的最终定义就是一个指针,第一步就确定了。
——————————————————————————————————————
一、
[C语言例题]
用变量a 给出下面的定义:
a) 一个有10个指针的数组,每个指针都指向一个整型数;
b) 一个指向有10个整型数的数组的指针;
c) 一个指向函数的指针,该函数有一个整型参数并返
回一个整型数;
d) 一个有10个指针的数组,该指针指向一个函数,该
函数有一个整型参数并返回一个整型数;

[标准答案]

a) int * a[10] //a是一个数组

b) int (*a)[10] //a是一个指针

c) int (*a)(int) //a是一个指针

d) int (*a[10])(int) //a是一个数组

二、
定义一个类型的函数指针:

typedef void (*Xil_InterruptHandler)(void *data);
//Xil_InterruptHandler是一个函数指针,指向的函数 形参为空指针类型,返回值为空。

一个符合上述定义的实际函数:

void XAxiVdma_ReadIntrHandler(void * InstancePtr)
{
	//...
}

另一个实际函数,将函数指针作为了形参:

void  Func_B(Xil_InterruptHandler Xhandler, void *a, int b,void *c)
{
    //...
}

void main()
{
	void *a = NULL;
	int   b = 18;
	void *c = NULL;
	
    Func_B(XAxiVdma_ReadIntrHandler, a, b,c);
}

13、字符串转整数atoi()

最简单情况下的例子:输入字符串,输出无符号十进制整数:

unsigned long myatoi(char *ptr)
{
	int num = 0;
	while(*ptr == NULL)  //null等于数字0,这句话等效于 while(*ptr == ' ')
	{
		ptr++;
	}
	while((*ptr >='0')&&(*ptr<='9'))
	{
		num = num*10 + (*ptr-'0') ;
		ptr++;
	}
	return num;
}

int main(void) 
{
  char *str="0010806010";
  unsigned long  q;
  q=myatoi(str);
  printf("q= %d\n",q);
}

网友写的带符号和判断的方法:

#include <stdio.h>                                                                                                                            
#include <stdbool.h>
  
int my_atoi(const char *src)
{
      int s = 0;
      bool isMinus = false;
  
      while(*src == ' ')  //跳过空白符
      {
          src++; 
      }
  
      if(*src == '+' || *src == '-')
      {
          if(*src == '-')
          {
              isMinus = true;
          }
          src++;
      }
      else if(*src < '0' || *src > '9')  //如果第一位既不是符号也不是数字,直接返回异常值
      {
          s = 2147483647;
          return s;
      }
  
      while(*src != '\0' && *src >= '0' && *src <= '9')
      {
          s = s * 10 + *src - '0';
          src++;
      }
      return s * (isMinus ? -1 : 1);
 }

 int main()
 {
      int num;
  
      char *str = "a123456";
      num = my_atoi(str);
      printf("atoi num is: %d \r\n", num);
  
      return 0;
 }

14、整数转字符串itoa()

#include <stdio.h>
#include <ctype.h>

//整数的各位数字加‘0’转换为char型并保存到字符数组中                                                                                           
int itoa(int n, char s[])
{
  int i;
  int j;
  int sign;

  sign = n;    //记录符号
  if(sign < 0)
  {
     n = -n;  //变为正数处理 
  }

   i = 0;
  do{
     s[i++] = n % 10 + '0';  //取下一个数字
  }while((n /= 10) > 0);
  
  if(sign < 0 )
  {
    s[i++] = '-';
    s[i] = '\0';
  }

  for(j = i; j >= 0; j-- )
  {
	printf("%c \r\n", s[j]);
  }
  return 0;
}

int main()
{
  int n;
  char s[20];

  n = -688;
  itoa(n, s);
  return 0;
}

15、getmemery返回内存

//错误:把字符串常量拷贝到临时变量,返回时p被释放
char *getmemery()
{
	char p[] = "hello world!";
	return p;
}

//正确:p指向字符串常量的首地址,把首地址返回
char *getmemery()
{
	char *p = "hello world!";
	return p;
}


main()
{
	char *str = NULL;
	str = getmemery();
	printf("%s\n",str);
}
void getmemery(char **p)
{
 	*p = (char *)malloc(100);
}
 
main()
{
 	char *str = NULL;
 	getmemery(&str);
 	strcpy(str,"hello world!");
 	printf("%s\n",str);
}

16 各种类型的打印

image

posted @ 2022-02-24 19:01  solonj  阅读(162)  评论(0编辑  收藏  举报