3.14e7是3.14×10的7次方,e代表10,使用的科学计数法
把一个数表示成a与10的n次幂相乘的形式(1≤|a|<10,a不为分数形式,n为整数),这种记数法叫做科学记数法
在计算机中负数是以补码的形式存储,并且符号位的1也是要计算的
在Ubuntu中使用math.h ,要在编译时加上 -lm
pow 函数
pow(x,y)
计算x的y次幂。
返回值:返回幂指数的结果,即x的y次幂。若x为负数且y为小数,或者x为0且y小于等于0,将出现结果错误。
数据的二进制第六位和六位不相同,第六位是从二进制的0位开始计算,而六位是按数数从1开始
1&?=?
0&?=0
qword全称是Quad Word。2个字节就是1个Word(1个字,16位),q就是英文quad-这个词根(意思是4)的首字母,所以它自然是word(2字节,0~2^16-1)的四倍,8字节,0~2^64-1。
scanf只会读出需要的几位数字,读取足够后,后面多输入的数字不会读取
switch语句
一个{}代表一个代码块;
如果要在分支/循环语句后连接多个语句,需添加{}
否则只有在其后面的第一句可以执行,或程序报错
case 5 .... 10:表示5-100 ,5后和10前要又空格与省略号隔开
switch()括号里可以是变量/变量+常量/字符
case后的常量不能是浮点数
go to不能跨函数
从低到高强转,可以求最低有效位
srand()随机值 srand((unsigned)time(NULL))
直接传入一个空指针,因为程序中往往并不需要经过参数获得的数据
指针的加减就是偏移多少个数据类型长度
端绪
高低位是看权位判断的
最常见的有两种
1. Little endian:将低序字节存储在起始地址
2. Big endian:将高序字节存储在起始地址
LE little-endian
最符合人的思维的字节序
地址低位存储值的低位
地址高位存储值的高位
怎么讲是最符合人的思维的字节序,是因为从人的第一观感来说
低位值小,就应该放在内存地址小的地方,也即内存地址低位
反之,高位值就应该放在内存地址大的地方,也即内存地址高位
BE big-endian
最直观的字节序
地址低位存储值的高位
地址高位存储值的低位
为什么说直观,不要考虑对应关系
只需要把内存地址从左到右按照由低到高的顺序写出
把值按照通常的高位到低位的顺序写出
两者对照,一个字节一个字节的填充进去
例子:在内存中双字0x01020304(DWORD)的存储方式
内存地址
4000 4001 4002 4003
LE 04 03 02 01
BE 01 02 03 04
例子:如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为
big-endian little-endian
0x0000 0x12 0xcd
0x0001 0x23 0xab
0x0002 0xab 0x34
0x0003 0xcd 0x12
x86系列CPU都是little-endian的字节序.
指针
指针变量存储的实际上就是一块内存的起始地址(基地址)
指针类型决定了指针变量可以从这个起始地址合法访问的大小
*(星号)在定义中是修饰一个变量为指针变量,在表达式中,如果只有一个操作数,就表示解引用操作,访问指针变量所存储的起始地址的这片内存,如果由两个操作数,表示乘法
int *p ==(int *)p //类型转换
*p//解引用
p*p//乘法
int*指针内部可以存地址
64位系统 八个字节
指针除了可以存地址外,本身也有地址
int a=100; int *p=&a;
&a=0x1000; *p 解引用p;
*(&a)=100; *p=a=*(&a);
int **q=&p;
**q=p;
*q=&p;
需要引用stdlib.h头文件
strlen()计算字符串实际长度
sizeof()计算字符占用的空间字节大小
数组
“abcd”是一个匿名数组,“abcd”就是首地址
sizeof(“abcd”)计算数组大小
”abcd“[0]访问第一个元素是a
*p++,就是先加,再解引用
*和++优先级相同,如果同时出现,运行从右到左
#include <stdio.h>
#define DISPALY_ADDR(_p, _addr_size) do \
{ \
printf("%p\t%hhu\n", _p, *_p++); \
}while(--_addr_size)
#define PRINT_VAR_ADDR(addr, _size) char *_p = (char *)addr; \
int _addr_size = _size; \
DISPALY_ADDR(_p, _addr_size)
int main(int argc, char *argv[])
{
// int a[2][3] = {{1, 2, 3}, {6, 5, 4}};
// int a[2][3] = {{1, 2}, {6}};
int a[2][3] = {1, 2, 3, 6, 5};
int i, j;
for(i=0; i<2; i++)
{
for(j=0; j<3; j++)
{
printf("a[%d][%d] = %d\n", i, j, a[i][j]);
}
}
printf("a[0] = %p\n", a[0]);
printf("a[1] = %p\n", a[1]);
PRINT_VAR_ADDR(a, sizeof(a));
a[0][0] == *(a[0]+0) == *(*(a+0)+0)
printf("%d\n", *(a[0]+0));
printf("%d\n", *(*(a+0)+0));
printf("&a = %p\n", &a); //int (*)[2][3]
printf("&a+1 = %p\n", &a+1);
printf("a = %p\n", a); //a == &a[0] int (*)[3]
printf("a+1 = %p\n", a+1);
printf("&a[0] = %p\n", &a[0]); //int (*)[3]
printf("&a[0]+1 = %p\n", &a[0]+1);
printf("a[0] = %p\n", a[0]); //a[0] == &a[0][0] int *
printf("a[0]+1 = %p\n", a[0]+1);
printf("&a[0][0] = %p\n", &a[0][0]); //int *
printf("&a[0][0]+1 = %p\n", &a[0][0]+1);
printf("*a = %p\n", *a); //a == &a[0] , *a == *(&a[0]), *a == &a[0][0] = a[0]
printf("*a+1 = %p\n", *a+1);
return 0;
}
void *fi()
返回值必须是全局指针,局本变量会报错
复杂类型
#include <stdio.h>
// 1
int a = 100;
int *pa = &a;//int 型指针(指向int的指针)
// 2
int **pp = &pa;//int型指针的指针
// 3
int arr[5]={1, 2, 3, 4, 5};//int型数组
// 4
int (*arrp)[5] = &arr;//指向int数组的指针
//*arrp int [5]
// 5
int *parr[5] = {&arr[0], &arr[1], &arr[2]};
//int型指针数组
printf("%d\n", *parr[0]);
// 6
int *(*parrp)[5] = &parr;
//指向int型指针数组的指针
printf("%d\n", *(*parrp)[0]);
// 7
int **pparr[5];
//int型指针的指针数组
// 8
int f(void) //返回值为int的函数
{
printf("%s---[%d]\n", __FUNCTION__, __LINE__);
}
// 9
int *f1(void)//返回值为int型指针
{
printf("%s---[%d]\n", __FUNCTION__, __LINE__);
}
// 10
int **f2(void)//返回值为int型指针的指针的函数
{
printf("%s---[%d]\n", __FUNCTION__, __LINE__);
}
// 11
int (*pf)(void) = &f;//返回值为int的函数指针
// 12
int *(*pf1)(void) = &f1;//返回值为int型指针的函数指针。
// 13
int **(*pf2)(void) = &f2;//返回值为int型指针的指针的函数指针
// 14
int (*fparr[3])(void) = {f, f, f};//返回值为int型的函数指针的数组
// 15
int *(*fpparr[3])(void) = {f1, f1, f1};
//指向“返回值为int型指针的函数”的指针的数组。
// 16
int **(*fppparr[3])(void) = {f2, f2, f2};
//指向“返回值为 int型指针的指针的函数”的指针的数组。
// 17
int (*f3(void))(void)//返回值为“返回值为 int的函数指针”的函数
{
printf("%s---[%d]\n", __FUNCTION__, __LINE__);
}
// 18
int (**f4(void))(void)//返回值为“返回值为int型函数的指针的指针”的函数
{
printf("%s---[%d]\n", __FUNCTION__, __LINE__);
}
// 19
int *(*f5(void))(void)//返回值为“返回值为int型指针的函数指针”的函数
{
printf("%s---[%d]\n", __FUNCTION__, __LINE__);
return f1;
}
// 20
int (*(*pff)(void))(void) = f3;
//返回值为int型指针的函数指针”的函数指针
// 23
int (*(*pfarr)(void))[3];
//“指向int型数组的指针”的函数指针
// 24
int *(*(*pfparr)(void))[3];
//“指向int型指针数组的指针”的函数指针
// 25
int (*(*(*ppffarr)(void))[3])(void);
//返回值为“指向‘返回值为int型指针的函数指针’的数组的指针”的函数指针
// for(i=0; i<3; i++)
// {
// fpparr[i]();
// }
f3();
野指针
·概念:指向一块未知区域的指针,被称为野指针。野指针是危险的。
·危害:
1.引用野指针,相当于访问了非法的内存,常常会导致段错误(segmentation fault)2.引用野指针,可能会破坏系统的关键数据,导致系统崩溃等严重后果
·产生原因:
1.指针定义之后。未初始化
2指针所指向的内存。被系统回收3.指针越界
·如何防止:
1.指针定义时,及时初始化2.绝不引用已被系统回收的内存3.确认所申请的内存边界,谨防越界
void类型指针可以给其他任意类型赋值,也可以等于任意类型的值
const
const 修饰一个变量只读,但是可以通过地址对其修改,也就是引用指针
const修饰的变量一定要初始化即赋初值
const int a=200;
int *pa =(int *)&a;
*pa =300;//a的值就变成300
#include <stdio.h>
int main(int argc, char *argv[])
{
// const:修饰一个变量为只读
//const修饰的变量一定要赋初值
#if 1
//1. 修饰普通变量
const int a = 200; //修饰变量a对内存只读
// int const a = 200; //修饰变量a对内存只读,跟上面的写法等价
printf("%d\n", a);
// a = 100;
int *pa = (int *)&a; //可以通过指针间接改变
*pa = 300;
printf("%d\n", a);
printf("%d\n", *pa);
#endif
// 2. 修饰指针
// 2.1 修饰指针是否可以解引用访问指向的内存
int a = 200;
int b = 300;
#if 0
// int const *pa = (int *)&a; //忽略数据类型(int),只看const修饰什么
const int *pa = (int *)&a; //忽略数据类型(int),只看const修饰什么,跟上面的写法等价
// *pa = 100; //const限定了*pa无法解引用给指向的内存写入数据
pa = (int *)&b;
// *pa = 100;
printf("%d\n", *pa);
// 2.2 修饰指针是否可以指向其他的变量地址
int *const pa = (int *)&a; //忽略数据类型(int),只看const修饰什么
*pa = 400;
printf("%d\n", *pa);
pa = (int *)&b; //const限定了pa为只读变量属性,无法再去赋值其他的地址
// 2.3 修饰指针是否可以指向其他的变量地址,同时修饰指针是否可以解引用访问指向的内存
const int * const pa = (int *)&a;//忽略数据类型(int),只看const修饰什么
*pa = 200; //被前一个const修饰为只读访问
pa = (int *)&b; //被后一个const修饰为只读访问
#endif
return 0;
}
1.进程结束。2.遇到n。
3.缓冲区满。printf函数的缓中区大小为1024个字节,当超出缓中区的大小,缓中区会被刷剧新,将会打印出结果。
4.手动刷剧新缓中区fflush(stdout)。
5调用exit(O);但是还可以调用_exit(O),不刷新缓中区。
缓中区满足以上任意情况时,将会输出到显示屏
fflush(stdout)//强制刷新缓冲区
“stdio.h”//本地头文件,个人写的头文件
<stdio.h>//系统头文件,在系统目录下
ASCII 编码中第 0~31 个字符(开头的 32 个字符)以及第 127 个字符(最后一个字符)都是不可见的(无法显示),但是它们都具有一些特殊功能,所以称为控制字符( Control Character)或者功能码(Function Code)。
隐式转换:低精度向高精度转换,有符号向无符号转换
‘a’字符;
“a”字符串,有‘\0’,实际存储两个字符
void 无符号/泛型
#include <stdio.h>
#include <stdlib.h>
// 返回值类型 函数名(形参列表)
// {
// 函数体
// }
///void表示没有函数返回值
void f(int a)
{
return ; //返回值类型为void,无返回值
}
// /void*表示返回值类型为void *指针
void *f1(int a)
{
static int p[100];
return p; //void * 表示返回一个任意类型指针
}
int main(int argc, char *argv[])
{
// void:泛型(无类型)
// void a; //不可以定义普通变量
int a = 100;
float b = 4.232;
//void *可以存储任何类型的指针
//不允许对一个void *指针进行解引用
// void *p = &a;
void *p = &b;
// *p = 200;
//void *指针可以赋值给其他任意类型指针
int *q = p;
char *cq = p;
return 0;
}
typedef取别名
#include <stdio.h>
typedef int INT; //给int取了个别名叫INT
typedef float FLOAT; //给float取了个别名叫FLOAT
typedef int ARRAY[5]; //给int [5]取了个别名叫做ARRAY
typedef int *PINT; //给int * 取了个别名叫做PINT
typedef int (*ARR_P)[3];
//复杂的部分
// int (*arr_p)[3]; //声明了一个数组指针,这个指针指向一个数组,这个数组有3个整形元素
// int *p_arr[5]; //声明了一个指针数组,这是一个数组,数组有5元素,每个元素是一个int *的指针
// int *pf(void); //指针函数,函数返回值是一个指针
// int (*fp)(int a, char b); //函数指针,定义了一个函数指针fp指向了一个返回值为int,参数为int和char的两个形参
int main(int argc, char *argv[])
{
int i;
int a = 100;
// FLOAT f;
#if 0
// int a[5] = {1,2 ,3 , 4, 5};
ARRAY a = {1,2 ,3 , 4, 5};
for(i=0; i<5; i++)
printf("%d\n", a[i]);
printf("%ld\n", sizeof(a));
#endif
/*int *p = &a, *r=&a, *h; //定义多个指针变量时,每个变量前面都必须添加*修饰,否则是定义了一个普通变量
PINT q = &a, k=&a, u=&a; //指针变量的别名定义多个指针变量的时候不用再添加*修饰
q = p;*/
int **q = NULL;
#if 0
int arr[3];
ARR_P pa = &arr;
#endif
return 0;
}
函数定义
函数的名字就是一个地址编号(指针常量)
#include <stdio.h>
//在函数外部定义的变量就是全局变量
int a = 400; //全局变量整个程序有效
int b = 500; //整个程序中的函数共享
// extern int c; //这是一个声明,声明在全局,整个程序有效
// void f(int a); //声明一个函数,必须要加分号
void f(int); //声明一个函数,也可以省略这个函数的形参名
// void f(int a)
// {
// int i = 100;
// b = 300;
// printf("%s: b = %d\n", __FUNCTION__, b);
// printf("%s: i = %d, a = %d\n", __FUNCTION__, i, a);
// }
int main(int argc, char *argv[])
{
int a = 300; //局部变量在本函数有效
int i = 200;
extern int c; //局部声明,只在本函数有效
// printf("%s: b = %d\n", __FUNCTION__, b);
printf("%s: c = %d\n", __FUNCTION__, c);
f(100);
// f(i); //200
// printf("%s: b = %d\n", __FUNCTION__, b);
// printf("%s: i = %d, a = %d\n", __FUNCTION__, i, a);
return 0;
}
void f(int a)
{
int i = 100;
extern int c;
b = 300;
printf("%s: b = %d\n", __FUNCTION__, b);
// printf("%s: i = %d, a = %d\n", __FUNCTION__, i, a);
printf("%s: c = %d\n", __FUNCTION__, c);
}
int c = 100;
普通函数与回调函数的区别:
对普通函数的调用:调用程序发出对普通函数的调用后,程序执行立即转向被调用函数执行,直到被调用函数执行完毕后,再返回调用程序继续执行。从发出调用的程序的角度看,这个过程为“调用-->等待被调用函数执行完毕-->继续执行”
对回调函数调用:调用程序发出对回调函数的调用后,不等函数执行完毕,立即返回并继续执行。这样,调用程序执和被调用函数同时在执行。当被调函数执行完毕后,被调函数会反过来调用某个事先指定函数,以通知调用程序:函数调用结束。这个过程称为回调(Callback),这正是回调函数名称的由来
#include <stdio.h>
void show_i(void *parr, int len)
{
int i;
int *pa = (int *)parr;
for(i=0; i<len; i++)
{
printf("%d\t", pa[i]);
}
printf("\n");
}
void show_f(void *parr, int len)
{
int i;
float *pa = (float *)parr;
for(i=0; i<len; i++)
{
printf("%.2f\t", pa[i]);
}
printf("\n");
}
void show_c(void *parr, int len)
{
int i;
char *pa = (char *)parr;
for(i=0; i<len; i++)
{
printf("%c\t", pa[i]);
}
printf("\n");
}
void show_arr(void (*pf)(void *parr, int len), void *parr, int len)
{
pf(parr, len);
}
int main(int argc, char *argv[])
{
int a[5] = {1, 2, 3 ,4 , 5};
float b[5] = {1.1, 2.2, 3.3 ,4.4 , 5.5};
char c[5] = {65, 66, 67 ,68 , 69};
show_arr(show_i, a, 5);
show_arr(show_f, b, 5);
show_arr(show_c, c, 5);
void (*pf)(void *parr, int len) = show_c;
void (*pf)(void *parr, int len) = show_i;
return 0;
}
点餐
#include <stdio.h>
#include <unistd.h>
//extern void meituan( void (*pf)(int t), int t1, int t2);
//如果meituan写在外部文件,可以使用extern调用
void meituan( void (*pf)(int t), int t1, int t2)
{
pf(t1);
//void (*pf)(int t) =void naicha(int t);传参
printf("快递小哥接到餐...\n");
printf("配送倒计时\n");
while(t2--)
{
printf("%d\r", t2);
sleep(1);
fflush(stdout);
}
printf("已送达\n");
}
void luosifen(int t)
{
printf("螺蛳粉正在制作...\n");
while(t--)
{
printf("%d\r", t);
sleep(1);
fflush(stdout);
}
printf("螺蛳粉制作完成...\n");
}
void naicha(int t)
{
printf("奶茶正在制作...\n");
while(t--)
{
printf("%d\r", t);
sleep(1);
fflush(stdout);
}
printf("奶茶制作完成...\n");
}
int main(int argc, char *argv[])
{
// void (*pd)(int t) = &naicha;
// void (*pd)(int t) = naicha;
// printf("%p\n", naicha);
// printf("%p\n", &naicha);
// naicha(4);
// (*pd)(4);
// pd(4);
meituan(&naicha, 3, 4);
meituan(&luosifen, 5, 2);
return 0;
}
//内联函数:消除调用函数时的系统开销,以提高运行速度。
inline double circle(double r) //内联函数
{
double PI = 3.14;
return PI * r * r;
}
//内联函数在第一次被调用之前必须进行完整的定义,否则编译器将无法知道应该插入什么代码
//在内联函数体内一般不能含有复杂的控制语句,如for语句和switch语句等
//使用内联函数是一种空间换时间的措施,若内联函数较长,较复杂且调用较为频繁时不建议使用
//使用内联函数替代宏定义,能消除宏定义的不安全性
组合
1. 数组:定义多个相同类型的元素的集合,为所有的元素分配空间,通过下标访问元素
2. 结构体:定义多个相同或者不同类型的元素(成员)的集合,为所有的元素分配空间,通过元素名字访问元素
struct 标签 struct student
{ {
//成员1; //char name[];
//成员2; //int num;
//成员3; //int age;
//.....
}; };
struct student
{
//char name[];
//int num;
//int age;
} stu;//stu 是取的别名 调用时 struct 可省略
#include <stdio.h>
#include <string.h>
// struct:定义一个结构体
//只使用关键修饰类型声明一些变量,并没有确定结构体类型
#if 0
int num;
char name[10];
//括号限定了作用域,成员的声明在内部私有命名空间有效
//定义了多个不同类型成员的集合,然后声明了两个变量student1, student2;
struct
{
char name[20];
int num;
short age;
}student1, student2;
struct
{
char name[20];
int num;
short age;
}student3, student4;
struct
{
int a[10];
}arr1, arr2;
//数组是不可以直接整体赋值的
// int a[5];
// int b[5];
// a = b;
int main(int argc, char *argv[])
{
// student1 = student3; //不同类型不可以直接赋值
// student1 = student2; //相同类型可以赋值
// .点叫做成员访问符
// strcpy(student1.name, "lisi");
// student1.num = 1001;
// student1.age = 100;
printf("请输入一个学生的名字,学号,年龄\n");
scanf("%s%d%hd", student1.name, &student1.num, &student1.age);
printf("%s, %d, %hd\n", student1.name, student1.num, student1.age);
student2 = student1; //同类型的结构体变量可以相互赋值
printf("%s, %d, %hd\n", student2.name, student2.num, student2.age);
int i;
for(i=0; i<10; i++)
{
arr1.a[i] = 100+i;
}
//借助结构体的特性,完成数组的整体赋值
arr2 = arr1;
for(i=0; i<10; i++)
{
printf("%d\t", arr2.a[i]);
}
printf("\n");
return 0;
}
#endif
#if 0
struct student
{
char sex;
char name[20];
int num;
short age;
}; //分号必须要加
// int f(char *name, int num, short age, char sex)
// {
// ....
// ....
// }
//简化函数传参,利于软件升级
// int f(struct student stu)
// {
// }
int main(int argc, char *argv[])
{
int i;
//struct不可省略,否则student这个没有意义
// struct student stu1 = {"zs", 1001, 40}; //定义时赋初值,必须要顺序赋初值
struct student stu1 = {.name = "zs", .num = 1001, .age = 40}; //指定成员赋初值
// printf("%s, %d, %hd\n", stu1.name, stu1.num, stu1.age);
struct student a[3] = {
{.name = "zs", .num = 1001, .age = 40},
{.name = "ls", .num = 1002, .age = 40},
{.name = "dd", .num = 1003, .age = 40}
};
// for(i=0; i<3; i++)
// {
// printf("%s, %d, %hd\n", a[i].name, a[i].num, a[i].age);
// }
//结构体指针需要通过 -> 箭头成员访问符访问成员
struct student *p = &stu1;
printf("%s, %d, %hd\n", (&stu1)->name, (&stu1)->num, (&stu1)->age);
printf("%s, %d, %hd\n", p->name, p->num, p->age);
return 0;
}
#endif
typedef struct student
{
char sex;
char name[20];
int num;
short age;
}Stu, *Pstu; //分号必须要加
// Stu 是 struct student 的别名
int main(int argc, char *argv[])
{
int i;
// Stu stu1 = {"zs", 1001, 40}; //定义时赋初值,必须要顺序赋初值
Stu stu1 = {.name = "zs", .num = 1001, .age = 40}; //指定成员赋初值
// printf("%s, %d, %hd\n", stu1.name, stu1.num, stu1.age);
Stu a[3] = {
{.name = "zs", .num = 1001, .age = 40},
{.name = "ls", .num = 1002, .age = 40},
{.name = "dd", .num = 1003, .age = 40}
};
// for(i=0; i<3; i++)
// {
// printf("%s, %d, %hd\n", a[i].name, a[i].num, a[i].age);
// }
//结构体指针需要通过 -> 箭头成员访问符访问成员
Stu *p = &stu1;
printf("%s, %d, %hd\n", (&stu1)->name, (&stu1)->num, (&stu1)->age);
printf("%s, %d, %hd\n", p->name, p->num, p->age);
return 0;
}
3. 联合体:定义多个相同或者不同类型的元素(成员)的集合,以其中最大的元素的类型分配空间,通过元素名字访问元素