内存单元都有自己的编号,编号也叫地址,房间号就是地址,地址在C语言中又叫指针
一个内存单元:一个字节(8个bite)
变量创建的本质是在内存中申请空间
&(取地址操作符(属于单目操作符))在有多个字节时,取的是其中较小的地址
打印地址:%p
#include
int main()
{
int a = 0;
printf("%p ",&a);
return 0;
}
//&a取出的是a所占4个字节中地址较⼩的字节的地址。虽然整型变量占⽤4个字节,但是我们只要知道了第⼀个字节地址,就能访问到4个字节的数据。
用来存放指针(地址)的变量
通过*就可以通过地址找到地址所指向的对象
#include
int main()
{
int a = 10;
int* pa = &a;
//打印结果一样
printf("%p\n",&a);
printf("%p\n",pa);
//打印结果一样
printf("%d\n",a);
printf("%d\n",*pa);
return 0;
}
//也可以通过指针修改a的值
int a = 10;
int* pa = &a;//此处的*说明pa是指针变量
*pa = 20;//此处的*是解引用操作符(属于单目操作符),此处的*pa == a,*pa = 20就将a的值改为20了
地址的存放需要多大的空间,那么指针变量的大小就是多少
指针变量的大小与类型是没有关系的
32位平台下地址都是4个字节,即指针变量的大小是4个字节(x86环境)
64位平台下地址都是8个字节,即指针变量的大小是8个字节(x64环境)
指针的类型决定了,对指针进行解引用的时候有多大的权限(即一次能操作几个字节)
指针类型决定了指针向前或向后走几个字节
#include
int main()
{
int n = 10;
char *pc = (char*)&n;
int *pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc+1);
printf("%p\n", pi);
printf("%p\n", pi+1);
return 0;
}
//运行结果:
000000FE1A6FFA14
000000FE1A6FFA14
000000FE1A6FFA15
000000FE1A6FFA14
000000FE1A6FFA18
没有具体的类型(或者叫泛指针),
优点是:可以接受任意类型的地址,
局限性是:不能进行指针±整数和指针的解引用过程
#include
int main()
{
int arr[] = {1,2,3,4,5};
int* pa = arr;
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for(i = 0;i < sz;i++)
{
printf("%d ", *(pa + i));//此处的p+i就是指针+-整数
}
printf("\n");
return 0;
}
就是地址减去地址
指针减去指针时,两个指针一定是指向同一块区域的,所得到的绝对值,是两个指针之间的元素个数
#include
int my_strlen(char* s)
{
char* p = s;
while (*p != '\0')
{
p++;
}
return p - s;
}
int main()
{
int r = 0;
r = my_strlen("abc");
printf("%d",r);
return 0;
}
//结果为:3
指针的大小比较就是指针的关系运算(其实比较的就是地址的大小)
#include
void Print_arr(int* arr, int sz)
{
int* p = arr;
while (p < arr + sz)
{
printf("%d ",*p);
p++;
}
}
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,0};
int sz = sizeof(arr) / sizeof(arr[0]);
Print_arr(arr, sz);
return 0;
}
const
修饰指针const
修饰变量const
是常属性----不能改变的属性
const int a = 10;//a变成了常变量,本质还是变量,但被const修饰,语法限制a不能再改变
//以下方法可以改变,原理是绕过a,使用a的地址修改a
//但是使用了const修饰a,就是不想a再被改变,不应修改
int* p = &a;
*p = 1;
//而如果对指针变量修饰,就无法再修改了
const int* p = &a;
int a = 100;
int* p = &a;
*p = 0;
//p是一个指针变量,里面存的是地址
//*p是p指向的对象(a)
const
修饰指针变量const
可以放在*的左边,也可以放在右边,但是意义不一样,可以同时存在
const
放在*的左边,表示指针指向的内容不能通过指针来改变了,但是指针变量本身是可以改变的
const
放在*的右边,表示指针变量本身不能被修改了,但是指针指向的内容是可以通过指针变量来改变
int a = 100;
const int* p = &a;
*p = 10;//不能发生
p = &a;//可以发生
int a = 100;
int* const p = &a;
*p = 10;//可以发生
p = &a;//不能发生
指针指向的位置不可知(随机的,不正确的,没有明确限制的)
#include
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
//正确初始化:int* p = NULL;
*p = 20;
return 0;
}
#include
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int* p = arr;
int i = 0;
for (i = 0; i < 11; i++)
{
printf("%d ",*(p+i));
//当i=10时循环继续,p+i就超过数组的最大范围,造成了越界,
//一旦进行越界访问,p就成为了野指针
//为了避免中指针越界,在对指针使用之前,可以进行判断
//p = NULL;
//if(p != NULL)
//{
//语句;
//}
}
return 0;
}
#include
int* test()
{
int n = 100; //n是创建的临时变量,n所占的4个字节,当函数test返回后,n的空间 就返还给了操作系统
return &n; //此处返回的是临时变量的地址
}
int main()
{
int* p = test();
printf("%d\n", *p);
return 0;
}
aeeert.h
头文件定义了宏assert(),用于再运行时确保程序符合指定条件,如果不符合,就报错停止运行。这个宏就被称为断言。
断言可以关闭。
#define NDEBUG//用于关闭断言
#include
assert(p != NULL);//如果p不等于NULL,则继续执行,
//如果等于,终止运行,并且给出报错信息的提示
strlen
的模拟实现strlen
用于求字符串长度,统计的是字符串中\0之前的字符个数
#include
#include
int My_Strlen(char* str)
{
int count = 0;
assert(str);//断言str不为空
while (*str != '\0')//判断是否到\0,循环内计算\0之前的字符个数
{
count++;
str ++;
}
return count;
}
int main()
{
char* str = "abcdef";
int len = My_Strlen(str);//传的是首地址
printf("%d ",len);
return 0;
}
#include
void Swap(int x, int y)
{
int temp = 0;
temp = x;
x = y;
y = temp;
}
int main()
{
int a = 1;
int b = 2;
printf("交换前;a=%d b=%d\n", a, b);
Swap(a, b);
printf("交换后;a=%d b=%d\n", a, b);
return 0;
}
//结果为:
//a=1 b=2
//a=1 b=2
将上述代码运行后我们可以发现,我们写的交换函数其实并没有将a和b的值交换,这是因为实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参
刚才我们在进行交换a和b的操作中,并没有交换a和b,因为我们在传参的时候,传的是a和b的值,即传值调用,那么怎样才能交换a和b呢?
其实只要我们在传参的时候将a和b的值传过去就可以了,这种方式就叫做传址调用
#include
void Swap2(int* x, int* y)
{
int temp = 0;
temp = *x;
*x = *y;
*y = temp;
}
int main()
{
int a = 1;
int b = 2;
printf("交换前;a=%d b=%d\n", a, b);
Swap2(&a, &b);
printf("交换后;a=%d b=%d\n", a, b);
return 0;
}
//结果为:
//a=1 b=2
//a=2 b=1
数组名就是数值首元素的地址
//下面两行代码的结果是相同的
int* p = &arr[0];
int* p = arr;
数组名就是数值首元素的地址,但是有例外:
sizeof
(数组名)//单独放到sizeof
的括号中,数组名表示整个数组,计算出的是整个数组的大小
&数组名//这里的数组名也表示整个数组,取出的是这个数组的地址
&arr[0] //首元素的地址
arr //首元素的地址
&arr //数组的地址
&arr[0]+1 //首元素的地址
arr+1 //首元素的地址
&arr+1 //数组的地址
#include
int main()
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
int i = 0;
for(i = 0;i<sz;i++)
{
scanf("%d",p+i);
}
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
//printf("%d ", p[i];
}
return 0;
}
// int* p = arr;此处arr是数组首元素地址,赋值给了p,那么此处的arr与 p是等价的
//本质上p[i]等价于*(p+i)
//同理arr[i]等价于*(arr+i)
一维数组传参,函数的形参可以写成数组形式,也可以写成指针形式
void Fun(int* arr)
//此处的参数写成了数组形式,但是本质还是指针
void Fun(int arr[])
//此处参数写成指针行式
//两者等价
数组在传参的时候,不会将整个数组传递过去,传递的是数组首元素的地址,
所以在函数内没办法计算数组的元素个数,形参和实参的数组是同一个
核心思想:两两相邻的元素进行比较
#include
void Bubble_Sort(int* arr, int sz)
{
int i = 0;
//n个数字需要比较n-1趟
//每趟将会排序好一个数字,比较完成后,将较大的值置于数组末尾
for (i = 0; i < sz-1; i++)
{
int flag = 1;//假设已经有序了
int j = 0;
//sz-1之后再减i是因为每趟都会排序好一个数字,经过i趟后,
//所需排序的数字就减少了i个
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
flag = 0;//发生交换,证明无序
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
if(flag == 1)//第一趟没交换证明有序,后续就不用排序了
break;
}
}
printf("\n");
}
void Print_Arr(int* arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = { 3,1,7,5,8,9,0,2,4,6 };
int sz = sizeof(arr) / sizeof(arr[0]);
//排序前打印一次
Print_Arr(arr, sz);
Bubble_Sort(arr,sz);
//排序后打印一次
Print_Arr(arr, sz);
return 0;
}
指针变量也是变量,是变量就有地址,指针变量的地址就存放在二级指针中,
即二级指针变量是存放一级指针变量的地址的
int a = 10;
int* p = &a; //一级指针变量
int** pp= &p; //pp就叫二级指针,int*是说pp指向的p是int*类型的,
//*是说pp是指针变量,*pp=p,*p=a-->*(*pp)=a
//类似一级指针的
const int **pp
int * const *pp
int ** const pp
类比
字符数组:char arr[5]----存放字符的数组
整型数组:int arr[5]----存放整型的数组
那么指针数组就是存放指针(地址)的数组
#include
int main()
{
int a = 10;
int b = 20;
int c = 30;
int* arr[3] = { &a,&b,&c };//存放整型指针的数组
int** p = arr;//将数组arr的地址传给p
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d ", *(arr[i]));
}
printf("\n");
for (i = 0; i < 3; i++)
{
printf("%d ", *(*(p+i)));
//对(p+i)解引用得到*(p+i),
//*(p+i是a,b,c的地址,再次解引用得到*(*(p+i)),
//*(*(p+i))就是a,b,c内存中存储的对应的值10 20 30
}
}
//结果为:
//10 20 30
//10 20 30
#include
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 6,7,8,9,10 };
int arr3[] = { 11,12,13,14,15 };
int* arr[3] = { arr1,arr2,arr3 };
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
printf("%d ",arr[i][j]);
}
printf("\n");
}
return 0;
}