• 编译环境:Visual Studio 2012
  • 编程语言: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);
}

C语言知识点的实例的更多相关文章

  1. [iOS]C语言知识点系列视频

    C语言知识点系列视频 目录 C语言技术视频-01-变量的定义 C语言技术视频-02-程序分支结构(if...else) C语言技术视频-03-程序分支结构(switch) C语言技术视频-04-程序循 ...

  2. linux下C语言多线程编程实例

    用一个实例.来学习linux下C语言多线程编程实例. 代码目的:通过创建两个线程来实现对一个数的递加.代码: //包含的头文件 #include <pthread.h> #include ...

  3. UML标准建模语言与应用实例

    一.基本信息 标题:UML标准建模语言与应用实例 时间:2012 出版源:科技创新导报 领域分类:UML标准建模语言 面向对象 系统分析与设计 二.研究背景 问题定义:UML建模语言用图形来表现典型的 ...

  4. C语言知识点复习梳理

    C语言知识点复习梳理 C语言的知识点讲完了,接下来就是做一下整理与总结,然后就会进入其他知识的学习. 本文目录如下: 基础知识. 顺序程序设计. 数据类型. 标准输入输出. 进制转换. 选择结构. 循 ...

  5. C语言知识点总结

    本文采用思维导图的方式撰写,更好的表述了各知识点之间的关系,方便大家理解和记忆.这个总结尚未包含C语言数据结构与算法部分,后续会陆续更新出来,文中有漏掉的知识点,还请大家多多指正.

  6. C语言 知识点总结完美版

    本文采用思维导图的方式撰写,更好的表述了各知识点之间的关系,方便大家理解和记忆.这个总结尚未包含C语言数据结构与算法部分,后续会陆续更新出来,文中有漏掉的知识点,还请大家多多指正. 总体上必须清楚的: ...

  7. c++语言知识点汇总

    c++ primer version-5 的整理 section 1: 内置类型和自定义类型: main函数的返回值:指示状态.0:成功:1:系统定义. unix和win系统中,执行完程序可以使用ec ...

  8. 【Android 多语言切换简单实例分享】

    一.Android多语言切换 Android应用的开发不可能仅仅针对某一个国家或者区域使用,各国间语言文化各不同样,因此一个优秀的APP必须支持多种语言,为了实现这个特性,Android给出了一个解决 ...

  9. (转)Objective-C语言--属性和实例变量

    本文转自http://blog.csdn.net/addychen/article/details/39525681 使用Objective-C一段时间了,一直没有弄清楚在Objective-C中属性 ...

随机推荐

  1. Docker Explore the application

    https://docs.docker.com/docker-for-mac/#explore-the-application   Open a command-line terminal and t ...

  2. 使用污点分析检查log4j问题

    摘要:log4j问题的余波还在继续,为什么这个问题潜伏了这么长时间,大家一直没有发现?这里从静态分析的角度谈下log4j问题的发现. 本文分享自华为云社区<使用污点分析检查log4j问题> ...

  3. Spring Security探究之路之开始

    前言 在Spring Security介绍中,我们分析到了根据请求获取匹配的SecurityFilterChain,这个类中包含了一组Filter 接下来我们从这些Filter开始探究之旅 Sprin ...

  4. Solution -「AGC 004E」「AT 2045」Salvage Robots

    \(\mathcal{Description}\)   Link.   有一个 \(n\times m\) 的网格.每个格子要么是空的,要么有一个机器人,要么是一个出口(仅有一个).每次可以命令所有机 ...

  5. 走进Task(2):Task 的回调执行与 await

    目录 前言 Task.ContinueWith ContinueWith 的产物:ContinuationTask 额外的参数 回调的容器:TaskContinuation Task.Continue ...

  6. Redis 源码简洁剖析 13 - RDB 文件

    RDB 是什么 RDB 文件格式 Header Body DB Selector AUX Fields Key-Value Footer 编码算法说明 Length 编码 String 编码 Scor ...

  7. 微服务从代码到k8s部署应有尽有系列(五、民宿服务)

    我们用一个系列来讲解从需求到上线.从代码到k8s部署.从日志到监控等各个方面的微服务完整实践. 整个项目使用了go-zero开发的微服务,基本包含了go-zero以及相关go-zero作者开发的一些中 ...

  8. 查看树莓派系统相关信息的shell代码

    一.系统信息 1.显示系统名.系统版本和cpu架构等 在命令行中输入下面的指令 uname -a 2.系统位数 在命令行中输入下面的指令 getconf LONG_BIT 如图,显示多少就是多少位 3 ...

  9. ios cannot use "@throw" with objective-c exceptions disabled 问题解决方案

    http://blog.csdn.net/huayu_huayu/article/details/51781182 我不转载  我跳转 哈哈哈哈哈哈   其实也就是 解决办法:修改target -&g ...

  10. ClickHouse在大数据领域应用实践

    一.序言 面向大数据量查询数据库,优点是在较大数据量(千万级)的前提下具有较好的查询性能. 1.应用场景 ClickHouse应用于OLAP(在线分析处理)领域,具体来说满足如下特点使用此技术比较合适 ...