1,引言
int data = 10;
定义一个变量,名字叫做 data,是int类型,所以分配了4字节的内存,并且这4字节中存储的值是10 data和这4字节对应起来了
printf("%d\n",data); 从 data对应的4字节内存中取出数据,并打印出来 data = 30; 把30这个数据存储到 data对应的4字节内存中去 对一个变量的访问,总的来说就是两种:读 和 写 无论是读还是写,都需要找到这个变量对应的内存。 在计算机中,为了方便对内存的管理,以字节为单位对内存进行了编号,这个编号我们称之为地址。 一个int类型的变量占4字节,其实对应了4个编号,我们平常讲的地址是指4个字节中最小的那个编号 scanf("%d",&data); 把从键盘输入的一个整数存储到以 &data这个编号/地址开始的4字节内存中
2,指针
指针是一种数据类型
int 这种数据类型是用来保存整数的
double这种数据类型是用来保存小数的
char这种数据类型是用来保存字符的
指针这种数据类型是用来保存地址的
如果一个指针类型的变量(p)保存了某个变量的地址(data),我们就说p指向data。 我们可以通过p找到data并进行访问。 定义指针变量的语法: 基类型 * 指针变量的名字;//只定义没有初始化 或者 基类型 * 指针变量的名字 = 某个地址;//定义并初始化 说明: 基类型是指该指针变量将要指向的那个数据的类型 -》如果该指针变量将要指向一个int类型的变量,那么这个基类型就是int 如果该指针变量将要指向一个double类型的变量,那么这个基类型就是double ...... int data = 10; 定义一个变量用来保存data的地址 int * p = &data;//定义一个指针变量 p 并保存了 data的地址 或者 int * p;//定义一个指针变量 p p = &data;//把 data的地址赋值给指针变量 p 上面两种写法最终的效果是一样的 -》 p指向data 地址/指针变量可以用 printf("%p",....);打印 指针变量占多少字节? 只和机器有关,如果是32位机器,指针变量就是4个字节 2^32种情况,2^32字节 -> 4G内存 如果是64位机器,指针变量就是8个字节 2^64字节 -> 2^32*2^32 -> 2^32 * 4G内存 和它的基类型无关
3, * 运算符
* 运算符 读作 指向运算符/解引用运算符
指向运算符*是一个单目运算符,只需要一个操作数,这个操作数写在 *的后面, 这个操作数是一个地址或者是一个指针变量 *(地址/指针变量) 这个表达式的含义是这个地址对应的那个对象/是这个指针指向的那个对象 对象:可以是变量、函数、数组... 例如: int data = 20; *(&data) 合法的表达式 。 含义是 &data 这个地址对应的那个对象 :data int * p = &data;//这个 *不是指向运算符,只是一个标识,表示这个变量p是指针类型 *p 合法的表达式 。含义是 p指向的那个对象 : data *p = 30;//这个表达式有两个运算符 * = ,*的优先级高 -》 (*p) = 30; //-> 把30这个数据赋值给 *p这个表达式 //-> 把30这个数据赋值给 data printf("%d\n",*p);//打印 *p这个表达式的值 -》 打印data的值 scanf("%d",&data); scanf("%d",p);//和上面一样的效果 练习: 写一个函数,用来交换两个整数的值 void swap(int *p,int *q) { int * r; //以下三行代码交换的是 p和q的值 //不能实现交换 a和b的值的功能 r = p; p = q; q = r; } //正确 void swap(int *p,int *q) // int * p = &a; { int temp; temp = *p; *p = *q; *q = temp; } int main() { int a=10,b=20; swap(&a,&b);//传的是地址,但是不能说传的是实参的地址 }
4,空指针和野指针
空指针 NULL (值是0) ,表示不指向任何一段内存。
int * p = NULL;
*p = 10;//错误,不能把10存储到一块不存在的内存
野指针是指指向一块不能访问/不属于当前进程的内存 int *r; 目前为止 r是野指针,因为没有给r赋值,r就是一个随机值,这个随机值指向的内存大概率是不能访问的 *r = 某个值; //错误的,把某个值存储到 r指向的内存,但是r指向的这块内存是随机的 //非法访问 ----------------- int *r; if(1) { int data = 20; r = &data;//此时r指向 data,可以通过 *r 去访问 data printf("%d\n",*p);//正确的,打印 20 } //此时 r指向原来的data,但是data已经被销毁了(这块内存不再属于当前进程了) //所以此时 r也是野指针 printf("%d\n",*p);//错误的,非法访问 void swap(int *p,int *q) { int *r; *r = *p;//错误,r是野指针 *p = *q; *q = *r; }
5,一维数组与指针
数组各个元素都有自己的地址,并且各个元素之间的存储地址是相邻的
int a[5] = {1,2,3,4,5}; 共有5个元素: a[0] a[1] a[2] a[3] a[4] 每个元素都有自己的地址,可以这么表示: &a[0] &a[1] &a[2] &a[3] &a[4],是相邻的(相差4) 请你定义一个变量保存 a[0]的地址: int * p = &a[0];//指针变量p保存了a[0]的地址,-》 p指向a[0] 在一维数组中,数组名可以当作首个元素的地址,但是当作首个元素的地址的时候, 是一个指针类型的常量(不可修改) a 可以当作一个指针类型的常量(不可修改),和 &a[0]的值相同,和p的值也相同。 p是变量可以修改 int *p = a;//指针变量p保存了a[0]的地址,-》 p指向a[0] p = &a[1];//正确,现在指向a[1] a = &a[1];//错误,因为 a是常量 测试打印可以发现: a[1] 的地址比 a[0] 的地址数值上大4 &a[1] - &a[0] = ? 并不等于4,而是等于1 指针类型的数据做加减法,和普通的整数加减不一样,有一套自己的规则: p+i (p是指针,i是整数),不是简单算术运算,而是加减 i个指针p的步长。 指针的步长:就是指这个指针的基类型所占内存长度 int * p1;//p1的步长是4 double *p2;//p2的步长是8 ...... p,q都是指针,并且基类型相同 p-q ,结果是数值差除以步长 p+q 没有意义,乘除也没有意义 练习: int a[5] = {1,2,3,4,5}; int *p1 = &a[0]; int *p2 = &a[3]; p1+2 -> &a[2] p2-2 -> &a[1] p2-p1 -> 3 所以: int a[5] = {1,2,3,4,5}; int *p = a;//int *p = &a[0]; p+1 -> &a[1] a+1 -> &a[1] *(p+1) <-> *(a+1) <-> *(&a[1]) <-> a[1] p+2 -> &a[2] a+2 -> &a[2] *(p+2) <-> *(a+2) <-> *(&a[2]) <-> a[2] ..... -> *(p+i) <-> *(a+i) <-> *(&a[i]) <-> a[i] 所以:访问数组元素除了下标法之外,还可以用指针法 a[i] *(a+i) ,这两种方法无条件等价
6,数组作为函数参数
一般传两个参数,一个是数组的首地址,一个是数组元素个数
int array_sum(int a[],int n) //等价 int array_sum(int *a,int n)
{
int sum=0;
int i;
for(i=0;i<n;i++)
{
sum += a[i]; //等价 sum += *(a+i);
}
return sum;
}
无论是哪中写法,形参a都不是一个数组,而是一个指针,主调函数传参时把数组的首地址传给形参a
int main() { int a[] = {12,3,4,7,10,-12,34,19}; int n = sizeof(a)/sizeof(int); array_sum(a,n);//实参a在此表示数组的首地址/首个元素的地址 return 0; }
7,数组名的含义
int a[6] ;
数组名,在c语言中有两种含义:
(1)代表整个数组
以下这么几种情况
sizeof(a) ->求数组a所占内存大小
typeof(a) ->求数组a的类型 -> int [6]
&a ->求数组a的地址 数值上和 a 、&a[0]相等,但是含义不同,步长不同
(2) 代表数组的首地址
首个元素的地址,并且是常量,不可修改
形参 = 实参
8,二维数组和指针
二维数组 int a3;
有3行4列,共12个元素,每个元素都是 int类型,在内存按顺序储存。
实际上二维数组的本质也是一维数组:
有3个元素(一行为一个元素),分别这么表示: a[0] a[1] a[2], 都是 int [4]数组类型 (都是有4个int类型元素的数组类型)
我们可以这么理解: a[0] ,a[1] ,a[2] 既是二维数组的元素,又是一个数组名
a[0] 是一个数组名,有 a0 a0 a0 a0 四个元素
a[1] 是一个数组名,有 a1 a1 a1 a1 四个元素
a[2] 是一个数组名,有 a2 a2 a2 a2 四个元素
请问二维数组名a怎么理解? (1)代表整个数组 sizeof(a) typeof(a) &a (2)代表数组的首地址(首个元素的地址) : &a[0] a[0],a[1],a[2]作为数组名,怎么理解? (1)代表整个数组 (2)代表数组的首地址(首个元素的地址) a[0] -> &a[0][0] a[1] -> &a[1][0] a[2] -> &a[2][0] 练习: int a[3][4]; 假设二维数组a的首地址为 0x10066 求: printf("%p\n",a);//0x10066 printf("%p\n",&a);//0x10066 printf("%p\n",&a[0]);//0x10066 printf("%p\n",&a[0][0]);//0x10066 printf("%p\n",a+1);//0x10066 + 0x10 = 0x10076 a在这里表示 a[0]的地址 :&a[0] ,步长为16
printf("%p\n",&a+1);//0x10066 + 0x30 = 0x10096 a在这里表示整个数组,&a的步长是 48 printf("%p\n",&a[0]+1);//0x10076 &a[0]的步长是16 printf("%p\n",&a[0][0]+1);//0x10066 + 4 = 0x1006a &a[0][0]的步长是4
*(a+i) <-> *(&a[0]+i) <-> *(&a[i]) <-> a[i] *(*(a+i)+j) <-> *(a[i] + j) <-> *(&a[i][0] + j) <-> *(&a[i][j]) <-> a[i][j] 总结:不管是一维数组还是二维数组,元素的访问都有两种方式,下标法和指针法 对于一维数组, a[i] <-> *(a+i) 对于二维数组, a[i][j] <-> *(*(a+i)+j)
9,指针数组和数组指针
指针数组是一个数组,它的元素是指针类型
怎么定义?
数据类型 * 数组名[元素个数];
数据类型可以是任何合法类型
比如: int * a[3];
定义了一个数组a,有3个元素,每个元素都是 int * 类型
sizeof(a) = 24
double *b[5]; 定义了一个数组b,有5个元素,每个元素都是 double * 类型 sizeof(b) = 40 有什么用? 保存多个地址
例子: int a[9] = {1,2,3,4,5,6,7,8,9}; int *p[3] = {&a[0],&a[3],&a[6]}; //数组p有三个元素, p[0] ,p[1],[2],都是 int * 类型 //p[0] 指向 a[0], p[1]指向a[3] ,p[2]指向a[6] printf("%d,%d,%d\n",*p[0],*(p[1]+2),*(p[2]+1));//1,6,8 printf("%d\n",*(*(p+1)+2) );//6 *(*(p+1)+2) <-> *(*(&p[0]+1)+2) <-> *(*(&p[1])+2) <-> *(p[1]+2) <-> *(&a[3]+2) <-> *(&a[5]) <-> a[5] printf("%d\n",p[1][2]);//同上 p[1][2] <-> *(*(p+1)+2)
数组指针是一个指针,这个指针的基类型是一个数组(它指向某个数组) 指针变量:就是用来保存别的对象的地址 别的对象是 int类型,这个指针就是 int *类型 别的对象是 double 类型,这个指针就是 double *类型 ... 别的对象是数组,这个指针就是 数组 * 类型 作用是什么:就是用来保存一个数组的地址 怎么定义? 基类型 * 指针变量名; 基类型是数组类型,怎么表示? 如: int a[4]; 数组a的类型怎么表示? typeof(a) 或者 int [4] -> typeof * p; 或者 //int [4] *p;//c语言标准委员会觉得这样不好看,改成下面的写法 int (*p) [4]; 怎么保存数组地址? p = &a; 数组指针保存一个一维数组的地址,应用场景有限,常用的是用数组指针保存二维数组的首地址 例如: int b[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12}; //二维数组有 3个元素,分别是 b[0] b[1] b[2],都是 int [4]类型 int (*p)[4]; p = b; 或者 p = &b[0]; printf("%d\n",p[2][3]); 结论: p[2][3] <-> *(*(p+2)+3) <-> *(*(&b[0]+2)+3) <-> *(*(&b[2])+3) <-> *(b[2]+3) <-> *(&b[2][0]+3) <-> *(&b[2][3]) <-> b[2][3]
二维数组作为函数参数,一般也是传2个参数,第一个参数是二维数组的首地址(a[0]的地址,不是a[0][0]地址), 第二个参数是元素个数(行大小) int array_sum(int (*p)[3] ,int n)//等价 int array_sum(int p[][3] ,int n) { int i,j; int sum=0; for(i=0;i<n;i++) { for(j=0;j<3;j++) { sum += p[i][j]; } } return sum; } int array_max_value(int (*p)[3] ,int n) { int i,j; int max=p[0][0]; for(i=0;i<n;i++) { for(j=0;j<3;j++) { if(max < p[i][j]) max = p[i][j]; } } return max; } int main() { int a[4][3] = {.....}; array_sum(a,4); //a <-> &a[0] -> 数组指针 }
练习: 写一个函数,求二维数组的最大值
二维数组这样传参,通用性不够:如果数组的列是不同的,就没办法传了 可以进行改进: 传三个参数,第一个参数是,二维数组第一个元素的地址(不是a[0]的地址,而是a[0][0]的地址) 第二个、第三个参数就是行和列 int array2_max_value(int *p ,int n,int m) { int i,j; int (*q)[m];//定义一个数组指针 q = (typeof(q))p;//把 p强转赋值给q int max=q[0][0]; for(i=0;i<n;i++) { for(j=0;j<m;j++) { if(max < q[i][j]) max = q[i][j]; } } return max; }
10, const关键字
修饰普通变量,被修饰的变量的值不能修改
如: const int data = 10; //const和类型int的先后顺序无所谓
...
data = 20;//错误
const int data;//因为如果不初始化,后面又不能给它赋值,那就没有意义,建议必须初始化 修饰指针变量,有这么几种情况: int * const p; 和修饰普通变量一样,表示p不能被修改,只能一直指向一个固定的对象 int const * p;//const int * p; const不是修饰 p的,p是可以修改的,const的作用是指:不能通过p去修改它指向的对象 一般会用于函数的形参,表示该函数只需要对这个指针指向的对象进行读访问,不需要进行写访问, 加上 const就可以保护数据(避免被不小心修改) int const * const p; 上面两种情况的综合,也就是说 p不能被修改,也不能通过p去修改它指向的内容
11,字符串与指针
所谓的字符串,就是一串字符,c语言中并没有字符串这个类型,那么c语言中如何表示/保存字符串呢?
1,用字符数组保存
原理:每个元素保存字符串的一个字符
2,用字符类型的指针保存
原理:只保存字符串的首地址(第一个字符的地址),从这个地址依次往后面找,直到遇到'\0' 从第一个字符开始到'\0'结束,这个范围里的所有字符就是完整的字符串
"" 引起来的表示一串字符串,并且最后一个字符默认为 '\0' "" 引起来的这个表达式的值就是该字符串第一个元素的地址 语法: char * 指针变量名 = "字符串"; 例如: char * p = "hello"; 定义了一个指针变量 p,保存了 字符串首个字符 'h'的地址 这种形式的字符串保存在内存的一个特殊的区域, .rodata区域( read only data ),保存 在这个区域的数据只能读,不能写。 这和用字符数组保存字符串有本质的不同 例如: char * p = "hello";//"hello"这6个字符(包括'\0')保存在 .rodata区域,不能修改的 *p = 'H';//错误 printf("%c\n",*p);//正确,打印 'h' *(p+1) = 'E';//错误 printf("%c\n",*(p+1));//正确,打印 'e' char s[] = "hello";//"hello"这6个字符(包括'\0')保存在数组种(栈区),可读可写 s[0] = 'H';//正确 printf("%c\n",s[0]);//正确 sizeof(p) = ? 8 sizeof(s) = ? 6
12,几个常用的字符串处理函数(c语言标准库中的函数)
(1) strlen :用来求字符串的长度
#include <string.h> //要用字符串相关函数,需要包含这个头文件
size_t strlen(const char *s); //函数的声明,可以看出参数个数类型及返回值类型
s:要求长度的那个字符串的首地址
返回值: (size_t 就是int)
返回字符串的长度
char s[] = "hello"; int len = strlen(s); char * p = "hello"; len = strlen(p); strlen("hello"); (2) strcpy / strncpy :复制字符串,把一个字符串的内容复制到另一个字符串中 char s1[10] = "strlen"; char s2[10] = "hello"; //s1 = s2;//语法错误, s1在此表示数组首地址,并且是常量,不能修改 //s1 = "hello";//语法错误, s1在此表示数组首地址,并且是常量,不能修改 char * q = "world"; char * p = "hello"; p = q;//语法没有错误,效果是 p,q都保存了 'w'的地址, "hello"字符串找不到了 那如果想要修改数组s1的内容,应该怎么做: (1)一个一个元素进行赋值 s[0] = 'h'; s[1] = 'e'; s[2] = 'l'; s[3] = 'l'; s[4] = 'o'; s[5] = '\0'; (2)调用函数 strcpy/strncpy #include <string.h> char *strcpy(char *dest, const char *src); dest: 目的字符串首地址 src: 源字符串首地址 功能就是把 src指向的字符串内容拷贝到dest指向的字符串中去 成功返回目的字符串的首地址 strcpy内部的拷贝流程:从 src地址开始,一个一个进行拷贝,直到遇到 '\0'结束, 不会理会 dest是否越界 char *strncpy(char *dest, const char *src, size_t n); 功能是把 src指向的字符串内容至多拷贝n字节到dest指向的字符串中去 n一般是 dest字符串的容量-1,保证拷贝的过程中不越界,最后一个字符留给'\0' (3) strcmp / strncmp 比较两个字符串 char * s1 = "hello"; char * s2 = "world"; if(s1<s2) //这是比较的两个字符串首地址的大小,和字符串的内容没有任何关系 { printf("字符串s1小于s2\n"); } else { printf("字符串s1大于或等于s2\n"); } 字符串的比较规则:从第一个字符一个一个字符进行比较,直到不相同为止,首个不相同的字符 ascii 的大小就是两个字符串的大小 例如: "123" 比 "124" 小 因为 '3' < '4' "123" 比 "1234" 小 因为'\0' < '4' "124" 比 "1234" 大 因为 '4' > '3'
strcmp, strncmp - compare two strings
SYNOPSIS #include <string.h>
int strcmp(const char *s1, const char *s2); s1,s2 就是要进行比较的两个字符串的首地址 返回值: 字符串s1小于s2 ,返回-1 字符串s1等于s2 ,返回0 字符串s1大于s2 ,返回1 写一个函数,实现 比较两个字符串的功能 int strncmp(const char *s1, const char *s2, size_t n); 最多只比较两个字符串前n个元素
写一个函数,实现 比较两个字符串前n个字节的功能
(4) 字符串连接函数 strcat/strncat strcat, strncat - concatenate two strings SYNOPSIS #include <string.h> char *strcat(char *dest, const char *src); dest :目标字符串首地址 src:源字符串首地址 返回连接之后的目标字符串首地址 在 dest字符串的末尾连接上src字符串的内容 char dest[20]; char src[] = "world"; strcat(dest,src);//在连接的时候,会先找到dest末尾(也就是 '\0'),然后再连接 src //现在 dest字符串的 '\0'在哪不确定 printf("%s\n",dest);//不确定
--------------------- char dest[20] = "hello"; char src[5] = "world";//没有空间保存 '\0' strcat(dest,src);//连接 src 时,会一个一个字符进行赋值,直到 src遇到 '\0' //现在 src 字符串没有 '\0' printf("%s\n",dest);//不确定
练习: 自己实现这个函数 char *strncat(char *dest, const char *src, size_t n); 最多只连接 src的前n个字符到 dest中
有一个共同的问题:避免 dest指向的内存空间足够保存连接之后的字符串
总结: 所有和字符串相关的操作,都必须先考虑这个字符串是否有 '\0',如果没有'\0'会不会影响 你接下来的操作
13,函数指针
顾名思义就是指向一个函数的指针
每个函数都有自己的地址,调用函数其实就是通过这个地址找到函数内的指令并执行
(1) 函数的地址怎么表示? 函数名 或者 &函数名 (2)函数指针怎么定义? 首先得知道基类型是什么 -》 基类型是一个函数类型 函数类型怎么表示? -》 有两种表示方法 2.1 typeof(函数名) 2.2 把函数头的所有名字都去掉 包括函数名,形参名 例如: char *strcat(char *dest, const char *src); typeof(strcat) char *(char *, const char *); 定义函数指针变量: typeof(strcat) * p; char * (*q)(char *, const char *); 练习: int my_strncmp(const char * s1,const char * s2,int n); 请你定义一个指针变量并保存 my_strncmp 的地址 typeof(my_strncmp) * p = my_strncmp; int (*q)(const char * ,const char * ,int) = my_strncmp; (3) 如何通过函数指针调用函数 语法: 函数指针(实参列表); 或者 (*函数指针)(实参列表); -> void test() { typeof(my_strncmp) * p = my_strncmp; int (*q)(const char * ,const char * ,int) = my_strncmp; char * s1 = "123"; char * s2 = "1234"; switch (q(s1,s2,4)) { case 0: printf("字符串s1等于s2\n"); break; case -1: printf("字符串s1小于s2\n"); break; case 1: printf("字符串s1大于s2\n"); break; default: break; } } 主要应用场景:回调函数-》不是立马调用,回头再调用/在合适的时间调用 之前写一个选择排序的函数: void selection_sort(int a[],int n) { int i,j; int max_index; int temp; for(i=0;i<n-1;i++) { max_index = 0; for(j=0;j<n-i;j++) { if(a[j]>a[max_index]) { max_index = j; } } if(max_index != n-1-i) { temp = a[max_index]; a[max_index] = a[n-1-i]; a[n-1-i] = temp; } } } 这个函数只能进行从小到大排序,如果需要从大到小排序呢? 再重新写一个函数,但是选择排序是一个功能,写两个函数太麻烦。 -》 增加一个参数,函数指针 void selection_sort(int a[],int n,int (*func)(int,int)) { int i,j; int index; int temp; for(i=0;i<n-1;i++) { index = 0; for(j=0;j<n-i;j++) { //不直接比大小,而是通过传进去的函数指针去调用主调函数指定的比较函数 //如果 func这个函数指针指向的函数,第一个参数大于第二个参数返回真,就能实现升序的效果 //如果 func这个函数指针指向的函数,第一个参数小于第二个参数返回真,就能实现降序的效果 if(func(a[j],a[index])) { index = j; } } if(max_index != n-1-i) { temp = a[index]; a[index] = a[n-1-i]; a[n-1-i] = temp; } } } 具体代码见 函数指针.c
14,二级指针和多级指针
int data = 10; //sizeof(data) = 4
定义一个指针变量data的地址
int *p = &data; //sizeof(p) = 8 (64位)
既然 p是一个变量,占8个字节,那么p也有地址 定义一个变量保存p的地址,肯定也是指针变量 int ** q = &p; //int * 是基类型(也就是p的类型),所以 q 是 int **类型 //定义一个变量保存q的地址,怎么做? //int ***r = &q;
*p <-> data *q <-> p **q = 200; **q <-> *p <-> data
分析以下代码: void func(int x) { x = 10; } void test() { int y = 20; func(y); printf("%d\n",y);//20 } --------------------------- void func1(char *q) { static char s[] = "hello"; q = s; } void test1() { char * p = NULL; func1(p); //此时 p 为 NULL printf("%s\n",p);//报错 } ----------------------- 总结: 形参被改变,影响不了实参
void swap(int *p,int *q) { int temp; temp = *p; *p = *q; *q = temp; } void test() { int x=10; int y=20; swap(&x,&y); printf("%d,%d\n",x,y);//20,10 } ------------------------ void func2(char **q) { static char s[] = "hello"; *q = s; } void test2() { char * p = NULL; func2(&p); printf("%s\n",p);//正确打印 hello } 总结: 想要在被调函数内部改变主调函数中的某个变量的值,需要把这个变量的地址传递到被调用函数。
15,动态内存分配
系统提供了几个函数用于分配动态内存,动态内存的特点:需要手动释放,如果不手动释放,会一直占用 -》 生存期 ,随内核持续性
malloc 、 calloc 、 realloc 用来动态分配内存 free 用来释放动态内存 #include <stdlib.h>//需要包含这个头文件 void * -> 通用指针类型(不能理解为基类型为 void),它可以转换为其它类型的指针。 void * p = 某个地址; int * p1 = p; double *p2 = p; char * p3 = p; ......
void *malloc(size_t size); size :你想要分配内存的字节数目 返回值: 失败返回 NULL (内存不足的时候会分配失败) 成功返回 这块内存的首地址 malloc(20);//操作系统为该程序分配20个字节,没有名字,怎么访问呢? //只能通过它的首地址来访问,所以该函数才需要返回这个首地址 为什么返回 void * ,而不是 int * / double * / char * ...... 因为它不知道你要用这块内存保存什么类型的数据, 如果返回 int *,结果你是用来保存double类型的数据呢?还需要强转 如果返回 double *,结果你是用来保存char 类型的数据呢?还需要强转 ... 所以干脆不指定具体的类型,用 void *这种通用类型来表示,你可以根据实际情况进行赋值/转换 例如: 我想要动态分配4个字节用来保存一个整数 int * p = (int *)malloc(4); //int * p = malloc(4); //p指向了这四个字节, 对四个字节的访问可以通过 *p 进行 *p = 100; printf("%d\n",*p);
void free(void *ptr); //释放 动态分配的内存 ptr :动态分配内存的首地址
void *calloc(size_t nmemb, size_t size); 作用和 malloc类似,分配一个数组, nmemb * size 为要分配的总字节数目 nmemb :数组元素个数 size :单个元素所占字节大小 返回值: 失败返回 NULL (内存不足的时候会分配失败) 成功返回 这块内存的首地址 分配的这块内存,全部初始化为0 void *realloc(void *ptr, size_t size); 是用来为已经分配的动态内存进行扩容的 ptr :原来分配的内存的首地址 size :是扩容之后的大小 返回值: 失败返回 NULL (内存不足的时候会分配失败) 成功返回 扩容之后的内存的首地址 扩容之后,原来的数据保留
16, main函数的参数问题
主函数是可以有参数的,在运行程序时由“终端”给出实参,实参的类型都是字符串
形参怎么写,有固定的格式
int main(int argc,char *argv[]) {} 第一个形参 argc: 表示传入的实参个数(运行程序时终端输入的字符串个数,以空格分隔) 第二个形参 argv: 指针数组,有 argc个元素 比如: 运行时输入 ./a.exe 123 abc 调用主函数,进行传参: int argc = 3; char *argv[] = {"./a.exe","123","abc"}; -> argv[0] = "./a.exe"; argv[1] = "123"; argv[2] = "abc";