指针
指针变量类型的意义
取地址操作符(&)
c
int a=0;
&a//可以得到存储a的地址
指针的解引⽤
为了存储a的地址可以存储起来方便下次使用,使用指针来存储
c
int n=0;
int *a=&n;
那么如何修改存储的值呢
使用解引用操作符
c
int n=0;
int *a=&n;
*a=10;
这样n的值就改为了10
指针变量的⼤⼩
c
printf("%zd\n", sizeof(char *));
printf("%zd\n", sizeof(short *));
printf("%zd\n", sizeof(int *));
printf("%zd\n", sizeof(double *))
32位平台下地址是32个bit位,指针变量⼤⼩是4个字节
64位平台下地址是64个bit位,指针变量⼤⼩是8个字节
解引用的区别
但是char解引用可以修改1个字节
int解引用可以修改4个字节
指针±整数
char*±一步跨1字节
int*±一步跨4字节
void* 指针
void*可以避免类型转换不兼容
指针运算
指针± 整数
*(arr+1);
指针往后移动一个单位(int为4字节)
指针 - 指针
指针相距多少个单位
指针的关系运算
<表示一个指针在另一个指针之前
const指针
const 修饰指针变量
c
int *const p;//const在*右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指
向的内容,可以通过指针改变。
int const *p;//const int*p
//const在*左边,修饰的是指针指向的内容,
//保证指针指向的内容不能通过指针来改变。
//但是指针变量本⾝的内容可变。
野指针
成因
1.未初始化
2.越界访问
3.指向的空间释放了
如何避免
1.初始化置null
2.避免指针越界
3.不用时置null,用时检查有效性
4.避免返回局部变量的地址
数组名的理解
特殊情况:
1.sizeof(arr)表示整个数组
2.&arr表示整个数组
其他情况都表示首元素的地址
使⽤指针访问数组
*(p+1)等价于p[i]
字符指针变量
c
char*
const char*//如果内容相同,地址就相同,因为存储在常量区
C/C++会把常量字符串存储到单独的⼀个内存区域,
当⼏个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。但是⽤相同的常量字符串去初始
化不同的数组的时候就会开辟出不同的内存块。
指针数组
是存放指针的数组。
数组里面全都是存放指针的
数组指针
是指向数组的指针
c
int * p1[10];///[]优先级高于*,先与[]结合,是数组
int (*p2)[10];//先与*结合,是指针
函数指针
函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的。
函数是有地址的,函数名就是函数的地址
函数指针变量
通过函数指针调⽤指针指向的函数。
c
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
int main()
{
int(*pf3)(int, int) = Add;
printf("%d\n", (*pf3)(2, 3));
printf("%d\n", pf3(3, 5));
return 0;
}
//回调函数
void calc(int(*pf)(int, int))
{
int ret = 0;
int x, y;
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("ret = %d\n", ret);
//在calc()中传入函数地址
}
sizeof和strlen的对⽐
sizeof (操作符)
只关注占⽤内存空间的⼤⼩,不在乎内存中存放什么数据。
strlen (库函数)
求字符串⻓度.str 中这个地址开始向后, \0 之前字符串中字符的个数。
库函数的模拟实现
memcpy
c
void mymemcpy(void*des,cont void*src,size_t num)
{
assert(des);
assert(src);
void*res=des;
while(num--)
{
*(char*)des=*(char*)src;
des=(char*)des+1;
src=(char*)src+1;
}
return res;
}
strcpy与它的区别就是遇到\0时结束拷贝
memmove
c
void mymemove(void*des,const void*src,size_t num)
{
assert(des);
assert(src);
void*res=des;
if(src<=des||(char*)src+num<=(char*)des)
{
des=(char*)des+num-1;
src=(char*)src+num-1;
while(num--)
{
*(char*)des=*(char*)src;
des=(char*)des-1;
src=(char*)src-1;
}
}
else
{
while(num--)
{
*(char*)des=*(char*)src;
des=(char*)des+1;
src=(char*)src+1;
}
}
return res;
}
strstr
c
char* mystrstr(const void*str1,const void* str2)
{
assert(str1);
assert(str2);
char*cp=str1;
while(*cp)
{
char*s1=cp;
char*s2=str2;
while(*s1&&*s2&&*s1==*s2)
{
s1++;
s2++;
}
if(!*s2)return cp;
cp++;
}
return NULL;
}
自定义类型
内存对齐
原因:
- 平台原因 (移植原因)
- 数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。不然可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。
结构体内存对齐规则
- 对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。
- 默认值
VS 中默认的值为 8
Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩ - 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构
体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
联合
联合的⼤⼩⾄少是最⼤成员的⼤⼩。
当最⼤成员⼤⼩不是最⼤对⻬数的整数倍的时候,就要对⻬到最⼤对⻬数的整数倍。
数据存储
整形存储
原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
大小端及如何判断
区分
⼤端(存储)模式:
是指数据的低位字节内容保存在内存的⾼地址处,⽽数据的⾼位字节内容,保存在内存的低地址处。
⼩端(存储)模式:
是指数据的低位字节内容保存在内存的低地址处,⽽数据的⾼位字节内容,保存在内存的⾼地址处。
代码判断
c
//联合体判断
union myUnion
{
int a;
char c[4];
};
int main()
{
myUnion un;
un.a=0x12345678;
if(un.c[0]==0x12)
printf("大端存储");
else
printf("小端存储");
return 0;
}
c
//截断判断
int main()
{
int a=0x12345678;
char*pa=(char*)&a;
if(*pa==0x12)
printf("大端存储");
else
printf("小端存储");
return 0;
}
浮点数存储

V=(-1)sM 2E
- (−1)S 表⽰符号位,当S=0,V为正数;当S=1,V为负数
- M 表⽰有效数字,M是⼤于等于1,⼩于2的
- 2E 表⽰指数位
1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中 xxxxxx 表⽰⼩数部分。
类型提升和截断
运算时提升:小类型 → 大类型,顺序:
char/short → int → long → long long
float → double → long double
类型提升
当不同大小 / 类型的变量做运算时,编译器会自动把小范围的类型转换成大范围的类型,保证计算不丢失精度。也叫隐式类型转换、自动类型提升。
类型截断
把大范围类型强行塞进小范围类型里,装不下的高位数据直接丢掉。
编译链接
宏
#define
原则:多加括号
编译链接的过程
- 预处理(进⾏宏替换/去注释/条件编译/头⽂件展开等)gcc --E hello.c --o hello.i
- 编译(⽣成汇编) gcc --S hello.i --o hello.s
- 汇编(⽣成机器可识别代码) gcc --c hello.s --o hello.o
- 链接(⽣成可执⾏⽂件或库⽂件) gcc hello.o --o hello