C语言知识点的实例
- 编译环境: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的使用
并行设备的硬件寄存器(如:状态寄存器)
一个中断服务子程序中会访问到的非自动变量(如下例子)
多线程应用中被几个任务共享的变量
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
0x1a9ffe700x1a9ffe58
0x1a9ffe5a
0x1a9ffe5c0x1a9ffe4c
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语言知识点的实例的更多相关文章
- [iOS]C语言知识点系列视频
C语言知识点系列视频 目录 C语言技术视频-01-变量的定义 C语言技术视频-02-程序分支结构(if...else) C语言技术视频-03-程序分支结构(switch) C语言技术视频-04-程序循 ...
- linux下C语言多线程编程实例
用一个实例.来学习linux下C语言多线程编程实例. 代码目的:通过创建两个线程来实现对一个数的递加.代码: //包含的头文件 #include <pthread.h> #include ...
- UML标准建模语言与应用实例
一.基本信息 标题:UML标准建模语言与应用实例 时间:2012 出版源:科技创新导报 领域分类:UML标准建模语言 面向对象 系统分析与设计 二.研究背景 问题定义:UML建模语言用图形来表现典型的 ...
- C语言知识点复习梳理
C语言知识点复习梳理 C语言的知识点讲完了,接下来就是做一下整理与总结,然后就会进入其他知识的学习. 本文目录如下: 基础知识. 顺序程序设计. 数据类型. 标准输入输出. 进制转换. 选择结构. 循 ...
- C语言知识点总结
本文采用思维导图的方式撰写,更好的表述了各知识点之间的关系,方便大家理解和记忆.这个总结尚未包含C语言数据结构与算法部分,后续会陆续更新出来,文中有漏掉的知识点,还请大家多多指正.
- C语言 知识点总结完美版
本文采用思维导图的方式撰写,更好的表述了各知识点之间的关系,方便大家理解和记忆.这个总结尚未包含C语言数据结构与算法部分,后续会陆续更新出来,文中有漏掉的知识点,还请大家多多指正. 总体上必须清楚的: ...
- c++语言知识点汇总
c++ primer version-5 的整理 section 1: 内置类型和自定义类型: main函数的返回值:指示状态.0:成功:1:系统定义. unix和win系统中,执行完程序可以使用ec ...
- 【Android 多语言切换简单实例分享】
一.Android多语言切换 Android应用的开发不可能仅仅针对某一个国家或者区域使用,各国间语言文化各不同样,因此一个优秀的APP必须支持多种语言,为了实现这个特性,Android给出了一个解决 ...
- (转)Objective-C语言--属性和实例变量
本文转自http://blog.csdn.net/addychen/article/details/39525681 使用Objective-C一段时间了,一直没有弄清楚在Objective-C中属性 ...
随机推荐
- Docker Explore the application
https://docs.docker.com/docker-for-mac/#explore-the-application Open a command-line terminal and t ...
- 使用污点分析检查log4j问题
摘要:log4j问题的余波还在继续,为什么这个问题潜伏了这么长时间,大家一直没有发现?这里从静态分析的角度谈下log4j问题的发现. 本文分享自华为云社区<使用污点分析检查log4j问题> ...
- Spring Security探究之路之开始
前言 在Spring Security介绍中,我们分析到了根据请求获取匹配的SecurityFilterChain,这个类中包含了一组Filter 接下来我们从这些Filter开始探究之旅 Sprin ...
- Solution -「AGC 004E」「AT 2045」Salvage Robots
\(\mathcal{Description}\) Link. 有一个 \(n\times m\) 的网格.每个格子要么是空的,要么有一个机器人,要么是一个出口(仅有一个).每次可以命令所有机 ...
- 走进Task(2):Task 的回调执行与 await
目录 前言 Task.ContinueWith ContinueWith 的产物:ContinuationTask 额外的参数 回调的容器:TaskContinuation Task.Continue ...
- Redis 源码简洁剖析 13 - RDB 文件
RDB 是什么 RDB 文件格式 Header Body DB Selector AUX Fields Key-Value Footer 编码算法说明 Length 编码 String 编码 Scor ...
- 微服务从代码到k8s部署应有尽有系列(五、民宿服务)
我们用一个系列来讲解从需求到上线.从代码到k8s部署.从日志到监控等各个方面的微服务完整实践. 整个项目使用了go-zero开发的微服务,基本包含了go-zero以及相关go-zero作者开发的一些中 ...
- 查看树莓派系统相关信息的shell代码
一.系统信息 1.显示系统名.系统版本和cpu架构等 在命令行中输入下面的指令 uname -a 2.系统位数 在命令行中输入下面的指令 getconf LONG_BIT 如图,显示多少就是多少位 3 ...
- ios cannot use "@throw" with objective-c exceptions disabled 问题解决方案
http://blog.csdn.net/huayu_huayu/article/details/51781182 我不转载 我跳转 哈哈哈哈哈哈 其实也就是 解决办法:修改target -&g ...
- ClickHouse在大数据领域应用实践
一.序言 面向大数据量查询数据库,优点是在较大数据量(千万级)的前提下具有较好的查询性能. 1.应用场景 ClickHouse应用于OLAP(在线分析处理)领域,具体来说满足如下特点使用此技术比较合适 ...