指针
- 指针是一种数据类型,它指向内存
- 每个地址对应一个 Byte,
- 地址编号本身是 8 个字节(64位系统)or 4个字节(32位系统)的 无符号整数
int *p;
// 1. 指针变量是 p
// 2. void *p 可以指向任何类型
int a;
p = &a; // 把 a 的内存赋值给 p
// 或者 int *p = &a;
a = 1;
printf("%p\n",p); // 打印内存编号
*p = 10; // 给指针指向的位置赋值,*p 就代表a
// 1.
register int a;
定义的变量是无法用 & 取地址的,因为它一直在寄存器中。- 保证类型一致
空指针和野指针
// 野指针
int *p ;
*p = 100;
// 没有指向哪个变量,编译器不报错,但规范不允许
// 空指针
int *p = NULL;
// 空指针是允许的
// NULL 其实是0
指向常量的指针,指针常量
// 指针常量
int a = 0;
const int *p = &a; // p 是一个变量,但指向一个常量
// 可以通过 *p 读取,但不能通过 *p 写。可以通过 a 写。
// *p = 1; // 这个是不允许的
// a = 1; // 这个是允许的
// 可以这么理解把,获取了 a 的只读权限
// 常量指针
int *const p = &a;
// 这个 p 是常量指针,不能再指向其它变量,例如 *p = &a2 是错的
// 错误用法
const int a = 100;
int *p = &a;
*p = 1;
printf("%d != %d\n", a, *p); // 不相同,好像和编译器有关,不知道为啥
printf("%p == %p", p, &a); // 相同
// 上面这个用法是不符合规范的。
指针运算
p + 1; // 指向下一个 “数据单元”,而不是16进制地址加1
p - 1;
// 实际移动多少 16 进制大小呢?取决于定义指针时的指定类型。例如 int 是 4,double 是 8
// 如果指针类型和变量类型不一致(虽然不符合规范),以指针类型为准
// 同样,也可以有这个:
p += 5;
p -= 3;
p++;
p--;
*(p + 3) = 100;
p[3] = 100; // 等价写法,即使不是数组,也能这么写,位移3个单位
有趣的例子1
int a = 0x12345678;
char *p = &a; // 也可以 char *p = (char *) &a;
printf("%x\n", *p);
printf("%x\n", *(p + 1));
printf("%x\n", *(p + 2));
printf("%x\n", p[3]);
// 返回:
// 78
// 56
// 34
// 12
// 1. 颠倒过来是因为整数是“正向对其”的
// 2. 也可以写入
有趣的例子2:ip 其实对应一个 int
unsigned int a = 987654321;
unsigned char *p = (unsigned char *)&a;
for(int i=3;i>=0;i--){
printf("%u.",p[i]);
}
思考: ip 如何转回为 int
unsigned char a[4] = {58, 222, 104, 177};
unsigned char tmp[4];
for (int i = 0; i < 4; i++) {
tmp[i] = a[3 - i];
}
unsigned int *p = &tmp;
printf("%d",*p);
指针与数组
指向数组的指针
指向数组元素的指针
int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *p = arr; // 数组存入的就是内存地址,因此不需要 *p = &a;
int *p = &arr[1]; // 也可以
p[3] = 100; // 可以直接当数组名用
*(p + 3); // 等价用法
printf("%ld != %ld ", sizeof(arr), sizeof(p)); //但又不完全一样
int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *p = &arr[1];
p[3] = 100; // 其实是 a[4]
// ???这个有什么区别?
int arr[5] = {1, 2, 3, 4, 5};
int *p = (int *) &arr;
// 试了一下,与 int *p = arr 的区别:
// p+1 没有区别
// 不正经的写法
以上是指向数组元素的指针,下面是指向数组的指针
int arr[5] = {1, 2, 3, 4, 5};
int (*p2)[5] = &arr;// 定义指向数组的指针
p2 + 1; // 是把这个数组当做一个“整体”,然后移动到下一个位置:5*int 个 Byte
*p2; // 是个指针,指向第一个元素
*p2 + 1; // 一个指针,指向第二个元素
*(*p2 + 1) ; // 第1个元素
指针数组
一个数组,数组的元素是指针
int *p[5]; // 5个指针组成的数组(叫做指针数组)
p[2]; // 这是一个指针
*p[2]; // 这是那个指针指向的值
// 你需要先把指针的指向赋值好,才能用这个指针
int a = 2;
p[3] = &a;
*p[3] = 5;
二级指针
指的是指向指针的指针
int a = 0;
int *p = &a;
int **pp;//二级指针,指向指针的指针
pp = &p;
// 或者 int **p = &p;
**pp = 10; // 通过二级指针访问 a
如果想用一个指针指向指针数组,必须用一个二级指针指向它
int value = 10;
int *arr[10];//一个指针数组
arr[2] = &value;//指针数组的第2个,指向一个 int
int **p = arr; //指向指针数组的指针,必须用二级指针
p[2]; // 其实是 arr[2] ,存放的是一个指针
*p[2] = 5; // 那么,这个指针其实指向 value
类似的,还有多级指针
指针的应用
如果想用函数改变某个变量的值,必须通过指针
int swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
return 0;
}
int main() {
int a = 1;
int b = 2;
swap(&a, &b);
printf("%d%d", a, b);
return 0;
}
指针作为函数的入参
当一个数组名作为入参时,自动作为指针变量。
int tst(int a[10]);
int tst(int a[]);
int tst(int *a);
// 以上三个是等价的
// 编译的时候,3个都会编译成最后一个
// 规范:通常使用最后一个
如果函数不修改数组,可以约定一个 const
int tst(const int *a); // 这样,函数内部就无法修改数组 a 了
// 其实做个强转后,还是能改的 int *p = (int *)a; p[5] = 999;
// C++ 没这漏洞了
// 一般约定,函数只要不改数组,都加 const
如果数组作为入参,数组的大小在函数中是不可见的
int tst(const int *a) {
printf("%lu\n", sizeof(a)); // 是指针的size,必然为 8
return 0;
}
// 因此,往往需要额外传入一个数组长度。不过字符串不需要,因为字符串是 0 结尾的
int tst(int len_arr,int *arr) {
return 0;
}
二维数组作为入参
int func(int len1, int len2, int p[3][3]);
int func(int len1, int len2, int p[][3]);
int func(int len1, int len2, int (*p)[3]);
指针作为函数的返回值
一个函数也可以返回一个指针
int *tst(); // 函数返回指针
main函数的参数
int main(int argc, char **args) {
for (int i = 0; i < argc; i++) {
printf("%s, ", args[i]);
}
printf("\n");
return 0;
}
// 如何使用?
// 编译后:./main -l -s 0
// 打印:./main, -l, -s, 0,
// !注意,在linux下,* 指的是通配符,被目录下的全部文件替换
// 要想传入星号,要传入 \*
标准库-内存操作
string.h
memset:内存置空
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
memset(a, 0, sizeof(a));
// 入参:置空的区域的首地址,0,这块内存大小(单位 Byte)
memcopy:内存拷贝。要确保没有内存重叠区域
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int b[10] = {0};
memcpy(b, a, sizeof(a));
// 入参:目标地址,源地址,拷贝的大小(单位 Byte)