1.内存和地址
CPU(中央处理器),在处理数据的时候,需要处理的数据是从内存中读取的,当数据处理完时又会放回到内存中。
内存会被划分为一个个的内存单元,每一个的内存单元大小都是一个字节。每个字节可以放八个比特位每个内存空间大小都有对应的编号,有了这个空间单元编号,CPU就能迅速找到一个内存空间,当然,在C语言中内存单元叫作地址。
2.指针的变量
2.1和地址
在C语言中创造变量,就是向内存申请空间
比如:创建一个整形变量,好比向内存申请四个字节大小,每个字节都有地址
2.2取地址操作符和解引用操作符
我们可以通过取地址的操作符(&)拿到地址的数值,并将这个变量它存储下,存放在指针变量中的值都会被理解为地址
int main()
{
int a = 10;
int* pa = &a; //取出a的地址
*pa = 0; // 把里面的地址取出来,当然也可以更改这个值
return 0;
}
可以看到a的值,进行了改变,由此可见,这个说法成立
2.3.指针变量的大小
32位平台下的地址就是32bit,指针变量的大小是4个字节
64位平台下的地址就是64bit,指针变量的大小是8个字节
在指针变量的大小与类型是无关的,管他是什么整型,只要是在相同的平台下都是一样的字节4/8
3.指针变量类型的意义
指针的类型决定了,对指针解引用的地址一次又多大的权限(一次能操作多少字节)
3.1 指针的解引用
//代码1
#include <stdio.h>
int main()
{
int n = 0x11223344;
int *pi = &n;
*pi = 0;
return 0;
//代码2
#include <stdio.h>
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;
*pc = 0;
return 0;
}
调试可以看出来,代码1会将n的4个字节全部改回0,而代码2会将n的第一个字节改为0别的不变。
3.2 指针运算
1.指针+整数
2.指针-指针
3.指针的关系运算
1.指针+整数 指针的类型决定了指针向前或者向后走多大的距离
(举例:当类型是int*类型是4个字节,加1便是4位的距离同理,
char*类型是1个字节,加1便是加1个字节)
2.指针-指针 可以理解成找到俩个指针然后进行相减
void my_strlen(char* s)
{
char *p = s;
while(*p != '\0');
{
p++;
return p-s;
}
}
int main()
{
printf("%s ",my_strlen("abc"));
return 0;
}
3.指针的关系的运算:是对用操作符进行指针的变量的比较。
3.3 void* 指针
在指针类型中void*是一种特殊的类型,可以接收任意指针类型的指针(简称万能桶)但他无法直接进行指针计算。
4. const修饰指针
变量是可以修改的,如果我们不想将这个变量进行修改的话,就需要用到const来修饰指针
//代码1:
void test1()
{
int n = 20;
int m = 30;
const int* p =&n; //这会是修饰*左边整个整数指针的,没有修饰*右边的式子
*p = 20; //err //所以这是式子是执行不了的
p = &m; //ok //这个没有被修饰到,所以可以使用
}
//代码2:
void test2()
{
int n = 10;
int m = 20;
int const * const p = &n // 当这俩个式子*左右俩边都被修饰到后,就都变成了不可修改式子
*p = 20; //err
p = &m; //err
}
结论:const修饰指针变量的时候
const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不被改变。
但是指针变量本⾝的内容可变。
const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指
向的内容,可以通过指针改变。
5.assert断言
平时要是为了确保程序的符合指定条件,不符合就会报错,中止程序运行,我们一般会用到一个叫宏定义assert一个头文件,用来测这个程序的严谨性。
优点:假设他报错便会直接跳转到报错的那一行,对程序员十分的友好的,但他缺点也十分明显就是会增加程序运行的时间,加入了额外的检查
#define NDEBUG
#include <assert.h>
上面这个表示可以对这个assert暂用或者换种说法先注释他不执行这个行代码,当然这个是只能在debug版本下才能运行
6.指针的使⽤和传址调⽤和传值调用
//将a,b整数变量值进行交换
//传值的调用
void swap(int a,int b)
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int a = 10;
int b = 20;
printf("交换前:a = %d b = %d",a,b);
swap(a,b);
printf("交换后:a = %d b = %d",a,b);
return 0;
}
当程序进行传值调用的时候,运行结果时俩者俩个整形并没进行交换,是因为:实参的地址和形参的地址不同(无法进行交换),只是把值给传过来,临时空间接收实参罢了
但将传值调用换成传址调用 把整个地址跟主函数建立关系便能使用程序就成功运行了
结论:
传值调用: 实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参。
传址调用: 便是可以让函数和主函数建立真正得关系,可以在函数中修改主函数的变量。
7.野指针
概念:野指针指向的为位置是不可知的(随机的,不正确,没有明确限制),没有初始值,指针越界访问、指针指向的空间释放
最好将指针使用后,在添加一个NULL的符号,用来有效避免野指针的出现
8.数组名的理解
数组名就是首元素的地址
注意:
sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩,
单位是字节
• &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素
的地址是有区别的)
使用指针访问数组
可以理解成把地址给传址过去访问数组,如图所示
一维数组的传参的本质
本质来说,一维数组的传参的本质传的就是首元素的地址
并且⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。(二者都可以)
二级指针
指针的变量也是变量,是变量也就有地址,那地址变量的地址就叫二级指针。
int main()
{
int a = 0;
int* p = &a; //将取出a的指针变量地址放在*p里面
int** pa = &p; //这里便表示a地址要发生改变的话,就要再存储别的地址上,再整个级别高一点地方
return 0;
}
指针数组
整数数组:存放整数的数组
字符数组:存放字符的数组
指针数组:存放指针的数组(数组的每个元素的都是指针)
用指针数组模拟实现二维数组
数组指针变量
定义:
• 整形指针变量: int * pint, 存放的是整形变量的地址,能够指向整形数据的指针。
• 浮点型指针变量: float * pf, 存放浮点型变量的地址,能够指向浮点型数据的指针。
• 数组指针变量:int(*p)[] 存放数组变量的地址,能够指向数组的指针变量。
数组指针变量初始化
俩者俩个指针变量类型都是一样的。
二维数组传参的本质
定义:
在二维数组的首元素就是第一行,是一个一维数组
本质上传的是地址,实际是传递一维数组的地址
函数指针变量
函数也是有地址的,函数名就是函数的地址,也可以用&函数 来进行表示
int(*p) (int x,int y).....p指定函数的参数类型和个数交代
. .
. .
. .
. .
. .
. 函数指针变量的名字
.
.
p指向函数返回类型
函数指针变量的使用
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*p)(int, int) = Add;
printf("%d\n", (*p)(2, 3)); // 俩种表达式都可以哈!!!
printf("%d\n", p(3, 5)); // 指针变量的名字就是首元素的地址
return 0;
}
typedef关键字
typendef是用来类型重命名的,可以将复杂的类型,简单化。
比如 ,要是觉得unsigned int 写起来不方便
就可以改成 typedef unsigned int uint;
之后你使用一个uint 就能代表整个式子
指针类型也是可以修改名字哈,列如
typedef int* ptr_t
之后用一个ptr_t 就可以代表指针类型
回调函数
回调函数就是通过函数指针调用的函数
如果把函数的指针作为参数传递给另一个函数,当整个指针被用来调用所指向函数,被调用的函数就是回调函数
回调前:
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
int mul(int x, int y)
{
return x * y;
}
int div(int x, int y)
{
return x / y;
}
void menu()
{
printf("*****************\n");
printf("**1.add 2.sub**\n");
printf("**3.mul 4.div**\n");
printf("*** 0.exit ***\n");
}
int main()
{
int x,y = 0;
int input = 0;
int ret = 0;
menu();
printf("请选择:\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输入操作数:\n");
scanf("%d %d", &x, &y);
ret = add(x,y);
printf("ret = %d\n", ret);
break;
case 2:
printf("输入操作数:\n");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
printf("输入操作数:\n");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %d\n", ret);
break;
case 4:
printf("输入操作数:\n");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %d\n", ret);
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入有误,请重新输入\n");
break;
}
return 0;
}
回调后:
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
int mul(int x, int y)
{
return x * y;
}
int div(int x, int y)
{
return x / y;
}
void menu()
{
printf("*****************\n");
printf("**1.add 2.sub**\n");
printf("**3.mul 4.div**\n");
printf("*** 0.exit ***\n");
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int (*p[5])(int x, int y) = {NULL,add,sub,mul,div };
do {
menu();
printf("请选择:\n");
scanf("%d", &input);
if (input <= 4 && input >= 1)
{
printf("输入操作数:\n");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
printf("ret = %d\n", ret);
printf("\n");
}
else if (input == 0)
{
printf("退出计算\n");
}else
{
printf("输入有误,请重新输入\n");
}
} while (input);
return 0;
}
qsort函数使用举例
函数实现排序
#include <stdio.h>
//qosrt函数的使⽤者得实现⼀个⽐较函数
int int_cmp(const void * p1, const void * p2)
{
return (*( int *)p1 - *(int *) p2);
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
{
printf( "%d ", arr[i]);
}
printf("\n");
return 0;
}
qsort函数模拟实现
print(int arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int int_cmp(const void* p1,const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
void Swap(char* buf1, char* buf2, size_t sidth)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++; //为啥这里不需要再用*buf1++?
buf2++; // 因为这里只是为指针向后移动一位,不能指针所指向的元素内容
}
//接下来是qsort模拟的实现函数
void bubble_sort2(void* base, size_t sz, size_t width, int(*cmp)(const void* p1, const void* p2))
{
for (int i = 0; i<sz - 1; i++)
{
for (int j = 0; j<sz - 1 - i; j++)
{
if (cmp((char*)base + j *width,(char*)base + (j+1) * width)> 0)
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
void test1()
{
int arr[] = { 6,2,4,5,3,7,9,8,1,0 };
int ret = sizeof(arr) / sizeof(arr[0]);
print(arr, ret);
bubble_sort2(arr, ret, sizeof(arr[0]), int_cmp);
print(arr, ret);
}
int main()
{
test1();
return 0;
}
到你看到这里,想必你也是很认真的看了本章博客,非常感谢您的阅读,后面我也会尽心尽力的给大家带来好的博客。