以下仅记录小白的学习进程,若有缺陷,还望各位大佬指教
1、何谓指针?
可以理解为当我们创建一个变量a,编译器会在内存中申请一个空间存放这个变量a(请参照下列图形)这个存放变量a的空间在c语言中称之为”地址“,这个地址内会自动对地址进行编号(也可以理解为门牌号)。当我们知道了你的门牌号,就可以清晰的到访这个房间,而到访你房间的这一过程就称之为指针。
上述图片中的地址则是指针变量的大小
那指针该如何书写呢?
指针的书写形式:
类型+指针变量 = &目标地址 (int a =10;int *b=&a。表示地址的类型,表示指针变量)
请大家试着完善以下画横线的类型?
2、&和*是什么?(取地址以及解引用)
这里的int*b=&a(理解为找到了门牌号,去该房间的过程)*b存放的就是a变量。这个操作符将a的10修改为20.
接下来我将通过内存将创建和修改一并展示出来(我是按照代码的执行顺序进行板书)
将会创建一个变量a的地址,为0x0133F908(首元素的地址)
当代码执行到retrun 0;指针变量b中存放的是a的地址,通过解引用地址修改a中的元素。
指针变量大小:
1byte=8bite(一个字节是8个比特位)x86是32个bite位构成,x64是64个bite位构成
因为x64是64个比特位,一个字节是8个比特位,8个字节换算结果位64个比特位。
因为x86是32个比特位,一个字节是8个比特位,4个字节换算结果位32个比特位。
问题:既然x86中所有的类型是4,x64中所有的类型是8,那是否意味着类型可以相互通用呢?
先说结论:并不是!!!
观察上述的4张图片,会发现当代码执行到636行时,原来存储n的地址有一次性初始化4个字节,有初始化一个字节的。
(原因是int 类型是一次访问4个字节,char类型是一次访问一个字节)
总结:*就是解引用:一次访问多少个字符。 &是取地址:设置门牌号。
3、野指针
当一个变量未初始化,编译器会随机为该变量进行初始化值(该操作仅能在gcc类型的编译器可以使用例如devc++,小熊猫)当我们知道这一概念,指针也是如此。可以理解为随机给你一个门牌号,这个就是你的房间,这一过程称之为”非法访问“,为避免此情形,我们就需要将改变量置换为NULL(相当于把随机给你个房间号这一规则限制,只要NULL不结束,规则永不结束)
使用指针一定要初始化
指针越界访问:(超出数组定义的部分)
4、const常变量
#include<stdio.h>
int main( )
{
const int a =10;
a=20;
printf("%d",a);//猜测一下最终的结果是多少?
上述的结果为10,const相当于一把锁将变量a固定到这个房间内,假如我们要进入房间内把值修改,该如何操作呢?(通过将指针变量进行修改)可以理解为撬锁,绕过锁住的大门走小窗或者撬锁,这一过程都称之为”指针变量修改元素值“。
#include<stdio.h>
int main( )
{
const int a =10;//是一个常变量
a =20;//无法修改原参数值
int *x= &a;
*x = 20;
printf("%d",a);
const修饰指针变量(放置*的左边 const int * a =10;该指针不能指向其他地址,但是可以修改自己的地址。放置*的右边 int *const a = 10;该指针不得修改地址内容,但是可以指向其他指针。)
问题:假如我不想将a修改为20,我该将const放置在什么位置呢?
5、assert断言
实施条件(当条件为真,改代码将不会实现。条件为假,改代码将会实现)
头文件为(assert.h)
若不想使用可以在头文件上方定义一个#define NDEBUG
6、strlen的含义以及用法:
用法:strlen是求字符串长度的个数,以\0作为终止条件
含义:strlen是库函数,使用时需要包含string.h
#include <stdio.h>
#include<string.h>//头文件的包含
// 实现 strlen 函数
size_t my_strlen(const char *str) {
size_t length = 0;
while (*str != '\0') { // 遍历字符串直到遇到终止符
length++;
str++;
}
return length;
}
int main() {
const char *testStr = "Hello, world!";//字符串创建末尾编译器会自动加上\0.
printf("Length of \"%s\" is %zu\n", testStr, my_strlen(testStr)); // 使用 %zu 格式说明符打印 size_t 类型
return 0;
}
strlen的返回值为size_t(无符号型),%zu可以理解为size_t的专属打印方式。类似于你要打印整型数字,需要加上%d。
传值调用和传址调用:
该函数并没有将5和6的位置进行交换,原因是函数接受值,仅在函数内部实现交换,出了该函数编译器会将这部分空间回收,故答案为5和6.
#include<stdio.h>
void Swap(int a ,int b)
{
int temp=a;
a = b;
b = temp;
}
int main( )
{
int a = 5;
int b = 6;
printf("%d %d\n",a,b);//调用前
Swap(a,b);//传值调用,
printf("%d %d\n",a,b);//调用后
return 0;
}
传址调用:(指针内存放的是地址,上文我们将a的变量10通过指针取地址修改为20,这里也是同理)
解析:
1、主函数Swap传地址给Swap函数内
2、Swap指针进行接受变量(当前指针a和b内部存放的就是5和6这个内存地址)
#include<stdio.h>
void Swap(int* a, int* b)//指针变量接受
{
int temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int a = 5;
int b = 6;
printf("%d %d\n", a, b);//调用前
Swap(&a, &b);//传址调用
printf("%d %d\n", a, b);//调用后
return 0;
}
总结:需要修改主函数内的元素时,使用传址调用。不需要修改函数值时,使用传值调用。
7、指针+-整数
通过+整数,实现元素前移(-则是实现元素后移)。但是这些前进和后移的个数,取决于指针变量的大小。
比如:
int +1 一次只能访问4个字节、
float +1访问4个字节、
double +1访问8个字节、
char +1访问1个字节。
这一点和上文的指针变量的有点儿相似.当我们知道了指针+-整数与解引用时,我们就可以通过这俩者写出一个修改数组内容的代码。
补充知识点:sizeof(数组名),&arr这俩表示整个数组的地址,除上述的俩种,其余均表示首元素
#include<stdio.h>
int main ( )
{
int arr[5]={10,20,30,40,50};//整型数组一个元素是4个字节
int* c = arr;//arr表示首元素的地址
*c = 200;//通过解引用将首元素的地址取出来
*(c + 1) = 20;//指针+-整数,一次访问4个字节。取出第二元素
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d\n", arr[i]);//最后将每个元素打印出来
}
指针-指针(前提俩个指针必须指向同一块内存空间)
指针-指针算出的是元素个数(有点儿类似于前面的strlen的概念)
#include<stdio.h>
#include<string.h>
size_t My_strlen(char*p)
{
char *start = p;//起始指针 同时指向首元素arr
char *end =p;//末尾指针 同时指向首元素arr
while(end!='\0')
{
end++;
}
return end-start;
}
int main( )
{
char arr[]="abcdef";
size_t p = My_strlen(arr);//arr表示首元素个数;
printf("%zu\n",p);
return 0;
}
数组名的含义:
前文提到了除sizeof(数组名)和&arr,其他都是首元素。
解析:
1、第一个先判断是否为整个数组地址,指针+-整数,访问个数取决于( )
这里的c表示16进制数
1,2,3,4,5,6,7,8,9,A, B, C, D, E, F, G, H,I,J,K(中间差了4个字节)
2、第二个也是如此
3、判断为数组的地址,数组地址和首元素地址均是同一个地址,但是由于第三个是数组的地址,整个数组共有10个元素,一个元素占4个字节,4*10共40个字节。
#include<stdio.h>
int main( )
{
int arr[6]={1,2,3,4,5,6};
int *p= arr;
int sz=sizeof(arr)/sizeof(arr[0]);
int i =0;
for(i=0;i<sz;i++)
{
printf("%d",arr[i]);//arr[i] = *(arr+i)
printf("%d",*(arr+i));
printf("%d",*(i+arr));
printf("%d",i[arr]);
}
return 0;
}
一维数组传参的本质:(传的是首元素地址)
有的同学这里会说:既然函数传的是首元素地址,那就应该用指针进行接受啊!那这里写成数组的形式编译器为什么不报错呢?
在C语言中,当数组作为参数传递给函数时,它会退化为指向数组首元素的指针。这意味着在函数内部,无论你声明形参是数组还是指针,编译器实际上都会将其解释为指针。因此,虽然在函数定义中将参数声明为数组形式,但编译器仍然会将其视为指针,这就是为什么你的代码能够编译和运行的原因。解决完!!!
我们看一下sz1和sz2. 俩者打印的结果为多少呢?10?答案为10和1&&2
着重解析一下这里的1,因为函数接受的是首元素地址,x86中是4个字节,首元素地址为4
4/4=1;
当编译器为x64是,字节个数为8,8/4=2.
#include<stdio.h>
int test(int arr[10])//!!!
{
int sz2 = sizeof(arr) / sizeof(arr[0]);
printf("sz2 = %d", sz2);//???
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz1 = sizeof(arr) / sizeof(arr[0]);
printf("%d", sz1);//???
test(arr);//!!!
return 0;
}
二级指针:
书写形式:地址类型 *元素名 = &。
含义:
一级指针用来存放变量的地址,二级指针是用来存放一级指针变量的地址,当然三级指针也是如此。(可以理解为俄罗斯套娃,需要打开最外层的才能打开最里面的,一层一层相互制约,不得越界)
#include<stdio.h>
int main( )
{
int a =0;
int *p = &a;
int **pa = &p//这个就是二级指针
return 0;
}
指针数组:用来存放相同类型的值(请参照下列图片)
指针模拟二维数组的使用:
#include<stdio.h>
int main( )
{
int arr1[5]={1,2,3,4,5};
int arr2[5]={2,3,4,5,6};
int arr3[5]={3,4,5,6,7};
int *arr[ ] ={arr1,arr2,arr3};
int i =0;
for(int i =0;i<3;i++)
{
int j = 0;
for(j=0;j<5;j++)
{
printf("%d",arr[i][j]);//(*(arr+i)+j)
}
}return 0;
暂时完