C语言深入理解指针(1)
【摘要】 1.内存地址内存单元的编号 == 地址 == 指针cpu访问内存中的某个字节空间,必须知道这个字节空间在内存的什么位置,而因为内存中字节很多,所以需要给内存进行编址/int main()//{// int a = 20;//创建变量的本质其实是在内存中申请空间// //向内存申请4个字节的空间,用来存放20这个数字// //这4个字节,每个字节都有编号(地址)// /...
1.内存地址
内存单元的编号 == 地址 == 指针
cpu访问内存中的某个字节空间,必须知道这个字节空间在内存的什么位置,而因为内存中字节很多,所以需要给内存进行编址
/int main()
//{
// int a = 20;//创建变量的本质其实是在内存中申请空间
// //向内存申请4个字节的空间,用来存放20这个数字
// //这4个字节,每个字节都有编号(地址)
// //变量的名字仅仅给程序员看,编译器不看名字,
// // 编译器是通过地址找内存单元的
// //
// return 0;
//}
2.指针变量和地址
& --取地址--拿到地址
-
--解引用--通过地址找回去
通过地址来找回a *pa
解释:
1.*表示pa是指针变量
2.int 表示pa指向的变量a的类型是int
char ch ='w'
char *pc =&ch;
/*int main()
{
int a = 20;
&a;//&----取地址操作符,拿到变量a的地址
printf("%p",&a);//打印出来的地址是00B3FD40
int* pa = &a;//将a的地址存在变量pa里面
//这个变量是用来存放地址(指针)的
//所以pa叫指针变量
//int*来是pa的类型
return 0;
}*/
/*
解释:
1.*表示pa是指针变量
2.int 表示pa指向的变量a的类型是int
char ch ='w'
char *pc =&ch;
*/
int main()
{
int a = 20;
int* pa = &a;
*pa=30;//* -解引用操作符(间接访问操作符)
//a被改成30
//通过*pa找到a
//*pa其实就是a
printf("%d", a);
return 0;
}
/*
& --取地址--拿到地址
* --解引用--通过地址找回去
通过地址来找回a *pa
*/
//int main()
//{
// int a = 10;
// int* p = &a;
// //1.指针变量是用来存放地址的,
// // 地址的存放需要多大空间
// //那么指针变量的大小就是多大
//
///*
//指针变量的大小取决于地址的大小
////32位平台下地址是32个bit位(即4个字节)
////64位平台下地址是64个bit位(即8个字节)
//x86是32位的环境,x64是64位的环境*/
// printf("%zd", sizeof(p));//输出结果是4
// return 0;
//}
//int main()
//{
// char ch = 'w';
// char* pc = &ch;
//
//
// printf("%zd", sizeof(pc));//输出结果是4
// return 0;
//}
//指针变量的大小跟类型是无关的
int main()
{
printf("%zd\n", sizeof(char*));
printf("%zd\n", sizeof(short*));
printf("%zd\n", sizeof(int*));
printf("%zd\n", sizeof(float*));
printf("%zd\n", sizeof(double*));
//输出结果都是4个字节
return 0;
}
//指针变量的大小跟类型是无关的
//只要是指针类型的变量,只要在同一个平台下,大小就都是相同的
//指针类型有什么意义?为什么存在那么多的指针类型?
3.指针变量类型的意义
指针的类型决定了,对指针解引用的时候有多大权限(一次能操作几个字节)
比如:char的指针解引用就只能访问一个字节,而int的指针解引用就能访问四个字节
//指针类型有什么意义?为什么存在那么多的指针类型?
int main()
{
int a = 20;
int* pa = &a;//取地址a放到pa里面
char* pc = &a;
printf("&a=%p\n", &a);
printf("pa=%p\n", pa);
printf("pc=%p\n", pc);
printf("&a+1=%p\n", &a + 1);
printf("pa+1=%p\n", pa + 1);
printf("pc+1=%p\n", pc + 1);
return 0;
}
/*打印结果
&a = 004FF77C
pa = 004FF77C
pc = 004FF77C
& a + 1 = 004FF780
pa + 1 = 004FF780
pc + 1 = 004FF77D
*/
/*
char*类型的指针变量+1跳过1个字节,int类型的指针+1跳过4个字节
。这就是指针类型的差异带来的变化
int *pa
pa+1--->+1*sizeof(int)
pa+n--->+n*sizeof(int)
char*pc
pc+1--->+1*sizeof(char)
pc+n--->+n*sizeof(char)
*/
//对应int访问4个字节,+1跳过四个字节
//void* 指针----无具体类型的指针
//这种类型的指针可以用来接收任意类型的地址
//但是这种指针存在局限性,void*类型的指针不能直接进行指针的+-整数和解引用运算
//void*指针不能进行指针运算,可以接收不同类型的地址
//一般void*类型的指针是使用函数参数的部分,用来接收不同类型的地址
//这样的设计可以实现泛型函数的效果,使得一个函数来处理多种类型的数据
4.const修饰指针
int main()
{
const int n = 10;
//n=20;
int *p=&n;//把n的地址取出来给p
*p = 200;//通过n的地址来改变n的大小
printf("%d", n);
return 0;
}
//一般的const修饰指针变量,
// 可以放在*左边,也可以放在*右边
//int main()
//{
// int const* p;//const在*左边
// int* const p;//const在*右边
// return 0;
//}
//int main()
//{
// int n = 10;
// int m = 100;
// int* p = &n;
// *p = 20;//通过p找到n,因为p指向的是n
// p = &m;//用m的地址将n的地址覆盖了
///*关于指针有3个相关值
//* 1.p,p里面存放着一个地址
//* 2.*p,p指向的对象
//* 3.&p,表示的是p变量的地址
//*/
// return 0;
//}
//int main()
//{
// int n = 10;
// int m = 100;
// int const* p = &n;//等同于const int * p = &n;
//
// //const放在左边的时候,限制的是*p,也就是p指向的对象
// //const修饰指针变量
// //放在*左边,限制的是指针指向的内容,
// // 也就是不能通过指针变量修改它所指向的内容
// //在这里面还是限制上了n,n的数字不能被修改
//
// //但是指针变量本身是可以改变的,里面的地址是可以改变的
// *p = 20;//err
// p = &m;//ok
// return 0;
//}
//int main()
//{
// int n = 10;
// int m = 100;
// int *const p = &n;
//
// //如果要去改变p是不行的,但是没有限制*p
// //这里是可以改变p指向的对象的
//
// //
// // //将const放在*右边,此时限制的是指针变量p本身
// // //指针不能改变它的指向,
// // 但可以通过指针变量修改它所指的内容
// //指针指向的是n,但是目前已经被const固定住,不能改变指向
// *p = 20;//err
// p = &m;//ok
// return 0;
//}
//不想让const修改p就把p放在*右边
//不想让你通过p修改p的指向的内容就把const放在左边,将*p固定住
#include <stdio.h>
// 假设使用3个数据位和2个校验位
// 总位数为 3(数据位)+ 2(校验位)= 5位
// 汉明码编码函数
void hamming_encode(int data[], int *encoded) {
int p = 0; // 校验位计数器
for (int i = 0; i < 3; i++) {
// 计算每个校验位
if (i == 0) {
// 第一个校验位
encoded[p++] = (data[1] + data[2]) % 2;
} else if (i == 1) {
// 第二个校验位
encoded[p++] = (data[0] + data[2]) % 2;
} else {
// 第三个校验位
encoded[p++] = (data[0] + data[1]) % 2;
}
// 将数据位放入编码后的数据中
encoded[p++] = data[i];
}
}
// 汉明码解码和错误纠正函数
void hamming_decode(int received[], int *data) {
int p = 0; // 校验位计数器
int error检测 = 0;
for (int i = 0; i < 3; i++) {
// 计算每个校验位
if (i == 0) {
error检测 = (received[1] + received[2]) % 2;
} else if (i == 1) {
error检测 = (received[0] + received[2]) % 2;
} else {
error检测 = (received[0] + received[1]) % 2;
}
// 检查是否有错误
if (error检测 != received[p]) {
// 找到错误位并纠正
for (int j = 0; j < 5; j++) {
if (received[j] != (error检测 ^ received[j])) {
received[j] ^= 1; // 翻转错误位
break;
}
}
}
// 将数据位放入解码后的数据中
data[i] = received[p++];
}
}
int main() {
int data[] = {1, 0, 1}; // 原始数据
int encoded[5]; // 编码后的数据
int received[5]; // 模拟接收到的数据
int decoded[3]; // 解码后的数据
// 编码
hamming_encode(data, encoded);
printf("Encoded Data: ");
for (int i = 0; i < 5; i++) {
printf("%d ", encoded[i]);
}
printf("\n");
// 模拟接收到的数据(包含一个错误)
for (int i = 0; i < 5; i++) {
received[i] = encoded[i];
}
received[2] ^= 1; // 故意引入一个错误
// 解码和错误纠正
hamming_decode(received, decoded);
printf("Decoded Data: ");
for (int i = 0; i < 3; i++) {
printf("%d ", decoded[i]);
}
printf("\n");
return 0;
}
不想让const修改p就把p放在*右边-- *const p--一直指向一个数 不想让你通过p修改p的指向的内容就把const放在左边,将*p固定住-conat *p
p被const固定住,p指向内容的大小不得改变
6.野指针
指针的基本运算有三种,分别是:
指针+-指数
指针-指针
指针的关系运算
//循环打印数组的内容
//int main()
//{
// int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
// int sz = sizeof(arr) / sizeof(arr[0]);
// for (int i = 0; i < sz; i++)
// {
// printf("%d", arr[i]);
// }
//
//
// return 0;
//
//}
//采用指针来获取数组元素的地址
//int main()
//{
// int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
// int sz = sizeof(arr) / sizeof(arr[0]);
// int *p = &arr[0];//将arr[0]的地址存在*p中
// for (int i = 0; i < sz; i++)
// {
// printf("%d ", *p);//解引用来打印arr[0]
// p++;//打印完p++往后走一步,整型指针加一就是向后挪了一个整型
// //循环十次就能把这个数组的内容打印出来
// }
// return 0;
//}
//获取数组第一个数字的地址赋值给p,再利用*p解引用,打印*p所指的数
//p+1就是*(p+1),打印数组下一个数字
//1.指针类型决定了指针+1的步长,决定了指针解引用的权限
//2.数组在内存中是连续存放的
//int main()
//{
// int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
// int sz = sizeof(arr) / sizeof(arr[0]);
// int* p = &arr[0];//将arr[0]的地址存在*p中
// for (int i = 0; i < sz; i++)
// {
// printf("%d ", *(p + i));//直接解引用*(p+i),当i=0时,就是*p,打印的就是数组第一个数
//
// }
// return 0;
//}
////p+i 是跳过i*sizeof(int)个字节
//指针-整数,从10开始打印
//int main()
//{
// int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
// int sz = sizeof(arr) / sizeof(arr[0]);
// int* p = &arr[sz-1];//数组中最后一位的下标是sz-1
// for (int i = 0; i < sz; i++)
// {
// printf("%d ", *p );
// p--;
// }
// return 0;
//}
//指针-指针的绝对值是指针和指针之间元素的个数
//指针-指针,计算的前提条件是两个指针指向的是同一块空间
//int main()
//{
// int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
// printf("%d\n", &arr[9] - &arr[0]);//输出结果是9
// printf("%d\n", &arr[0]-&arr[9] );//输出结果是-9
// return 0;
//}
//size_t my_strlen(char* p)//传过来的是数组名,用字符串指针来接收,*p指向的就是数组第一个元素
//{//size_t是无符号返回值
// size_t count = 0;
// while (*p != '\0')
// {
// count++;
// p++;//往后走一位
// }
// return count;
//}
//int main()
//{
// char arr[] = "abcdef";
// size_t len = my_strlen(arr);//数组名其实是数组首元素的地址,arr==&arr[0]
// //传过去数组名,就是传过去首元素的地址
// printf("%zd\n", len);//打印结果是6
//
// return 0;
//}
//另一种写法
size_t my_strlen(char* p)//传过来的是数组名,用字符串指针来接收,*p指向的就是数组第一个元素
{//size_t是无符号返回值
char* star = p;//指向的是数组第一个数字
char* end = p;
while (*end!= '\0')//如果end不等于\0,就让end++
{//这个while的循环条件可以是while(*end),因为到了\0的时候,\0的ASCLL值就是0,不满足循环条件就停下来了
end++;//直到enf走到\0不满足条件就不进行循环了,此时的*end指向的就是\0
}
return end-star;//两个指针相减得到的就是指针之间元素的个数
}//数组中最后一个元素的指针减去第一个指针的元素得到的 就是这个数组的数量
int main()
{
char arr[] = "abcdef";
size_t len = my_strlen(arr);//数组名其实是数组首元素的地址,arr==&arr[0]
//传过去数组名,就是传过去首元素的地址
printf("%zd\n", len);//打印结果是6
return 0;
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };//数组中随着下标的增长,地址由低到高变化的
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr[0];//*p指向数组首个元素
while (p < &arr[sz])//数组下标为10的数不在数组之内,所以在arr[sz]前面的就是数组所有的元素
{//p的地址大小小于arr[sz]的地址,所以只要地址一直小于arr[sz]的地址就一直可以循环打印
printf("%d ", *p);
p++;
}
return 0;
}
//这里就运用到指针关系大大小p < &arr[sz]
7.assert断言
assert.h头文件定义了assert(),用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行,这个宏常常被称为“断言”
#define NDEBUG
int main()
{
/*int* p = NULL;
assert(p != NULL);*///会报错,assert判断后面括号的条件,为假就报错
int a = 10;
int* p = &a;
assert(p != NULL);//这种情况就不会报错
//assert可以判断指针的有效性
//#define NDEBUG,利用这句话就可以控制assert,是否产生效果
//如果想产生效果就注释掉,不想产生效果就在#include <assert.h>上方添加
//只要添加了#define NDEBUG这个语句,代码中的assert就会被禁用
//assert()语句的缺点就是,因为引入了额外的检查,增加了程序的运行时间
/*if (p != NULL)
{
*P = 200;
}
return 0;
}*/
//在vs版本中,Debug中assert()语句是可以使用的,但是在Release版本中直接优化掉了assert()语句
//这样debug版本编写代码有利于程序员排查问题,在Release版本不影响用户使用时的效率
//在Release版本选择性的优化assert()
8.指针的使用和传址调用
//strlen是求字符长度的,统计的是字符串中\0之前的字符个数
//函数求字符串长度
//参数s指向的字符串不期望被修改
size_t my_strlen(const char*s)//把字符元素的地址传过来,用char*s接收
{//添加const不希望字符串被修改,直接将每次传来的实参固定死
//不加const的话原先字符串的长度就被修改了
size_t count = 0;
assert(s != NULL);//防止传过来的实参为空指针,检测指针s是否有效
while (*s)//当s遇到\0的时候,循环就停止
{
count++;
s++;
}
return count;
}
int main()
{
char arr[] = "abcdef";
size_t len = my_strlen(arr);
printf("%zd", len);
return 0;
}
//写一个函数,交换两个整型变量的值
//void Swap1(int x, int y)
//{
// int z = 0;
// z = x;//先把x的值放到z里面,x空了
// x = y;//把y的值放到x里面,y空了
// y = z;//把z的值放到y里面去,在这之前放在z里面的值是x
//}
//
//
//int main()
//{
// int a = 0;
// int b = 0;
// scanf("%d %d", &a, &b);
//
// //交换a和b的值
// printf("交换前:a=%d b=%d\n", a, b);
// Swap1(a, b);
// printf("交换后:a=%d b=%d\n", a, b);
// return 0;
//}
//改代码打印结果是:
//交换前:a=3 b=5
//交换后:a = 3 b = 5
//很明显,出问题了
//当实参传递给形参的时候,形参是实参的一份临时拷贝,
//对形参的修改不会影响实参
//那么如何修改呢?
void Swap2(int *pa, int*pb)
{
int z = 0;
z = *pa;//z=a
*pa = *pb;//a=b
*pb = z;//b=z
}//脑海中把图画出来
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
//交换a和b的值
printf("交换前:a=%d b=%d\n", a, b);
Swap2(&a, &b);//把a、b的地址传过去
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
//在这两个代码中,Swap1是传值调用
//Swap2是传址调用,直接将变量本身传递过去了
//当我们采用的是传值调用,形参和实参占用的是不同的空间,对形参的修改不会改变实参
//完成两个整数的相加
Add(int x,int y)
{
int z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int c=Add(a, b);
printf("%d\n", c);
//传值调用
return 0;
}
//当使用传值调用时,实际上是将参数值复制到函数内部的一个局部变量中。
// 这意味着函数内部对参数值所做的任何修改都不会影响原始变量。
//原始数据不会被修改,传值调用通常被认为是安全的
//传址调用涉及将参数的内存地址传递给函数。这意味着函数可以直接访问和修改原始变量。
传值调用:实际上是将参数值复制到函数内部的一个局部变量中,这意味着函数内部对参数值所做的任何修改都不会影响原始变量,原始数据不会被修改
传址调用:涉及将参数的内存地址传递给函数,这意味着函数可以直接访问和修改原始变量。
野指针,assert,NULL
//野指针
//int main()
//{
// int a = 10;
// int* p = &a;//给一个明确的地址
//
// int* p2 = NULL;//给P2赋值为空指针
// //*p2 = 200;一旦将指针初始化就不能用这个指针了
// //只要指针是NULL就不能进行访问
// return 0;
//}//局部变量的地址返回就成野指针了
//
//
//
//#define NDEBUG//再头文件前面添加一个NDEBUG就能控制assert的运行,当我们添加了这么一句话,那么assert就无法运行
////起到了assert开关的作用,所以assert这个宏失效了
//#include <assert.h>
//
////assert()断言,用于在运行程序符合指定条件,如果不符合,就报错终止运行
//int main()
//{
// int* p = NULL;
// //检测有效性
// assert(p != NULL);//如果assert内的内容是真的,那么什么也不会发生的
// //在这里,因为p是空指针,所以与assert内的内容发生矛盾,会发生报错
// //if (p != NULL)//只有在p不为空指针才能被使用
// //{
// // *p=200;
// //}
// return 0;
//}
////assert会增加程序运行的时间
////assert是用来检测指针的有效性
//strlen是用来统计字符串\0之前的字符的个数
//这个函数是求字符串长度
//参数s指向的字符串不不指望被修改
//所以我们在*左边加上const控制住*s不能被修改
//不加const的话可能会被修改的
//size_t my_strlen(const char* s)//传过来的是数组首元素的地址
//{
// size_t count = 0;
// assert(s != NULL);//如果s为空指针会很可怕,这里我们就要用到assert,保证s不是空指针
// //如果实参传过来的是空指针这里的assert就会报错,告诉我们传来的是空指针
// //检测指针s是否有效
// while (*s)//如果*s检测的不是'\0'的话就持续运行,直到遇到'\0'就停止
// {
// count++;
// s++;//往数组后面走
// }
//}
//int main()
//{
// char arr[] = "abcdef";
// size_t len = my_strlen(arr);
// printf("%zd\n", len);
// return 0;
//}
//
//
//
//传值调用和传址调用
//写一个函数交换两个整型变量的值
/*void swap1(int x, int y)//不许要返回,只要能交换就行了
{
int z = 0;
x = y;
y = z;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("交换前a=%d,b=%d\n", a, b);
swap1(a, b);
printf("交换后a=%d,b=%d", a, b);
//交换a和b的值
return 0;
}*/
//输出结果:
//交换前a=3,b=5
//交换后a = 3,b = 5
//可见并没有达到交换的效果
//那么为什么没交换呢?
//通过调试我们可以知道x和y与a,b的地址并不一样,仅仅是拷贝过来的值而已
//当实参传递给形参的时候,形参是实参的一份拷贝,对形参的改变并不会改变实参
//改变思路,将地址传过去,通过地址访问且改变
//void swap1(int*pa, int*pb)//不许要返回,只要能交换就行了
//{
// int z = 0;
// z=*pa;
// *pa = *pb;
// *pb=z;
//
//}
//int main()
//{
// int a = 0;
// int b = 0;
// scanf("%d %d", &a, &b);
// printf("交换前a=%d,b=%d\n", a, b);
// swap1(&a, &b);
// printf("交换后a=%d,b=%d", a, b);
// //交换a和b的值
//
//
// return 0;
//}
//输出结果是:
//交换前a=3,b=5
//交换后a = 5,b = 3
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)