目录
一.内存
1.内存的定义
内存(Memory)是计算机的重要部件,也称内存储器和主存储器,它用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。它是外存与CPU进行沟通的桥梁,计算机中所有程序的运行都在内存中进行,内存性能的强弱影响计算机整体发挥的水平。只要计算机开始运行,操作系统就会把需要运算的数据从内存调到CPU中进行运算,当运算完成,CPU将结果传送出来。
计算机中常用的内存的大小是 4g/8g/16g
程序在运行前会先加载到内存中,也会使用内存。
2.内存的结构图
一个字节是8个bit位。
二.地址
1.什么是地址
地址是指内存中一个最小单元的编号。
2.什么是变量的地址
变量的存储空间的首字节的地址就是整个变量的地址。
一个例子:以int类型的变量为例子
三.什么是指针
1.指针的定义
指针指的是内存中一个最小单元的编号。
所以我们在提到指针的概念的时候可以认为指针就是地址。
四.如何获取数据存储空间的地址
1.&运算符
&地址运算符又称取地址运算符,&运算符的运算对象是变量,将变量数据的存储空间的地址叫做表达式的派生值。
在C语言中我们用%p来作为地址的转换说明。
那么此时我们获取到的相应类型的地址数据应该保存到什么类型的变量中呢?
五.指针变量
1.什么是指针变量(一级指针变量)
指针变量是一个变量,变量中的内容保存的是存储相应类型的存储空间的首字节地址。
2.指针变量的定义
#include <stdio.h>
int main(void)
{
int number = 10;
int* ptr_number = &number;
printf("number的地址:%p\n", &number);
printf("ptr_number中保存的数据:%p\n", ptr_number);
return 0;
}
*ptr_number表明ptr_number这个变量是一个指针变量,该变量中保存的数据是保存int类型数据的存储空间的编号,也就是地址。
此时就会有疑问,为什么我们通过变量名number也可以得到我们想要的数据,为什么还需要通过地址来访问存储空间,从而得到我们想要的数据。
3.指针变量的大小
在32位操作系统下 在64位操作系统下
为什么在不同位的操作系统下,同样的代码同样的指针变量却有不同的结果呢?
六.指针变量的大小
上面我们说到指针变量中保存的是存储空间的地址,那么指针变量在不同的平台下所占的字节数不同,也就说明了在不同的平台下我们的地址这个数据的大小不同。
对于32位平台下:
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0);
所以我们要保存32位bit位的地址数据,那么我们就需要用4个字节来保存。
所以在32位平台下指针变量的大小都是4字节。无论保存什么样类型的数据。
对于64位平台下同理。
七.使用指针变量
1.*运算符
*运算符又称解引用运算符(间接运算符),*运算符的运算对象是指针变量或地址,
通过它得到的表达式的派生值是地址中保存的相应类型的数据。
#include <stdio.h>
int main(void)
{
int number = 10;
int* ptr_number = &number;
printf("number=%d\n", number);
printf("*ptr_number=%d\n", *ptr_number);
return 0;
}
2.为什么需要使用地址
#include <stdio.h>
void Swap(int number_a, int number_b)
{
int number_tmp = 0;
number_tmp = number_a;
number_a = number_b;
number_b = number_tmp;
}
int main(void)
{
int number_a = 10;
int number_b = 20;
printf("两数交换前分别为number_a = %d number_b = %d\n", number_a, number_b);
Swap(number_a, number_b);
printf("两数交换后分别为number_a = %d number_b = %d\n", number_a, number_b);
return 0;
}
此时我们运行代码发现两数并没有交换:
原因就是此时我们传入函数的就是一个值而已,在我们主调函数中的值并没有改变。
解决办法:
上面我们提到了地址是存储空间的编号,那么我们将变量的地址传入到函数中,不就可以直接对存储空间中的数据进行更改了。
#include <stdio.h>
void Swap(int* ptr_number_a, int* ptr_number_b)
{
int number_tmp = 0;
number_tmp = *ptr_number_a;
*ptr_number_a = *ptr_number_b;
*ptr_number_b = number_tmp;
}
int main(void)
{
int number_a = 10;
int number_b = 20;
printf("两数交换前分别为number_a = %d number_b = %d\n", number_a, number_b);
Swap(&number_a, &number_b);
printf("两数交换后分别为number_a = %d number_b = %d\n", number_a, number_b);
return 0;
}
八.指针的运算
1.指针 +- 整数
int main(void)
{
int a = 10;
int* ptr_a = &a;
printf("a变量的地址是:%p\n", &a);
printf("ptr_a:%p\n", ptr_a);
printf("ptr_a + 1:%p", ptr_a + 1);
return 0;
}
从上图我们可以看出,指针+-整数并不是单纯的数值上面的累加,而是跳过整数倍个该类型所占的字节数。
int* ptr:
*ptr表明ptr变量是指针变量,所以这个变脸中保存的数据是一个地址,该地址所标识的存储空间中的数据是一个int类型的数据。
所以我们在声明一个变量是指针变量的时候,*表明这个变量是一个指针变量,而指明数据类型,是为了保证我们在处理指针变量的时候,结果的正确。
2.指针-指针
指针-指针的前提条件是两个指针变量指向同一段连续的存储空间上。不同的存储空间上计算出来的结果没有意义。
指针-指针的结果是两个地址之间的元素个数。
int main(void)
{
int numbers[10] = { 0 };
int* ptr_numbers_start = numbers;
int* ptr_numbers_end = numbers + 10;
printf("%d\n", ptr_numbers_end - ptr_numbers_start);
return 0;
}
3.指针的关系运算
int main(void)
{
int numbers[3] = { 1,2,3 };
int* ptr_numbers_start = numbers;
int* ptr_numbers_end = numbers + 3;
while (ptr_numbers_start < ptr_numbers_end)
{
printf("%d ", *ptr_numbers_start);
ptr_numbers_start++;
}
printf("\n");
return 0;
}
注意:
C语言中只保证了给数组分配空间时,指向数组后面的第一个位置的指针仍然是有效指针。不允许与指向数组第一个元素的前一个位置的指针进行比较。
九.野指针
1.什么是野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。
2.野指针的成因
没有对指针变量进行初始化
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
对空指针进行解引用
int main(void)
{
int* ptr = NULL;
*ptr = 11;
return 0;
}
3.如何避免野指针
#include <stdio.h>
int main()
{
int *p = NULL;
//....
int a = 10;
p = &a;
if(p != NULL)
{
*p = 20;
}
return 0;
}
十.指针和数组
先看一个例子:
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
通过观察上图我们可以得出结论:
数组名是数组首元素的地址。
十一.二级指针
1.什么是二级指针
二级指针是指一级指针变量的地址。
2.二级指针变量的声明
int ** pptr;
*pptr表明pptr这个变量是指针变量,这个变脸中保存的数据是一个地址数据,这个地址指向的存储空间中保存的数据是一个int*类型的数据。
十二.指针数组
1.什么是指针数组
顾名思义就是一个数组中的每个元素都是指针类型的。
2.指针数组的定义
int * ptrs[10];
ptr[10]表明此处是声明一个数组,该数组有10个元素,int*表明这个10个元素每一个都是int*的指针变量。
3.指针数组的使用
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组
十三.字符指针
1.什么是字符指针
保存char类型数据的存储空间的地址。
2.字符指针的使用
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
int main()
{
const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗?
printf("%s\n", pstr);
return 0;
}
对于代码 const char* pstr = "hello bit.";的解释:
”hello bit“是字符串字面量,这里”hello bit“表示的其实是字符串首元素的地址。这里是将这个字符串首元素的地址放到了pstr中。
一道笔试题:
#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char *str3 = "hello bit.";
const char *str4 = "hello bit.";
if(str1 ==str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if(str3 ==str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
这里首先函数声明了str1和str2两个字符数组,然后将字符串“hello bit.”拷贝到数组空间中,数组名表示数组首元素的地址,同时str1和str2又是两个不同的数组,所以str1不等于str2。但是对于str3和str4这两个指针数组来说,因为字符串字面量是保存在内存的常量区的空间是不会改变的,所以str3和str4所获取的地址是相同的。
十四.数组指针
1.什么是数组指针
首先数组指针是一个指针,这个指针是整个数组的地址。
2.什么是整个数组的地址
数组名表示的是数组首元素的地址,那么此时对数组名取地址,得到的就是整个数组的地址。
3.如何保存数组的地址
前面我们提到过指针也就是地址,要保存在指针变量中,那么数组指针也不例外,也因该保存在数组指针变量中。
4.数组指针变量的定义
#include <stdio.h>
int main()
{
int numbers[10] = { 0 };
int(*ptr_numbers)[10] = &numbers;
return 0;
}
首先*ptr_numbers表明ptr_numbers这个变量是一个指针变量,该指针变量的类型是一个有10个int元素组成的数组,该指针变量中保存的数据是一个10个元素数组的地址。
5.数组指针的使用
#include <stdio.h>
void Print(int (*ptr_numbers)[4], int r, int c)
{
int i_i = 0;
int i_j = 0;
for (i_i = 0; i_i < r; i_i++)
{
for (i_j = 0; i_j < c; i_j++)
{
printf("%d ", *(*(ptr_numbers + i_i) + i_j));
}
printf("\n");
}
}
int main(void)
{
int numbers[3][4] = { {1, 2, 3, 4}, {4, 5, 6, 7} ,{7, 8, 9, 10} };
Print(numbers, 3, 4);
return 0;
}
6.区别数组,指针数组,数组指针,数组指针数组
int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];
7.指针数组和数组指针数组的使用
#include <stdio.h>
int main(void)
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr3[10] = { 1,2,3,4,5,6,7,8,9,10 };
//整型指针数组
int* ptr[3] = { arr1, arr2, arr3 };
//数组指针数组
int(*ptrs[3])[10] = { &arr1, &arr2, &arr3 };
//遍历数据
//整型指针数组的遍历
int i_i = 0;
int i_j = 0;
for (i_i = 0; i_i < 3; i_i++)
{
for (i_j = 0; i_j < 10; i_j++)
{
printf("%d ", ptr[i_i][i_j]);
}
printf("\n");
}
printf("\n");
//数组指针数组的遍历
int i_z = 0;
for (i_i = 0; i_i < 3; i_i++)
{
for (i_j = 0; i_j < 10; i_j++)
{
printf("%d ", (*(ptrs[i_i]))[i_j] );
}
printf("\n");
}
printf("\n");
return 0;
}
十五.数组传参和指针传参
1.数组传参
一维数组:
#include <stdio.h>
void test1(int arr[10])
{}
void test1(int arr [])
{}
void test1(int* arr)
{}
void test2(int *arr[20])
{}
void test2(int* arr[])
{}
void test2(int **arr)
{}
int main(void)
{
int arr1[10] = { 0 };
int* arr2[20] = { 0 };
test1(arr1);
test2(arr2);
return 0;
}
二维数组:
#include <stdio.h>
void test(int arr[3][5])
{}
void test(int arr[][5])
{}
void test(int (*arr)[5])
{}
int main(void)
{
int arr[3][5] = { 0 };
test(arr);
return 0;
}
2.指针传参
一级指针传参:
#include <stdio.h>
void print(int *p, int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d\n", *(p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9};
int *p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);
return 0;
}
当一个函数的形数是一个一级指针的时候可以收什么样的实参呢?
数组名,变量的地址,一级指针变量。
二级指针传参:
#include <stdio.h>
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int*p = &n;
int **pp = &p;
test(pp);
test(&p);
return 0;
}
当一个函数的形数是一个二级指针的时候可以收什么样的实参呢?
二级指针变量,一级指针变量的地址,一级指针数组的数组名。
十六.函数指针
1.什么是函数指针
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
2.函数指针变量
void test()
{
printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
分析一下两段代码:
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);
代码1:
(void (*)())0是将0强转类型转换为函数指针,(*0)() 是调用函数。
代码2:
首先(*signal(int , void(*)(int)))是一个函数,该函数的返回值类型是void(int)。
代码2太复杂,如何简化:
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);
3.函数指针变量的使用
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
void (*ptr_test)() = test;
ptr_test();
return 0;
}
十七.函数指针数组
1.函数指针数组的定义
int (*parr1[10])();
2.函数指针数组的使用
函数指针数组的用途:转移表
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a*b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
while (input)
{
printf( "*************************\n" );
printf( " 1:add 2:sub \n" );
printf( " 3:mul 4:div \n" );
printf( "*************************\n" );
printf( "请选择:" );
scanf( "%d", &input);
if ((input <= 4 && input >= 1))
{
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = (*p[input])(x, y);
}
else
printf( "输入有误\n" );
printf( "ret = %d\n", ret);
}
return 0;
}
十八.指向函数指针数组的指针
1.定义
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
return 0;
}
十九.回调函数
1.定义
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。