文章目录
- 指针数组
- 1.定义:
- 2.格式:
- 3.应用示例:
- 1)用于存放普通变量的地址
- 2)用于存放二维数组的每一行第一个元素的地址(列地址)
- 3)用于存放字符串
- 4)命令行参数
- 函数
- 1.定义:
- 2.三要素:
- 3.格式:
- 4.函数声明
- 4.函数声明
- 5.函数调用
- 6.函数传参
- 6.1.值传递
- 6.2.地址传递
- 6.3.数组传递
- 开辟堆区空间(动态内容开辟)
指针数组
1.定义:
本质是数组,里面存放的是指针
2.格式:
存储类型 数据类型 *数组名[元素个数]
int* arr[2];
3.应用示例:
1)用于存放普通变量的地址
int a = 10, b = 20, c = 30;
int *p[3] = {&a, &b, &c};
访问 b 的地址
p[1] *(p+1)
访问 b 的值
*p[1] *(*(p+1))
2)用于存放二维数组的每一行第一个元素的地址(列地址)
int a[2][3] = {1, 2, 3, 4 ,5 , 6};
int *p[2] = {a[0], a[1]};
访问 a[1][2] 的地址:
p[1]+2 *(p+1)+2
3)用于存放字符串
char str[32] = “hello”;
char *str = “hello”;
printf(“%s\n”, str);
printf(“%p %p\n”, str, &str[0]);
printf(“%c %c\n”, *(str+1), str[1]);
使用指针数组存放字符串的方式:
char *p[3] = {“hello”, “world”, “ikun”};
打印 “ikun” 字符串
printf(“%s\n”, p[2]); // ikun
printf(“%s\n”, *(p+2)); // ikun
printf(“%s\n”, *(p+2)+1); // kun
打印 ‘k’ 这个字符
printf(“%c\n”, *(p[2]+1)); // k
printf(“%c\n”, ((p+2)+1)); // k
4)命令行参数
int main(int argc, char const *argv[])
{
printf(“%s\n”, argv[0]);
printf(“%s\n”, argv[1]);
}
// gcc 文件名.c 编译完成之后
// ./a.out 1234
argv:就是一个指针数组,里面存放的是命令行传递的字符串
argc:表示argv指针数组里面存储数据的个数,即命令行传递字符串的个数
函数
1.定义:
一个完成特定功能的代码模块
2.三要素:
功能、参数、返回值
3.格式:
存储类型 数据类型 函数名(参数列表)
{
函数体;
return 函数返回值;
}
1)没有参数:参数列表可以省略,也可以使用 void
2)没有返回值:数据类型为void,函数内部没有return
3)有返回值:要根据返回值的数据类型定义函数的数据类型
4)定义子函数时可以直接定义在主函数上面,如果定义在主函数下面需要提前声明函数
4.函数声明
数据类型 函数名(参数列表); // 形参
4.函数声明
数据类型 函数名(参数列表); // 形参
5.函数调用
1)没有返回值:直接调用:函数名(参数列表); // 实参
2)有返回值:如果需要接收返回值,就要定义一个与返回值数据类型相同的变量接收
如果不需要接收返回值,就直接调用函数
6.函数传参
6.1.值传递
单向传递,将实参传递给形参使用,改变形参实参不会受到影响
#include <stdio.h>
int fun(int a, int b)
{
a++;
b++;
return a + b;
}
int main(int argc, char const *argv[])
{
int a = 3, b = 4;
int num = fun(a, b);
printf("%d %d %d\n", a, b, num);
return 0;
}
6.2.地址传递
双向传递,在函数中修改形参,实参随之变化
#include <stdio.h>
int fun(int *a, int *b)
{
*a = *a + *b;
*b = *b + 2;
return *a + *b;
}
int main(int argc, char const *argv[])
{
int a = 3, b = 4;
// 因为你是拿到了地址,对地址进行赋值,并不是拿到值
int num = fun(&a, &b);
printf("%d %d %d\n", a, b, num);
return 0;
}
6.3.数组传递
和地址传递一样,参数中存在数组的定义,它也会认为是指针
#include <stdio.h>
char *fun(char str[32])
{
str = "hello";
printf("%d\n", sizeof(str)); // 4
return str;
}
int main(int argc, char const *argv[])
{
char *ch = fun("abc");
printf("%s\n", ch);
return 0;
}
补充:
// s 在栈区空间开辟4字节空间存放字符串常量 "hello" 的首地址
char *s = "hello";
// 在栈区开辟 32 个字节的空间,存放 "hello" 字符串
char str[32] = "hello";
开辟堆区空间(动态内容开辟)
为什么存在动态内存开辟
<1>在技术方面,普通的空间申请,都是在全局或者栈区,全局一般不太建议大量使用,而栈空间有限,那么如果一个应 用需要大量的内存空间的时候,需要通过申请堆空间来支持基本业务。
<2>在应用方面,程序员很难一次预估好自己总共需要花费多大的空间。想想之前我们定义的所有数组,因为其语法约束,我们必须得明确"指出"空间大小.但是如果用动态内存申请(malloc)因为malloc是函数,而函数就可以传参,也就意味着,我们可以通过具体的情况,对需要的内存大小进行动态计算,进而在传参申请,提供了很大的灵活性
#include <stdlib.h>
void *malloc(size_t size);
功能:在堆区开辟空间
参数:size:开辟空间的大小 (单位:字节)
返回值:
成功:返回开辟空间的首地址
失败:NULL;
#include <stdlib.h>
void free(void *ptr);
功能:释放堆区空间
参数:ptr:堆区空间的首地址
返回值:无
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
int *p = (int *)malloc(sizeof(int)*10);
if(NULL == p)
{
printf("开辟失败,请注意内存的释放");
return -1;
}
printf("开辟成功");
// 写操作
free(p);
p = NULL;
return 0;
}
注意:
1.手动开辟堆区空间,要注意内存泄漏
当指针指向开辟堆区空间后,又对指针重新赋值,则没有指针指向开辟带队去空间,就会造成内存泄漏
2.使用完堆区空间后及时释放空间