前言
本菜鸡开始了学习C语言的旅途。用这个来记录一下上课所学到的一些东西。仅作为自己的一个学习笔记。里面的内容也只是我对学到的东西的一些理解,可能有很多错误,大佬们勿喷。如果发现我理解的有哪些错误,欢迎评论区指出,希望大佬们不吝赐教。
6.数组与指针
数组名字的含义:
- 整个数组
- 在数组定义的时候表示整个数组
- 在使用sizeof的时候表示整个数组
- 在使用取地址符号时 &
-
首元素的首地址
其他
int a [4] ; // a 表示整个数据
printf("sizeof(a):%d\n" , sizeof(a)) ; // a 表示整个数组 。输出为16 4*整型
printf("%p\n" , &a ) ; // a表示整个数组的首地址
printf("%p\n" , &a+1 ) ; // a表示整个数组的首地址 + 1 表示怎加一个数组的单位 16字节
练习:
int a[10] = {0,1,2,3,4,5,6,7,8,9};
printf("a=%p a+1=%p &a+1=%p\n", a, a+1, &a+1);
这里的a就是数组首元素的首地址。 a+1就是a[1]的地址。&a+1:&a就是a的地址,指向的是a这个数组,再+1,就是加指向数据的长度,也就是加上一个数组a的长度。地址加了40字节。
即:a: &a[0]
a+1: &a[1]
&a+1: &a[10] //a[10]其实就是指向了a这个数组后面的那个地址,只是暂时这么称呼,实际中不能这么使用,这样是数组越界。
int b[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
printf("b=%p, b+1=%p, &b[0]+1=%p &b+1=%p\n", b, b+1, &b[0]+1, &b+1);
b就是数组首元素的首地址,即b[0]的地址(因为二维数组本质上还是一个一维数组,只是数组里面存的元素也是一个一维数组。)
b+1:b是b[0]的地址,所以b+1就是加一个b[0]的长度,也就是16字节,即变成了b[1]的地址。
&b[0]+1 : 这个和b+1是一样的,也是b[1]的地址。
&b+1 :&b是b这个数组的地址,指向的是b这个数组,所以+1就是加一个数组b的长度,即加了48个字节。
即
b: &b[0]
b+1: &b[1]
&b[0]+1: &b[1]
&b+1: &b[3] [0] //其实就是指向了b这个数组后面的那个地址,只是暂时这么称呼,即数组b的首地址加48个字节
int a[5] = {1,2,3,4,5};
int *ptr = (int *)(&a+1);
printf("%d %d\n", *(a+1), *(ptr-1)); //2 5
/*
ptr 保存了 &a[5] ptr 指向 a[5]
a+1: &a[1]
*(a+1): *(&a[1]) == a[1]
ptr-1: -1*sizeof(a[5]) ---> -4 --> &a[4]
*(ptr-1): *(&a[4]) == a[4]
*/
int a[5] = {1,2,3,4,5};
int *ptr = (int *)&a+1;//&a本来是指向a这个数组的,但是前面加了个int *,强制转化成一个指向int类型的指针,所以+1就是+4个字节。
printf("%d %d\n", *(a+1), *(ptr-1)); //2 1
/*
(int *)&a+1: +1*sizeof(int) --> +4
&a[1]
ptr保存了 &a[1] p->a[1]
ptr-1: -1*sizeof(int) ---> -4 ---> &a[0]
*(ptr-1):*(&a[0]) == a[0]
*/
7.多维数组与指针
二维数组其实就是一个一维数组,只不过一维数组中的元素又是一维数组
//假设有数组 int a[3][4] ---- 0代表第0行
/*
表达的含义 表达式的值
a(数组名) 1.代表整个数组 代表整个数组的空间 / &a[0]
sizeof(a)
2.指针
a[0](数组名) 1.代表整个数组sizeof(a[0]) 代表a[0]这一行数组的空间 / &a[0][0]
2.指针
&a[0][0] a[0][0]元素的地址 &a[0][0]
a+1 指向第1行数组的首元素 &a[1]
a->a[0] +1 --> 1*sizeof(a[0]) ---> 1*sizeof(int [4]) --> 16
&a[1] 指向第1行数组的首元素 &a[1]
&a 取数组整个空间的地址 &a
&a+1 整个数组a的下一个的地址 数组a的地址+ 1*sizeof(a)
a[1]+2 a[1]指针,指向第1行的首元素 &a[1][2]
a[1] -》 a[1][0]
+2 --》 2*sizeof(a[1][0])-->8
a[1]+2 指向第1行的第二个元素
*(a+1)+2 a[1]+2 指向第1行的第二个元素 &a[1][2]
&a[1][2] 指向第1行的第二个元素 &a[1][2]
*(a[1]+2) 第一行第二列的元素 a[1][2]
*(*(a+1)+2) 第一行第二列的元素 a[1][2]
a[1][2] 第一行第二列的元素 a[1][2]
*/
8.指针数组与数组指针
指针数组:是一个数组,数组里面的元素是指针。
int *p[4]
int a;
int *p[4];
p[0] = &a;
数组指针:是个指针,指向一个数组的指针。
int (*p)[4]; //p是一个指针,指向一个int [4]类型的数组
p+1 —> +1*sizeof(int [4]) --> +16
9.指针常量与常量指针
- 指针常量
int * const p;
const 修饰的是指针本身,表示P不能被修改,只能指向原本所指向的内容。
但是可以通过该指针来修改他所指向的内存内容。
// 指针常量
int a=5;
int b=10;
int * const p = &a;
printf("%d",*p); //输出5
p=&b; // assignment of read-only variable ‘p’ 报错!!!
// p 是一个常指针, 不能够再初始化后再修改他的指向
*p=b; //可以修改指向空间的内容。
printf("%d\n", *p );//输出10
2.常量指针
const int * p;或 int const * p
指向一个常量的指针,比较常见,用来限制指针的读写权限, 不可以通过该指针来修改他所指向的内存的内容,
但是可以修改他的指向。
// 常量指针
int a=5;
int b=10;
const int * p = &a;
printf("%d",*p); //输出5
*p=b; // assignment of read-only location ‘*p’ 报错
// p 是一个常量指针 不可以通过该指针来修改他所指向的内容
p=&b; //可以修改p的指向
printf("%d\n", *p );//输出10
如果我既不想改变指向,也不想改变指向空间的内容:
const int * const p = &a;
10.函数指针与指针函数
函数指针:本质是一个指针,是一个指向函数的指针。
1) :定义:指向的类型 *指针变量名 {=初始值};
如:
假设有一个sum函数
int sum(int x,int y)
{
return (x+y);
}
定义一个指针变量p,来保存sum的地址(指向sum函数),sum是什么类型?
typeof(sum) *p;
如何描述一个函数的类型呢?
函数类型:
函数返回值类型 (函数参数类型列表)
上面sum函数的类型: int (int , int)
定义一个指针p指向sum函数如下:
int (int, int) *p;
格式改变如下:
int (*p)(int ,int) = sum;
这个p就是函数指针,指向了一个返回值类型为int,有两个int类型参数的函数
p = sum;
2):怎么给函数指针赋值
p = 函数的地址;
函数的地址怎么获取?
&函数名 或者 函数名
p = sum;
p = & sum;
3):如何通过函数指针p去调用函数呢?
sum(3,5); //这是直接调用函数的方式。
p = sum;
p(3,5); //通过指针调用函数。
或:
p = & sum;
(*p)(3,5); ==> ( * &sum)(3,5);
函数指针调用函数的格式:
(*函数指针)(函数实参列表);
或者
函数指针(函数实参列表);
练习:有一个用来求一个一维数组的最大值,请利用函数指针调用它
#include<stdio.h>
int max(int a[],int n)
{
int max=a[0];
int i;
for(i=1;i<n;i++)
{
if(a[i]>max)
max=a[i];
}
return max;
}
int main()
{
int a[5]={12,4,78,6,13};
int (*p)(int * ,int)=max;
printf("%d\n",p(a,5));
return 0;
}
指针函数:返回值类型是指针的函数。
int *func(int a) //这就是指针函数,返回值是指针类型,但参数不一定是指针类型
{ int *p;
//....
return p;
}
11. 二级指针与多级指针
int a = 5;
我们定义一个指针变量p去保存a的地址:
int *p = &a;
我们要保存p的地址,那么应该怎么定义变量?
指针变量的定义格式: 指向类型 *指针变量名 {=初始值};
int **pp = &p;
二级指针:指向一级指针 的 指针 叫 二级指针
这个指针2保存了一个指针1变量的地址,该指针1是一个一级指针
我们要保存pp的地址,那么应该怎么定义变量?
int ** *ppp = &pp; //ppp就是三级指针
12.动态内存分配函数
malloc // calloc // realloc 动态内存分配
free 动态内存释放
malloc : memory allocate 内存分配
realloc: repeat allocate
malloc / calloc / realloc :是从一个叫堆空间的地方分配内存的
heap:堆空间,生存周期随进程持续,作用范围程序内有效
从堆空间上分配的内存,一旦分配,那么它就一直存在(可以被你合法使用),
直到你手动free或者程序结束,这块内存才被回收(你不可以使用了)
12.1 malloc函数
malloc ( 配置内存空间 )
头文件:
#include <stdlib.h>
定义函数:
void *malloc(size_t size);
参数分析:
size --> 需要申请内存空间的大小
返回值:
成功 返回一个指向该内存入口地址的指针
失败 返回NULL
示例:
int *p = (int *)malloc(120);
if( p == NULL )
{
printf("malloc fail\n");
return -1;
}
12.2 calloc函数
calloc ( 配置内存空间\并初始化为0 )
头文件:
#include <stdlib.h>
定义函数:
void *calloc(size_t nmemb, size_t size);
参数分析:
nmemb --> 需要申请内存的块数(多少个房间)
size --> 每一块内存的大小(房间大小)
返回值:
成功则返回一指针
失败则返回 NULL.
示例:
int *p = (int *)calloc(1, 120); //等价于 malloc(120)
if( p == NULL )
{
printf("malloc fail\n");
return -1;
}
12.3 realloc函数
realloc ( 重新配置内存空间 )
头文件:
#include <stdlib.h>
函数原型:
void *realloc(void *ptr, size_t size);
参数分析:
ptr --> 需要重新配置的原始地址
size --> 扩容后的大小
返回值:
成功 返回新内存的入口地址
失败 NULL
realloc用来把ptr指向的内存扩大到size个字节的大小,原来内容保持不变,后面新增一些内存
这些新增的内存不会初始化,但是如果后面没有足够的空间了(比如要将5字节的空间扩成10字节,但是后面这块空间已经被使用了,不能够扩大),那么系统就会重新分配一块10字节的空间给它,将原来的内容拷贝过来,再将原来的空间释放,realloc的返回值就是新内存的入口地址。
12.4 free函数
free ( 释放原先配置的内存 )
头文件:
#include <stdlib.h>
定义函数:
void free(void *ptr);
参数分析:
ptr --> 需要释放的内存的入口地址
返回值:
无
示例:
/*
free用来释放ptr指向的堆空间
ptr必须是指向堆空间
*/
void free(void *ptr);
free(ptr);
注意:malloc/calloc/realloc 分配的空间如果不使用了,一定要free,否则会造成内存泄漏
内存泄漏:不断的分配空间,空间不使用后,没有释放,找不到这个空间的地址,间接导致了不能释放
malloc/calloc/realloc 要和 free 成对出现
示例:如何使用动态分配出来的空间
char *p = (char *)malloc(100);
p[0] = 'a';
*p = 'a';
p[1] = '2';
*(p+1) = '2';
今日发现的一些问题
1.定义函数指针的时候一定要给*p加一个括号。
int (*p)(int ,int );
之前我一直是int *p(int ,int );就一直报错,找不到错的地方。
2.如果函数指针的函数参数有数组,定义函数指针的时候,函数参数类型就要写指针。
int max(int a[],int n;)
{
…
}
int (*p)(**int *** ,int );
之前我一直是int *p(int ,int ),也一直报错。后面发现数组在传参的时候就是传的数组首地址,是一个指针。