1. 再讲数组名作为函数传参
数组作为函数的实参,本质上传的是数组的首地址
当数组名作为函数的实参的时候,数组名当成是一个指针
那形参该如何定义?
(1)一维数组
void Func(int x[], int n)
{
x[0] = 100
}
void Func_1(int*x, int n)
{
x[0] = 100
}
int main()
{
int a[4] = {1,2,3,4};
Func(a,4);// 代表数组:typeof(a) ==> int[4]
Func_1(a,4);// 看作是指针 :typeof(a) ==> int*
}
(2)二维数组
void Func(int x[][4], int n)
{}
void Func_1(int(*x)[4], int n)
{
x[0][0] <===>a[0][0]
//*x <==> x指向的对象 ==> a[0] *(x+0) ==> x[0]
//x ==> &a[0]
}
int main()
{
int a[3][4] = {0};
Func(a, 3);//代表数组:typeof(a) ==> int[4][3]
Func_1(a, 3);// 看作是指针 :typeof(a) ==> int[4]* Func_1(&a[0], 3)
}
结论:
数组名作为函数参数,本质上 传的是 首元素的地址
2.多级指针
-
如果一个指针变量p1存储的地址,是另一个普通变量a的地址,那么称p1为一级指针
-
如果一个指针变量p2存储的地址,是指针变量p1的地址,那么称p2为二级指针
-
如果一个指针变量 p3 存储的地址,是指针变量 p2 的地址,那么称 p3 为三级指针
-
以此类推,p2、p3等指针被称为多级指针
-
示例:
int a;
//定义一个指针变量p,保存a的地址
int*p = &a; // p: 一级指针
//定义一个指针变量p1, 来保存p的地址
typeof(p) *p1 = &p;
==> int* *p1 = &p; // p1 :二级指针
//定义一个指针变量p2, 来保存p1的地址
typeof(p1) *p2 = &p1;
==> int** *p2 = &p1;// p2: 三级指针
//注意: 无需搞清楚到底是几级指针, 只要知道是一个指针,该指针指向的对象是什么类型
3.指针万能拆解法
- 任意的指针,不管有多复杂,其定义都是由两部分组成
- 第一部分:指针所指向的对象的数据类型,可以是任意类的类型
- 第二部分:*指针的名字
- 示例:
char (*p1); // 第2部分:*p1; 第1部分:char;
char *(*p2); // 第2部分:*p2; 第1部分:char *;
char **(*p3); // 第2部分:*p3; 第1部分:char **;
char (*p4)[3]; // 第2部分:*p4; 第1部分:char [3];
char (*p5)(int, float); // 第2部分:*p5; 第1部分:char (int, float);
- 注解:
- 上述示例中,p1、p2、p3、p4、p5本质上并无区别,它们均是指针
- 上述示例中,p1、p2、p3、p4、p5唯一的不同,是它们所指向的对象的数据类型不同
- 第1部分的声明语句,如果由多个单词组成,C语言规定需要将其拆散写到第2部分的两边
4.指针函数与函数指针
指针函数:本质上是一个函数,返回值类型是指针类型
int* Get_Memory_int()// ==>指针函数
{
static int n;
return &n;
}
int main()
{
int*p = Get_Memory_int();
int*q = Get_Memory_int();
}
函数指针:
-
概念:指向函数的指针,称为函数指针。
-
本身是一个指针,指向的对象是一个函数
-
特点:函数指针跟普通指针本质上并无区别
-
在C语言中 , 函数名代表函数的地址 &函数名 (1)函数指针如何定义? 指针的定义: 指向的对象的类型 * 指针变量名 函数 : 函数头 + 函数体 定义: 函数返回值类型 函数名(函数形参列表) ==》 函数头 {} ==》 函数体 函数类型: 函数返回值类型 (函数形参列表) eg: int Func(int n) { printf("Func:%d\n", n); } // 定义一个指针变量p保存Func的地址 typeof(Func) *p; ==> int (int) *p; ==> int (*p)(int);// p:函数指针,指向一个带int型参数,返回值为int型的函数 (2)如何给函数指针赋值? p = Func;// p(2) or p = &Func;// (*p)(2) (3)怎么通过函数指针去调用指向的函数? p(2) or (*p)(2) 通过函数指针去调用所指向的函数: 指针变量名(实参列表); (*指针变量名)(实参列表);
-
示例:
练习:
定义一个函数,通过指针变量p去间接调用
#include <stdio.h>
void Func(int n)
{
printf("Func:%d\n", n);
}
void YYY(int n)
{
printf("YYY:%d\n", n);
}
void XXX(int n)
{
if(n <= 0)
Func(n);
else
YYY();
}
int main()
{
void(*p)(int);// p:函数指针,指向一个带int型参数,返回值为int型的函数
//p = Func;
// p = &Func;
//p(2);
//(*p)(3);
}
-
回调函数:一个函数作为参数进行传参,该函数被称为回调函数
-
要点:
-
函数指针是一类专门用来指向某种类型函数的指针。
-
函数的类型不同,所需要的函数指针也不同。
注意: 在给函数指针指向函数的时候 一定要对应 函数类型
-
函数的类型,与普通变量的类型判定一致,即去除声明语句中的标识符之后所剩的语句。
-
5.字符串处理函数
函数strstr
- 注意:
- 该函数仅仅只是查找是否有该子串,返回的是该子串所在的首字符地址
- 示例:
#include <stdio.h>
#include <string.h>
int main()
{
char*s = "HF240401_pxl_1";
char*p = strstr(s,"pxl");
if(p == NULL)
{
printf("未找到\n");
}
else
{
printf("p = %s\n", p);
}
}
练习:
写一个程序,实现将一个ip字符串中每个数字进行输出,ip字符串通过键盘输入
eg:
char *s = "192.168.10.165";
==> 192
168
10
165
#include <stdio.h>
#include <string.h>
int main()
{
//char s[] = "192.168.10.165";
//char *s;
char s[128] = {0};
//scanf("%s", s);
gets(s);
char*start = s;// 指向字符串起点位置
while(1)
{
char*p = strstr(start, ".");
if(p == NULL)
{
printf("%s\n", start);
break;
}
*p = '\0';
printf("%s\n", start);
start = p+1;
}
}
函数strlen
字符串长度:第一个字符距离第一个’\0’的元素个数,不包含‘\0’
- 示例:
char *s = "123\0a123456";
strlen(s) ==> 3
s = "123\0123456123";
sizeof(s) ==> 8
strlen(s) ==> 11
char s[20] = "123\17891234";
sizeof(s) ==> 20
strlen(s) ==> 10
char s[] = "123456";
strlen(s+2) ==> 4
函数strtok
- 注意:
- 该函数会将改变原始字符串 str,使其所包含的所有分隔符变成结束标记 ‘\0’ 。
- 由于该函数需要更改字符串 str,因此 str 指向的内存必须是可写的。
- 首次调用时 str 指向原始字符串
- str == NULL 使用的是从之前子串后一个字符的位置
- 若自己给定str ,从str指定的位置开始
- 示例:
#include <stdio.h>
#include <string.h>
int main()
{
char s[] = "www.baidu.com";
char *p = strtok(s, ".");
printf("p = %s\n", p);
p = strtok(NULL, ".");
printf("p = %s\n", p);
p = strtok(NULL, ".");
printf("p = %s\n", p);
p = strtok(NULL, ".");
printf("p = %s\n", p);
}
函数strcat与strncat
- 注意:
-
这两个函数的功能,都是将 src 中的字符串,复制拼接到 dest 的末尾。
-
strcat() 没有边界控制,因此可能会由于 src 的过长而导致内存溢出:越界。
-
strncat() 有边界控制,最多复制 n+1 个字符(其中最后一个是 ‘\0’ )到 dest 的末尾。
在复制过程中,strncat结束有两种情况:1. 遇到\0但是还没有复制到n个字符
2. 已经复制了n个字符没有遇到\0 , 最后会补一个\0
以上两种情况最后都会有\0
-
- 示例:
int main()
{
char s1[32] = {"123456\0a78910"};
char s2[] = {"12"};
//char *p = strcat(s1+2,s2+3);
//printf("%p %p %p\n", s1, s2, p);
char*p = strncat(s1,s2,6);
printf("p = %s\n", p);
for(int i = 0; i < 32; i++)
{
printf(".%c", s1[i]);
}
printf("\n");
}
- 注意:strncat()是具备边界检查的安全版本,推荐使用。
函数strcpy与strncpy
NAME
strcpy, strncpy - copy a string
SYNOPSIS
#include <string.h>
char *strcpy(char *dest, const char *src);
dest: 指向的空间用来存储要拷贝的字符串
src: 指向要拷贝字符串
返回值:
返回 :拷贝之后新的字符串首地址 等价于dest
strcpy函数存在越界的风险
char *strncpy(char *dest, const char *src, size_t n);
dest: 指向的空间用来存储要拷贝的字符串
src: 指向要拷贝字符串
n : 最多拷贝src指向的字符串前n个字符
返回值:
返回 :拷贝之后新的字符串首地址 等价于dest
-
注意:
-
这两个函数的功能,都是将 src 中的字符串,复制到 dest 中。
-
strcpy() 没有边界控制,因此可能会由于 src 的过长而导致内存溢出:越界。
-
strncpy() 有边界控制,最多复制 n 个字符到 dest 中。
-
在复制过程中 ,
1.复制的字符数 <=n 的时候,遇到\0结束, \0 也会复制过去
2.如果 字符数 ==n 的时候期间一直未遇到\0, 拷贝完n个字符数结束,\0不会被拷贝
-
-
注意:strncpy()是具备边界检查的安全版本,推荐使用。
#include <stdio.h> #include <string.h> int main() { //char s1[32] = {"123"};//1 2 3 \0 1 2 3 4 5 6 \0 \0 \0 // char s1[] = {"123"}; // char* s2 = "123456"; //strcpy(s1,s2); char s1[32] = {"12345\0asdfg"}; char *s2 = "as123"; strncpy(s1,s2, 2); printf("s1 = %s\n", s1); }
函数strcmp与strncmp
-
注意:
-
比较字符串大小,实际上比较的是字符的 ASCII码值的大小。
-
从左到右逐个比较两个字符串的每一个字符,当能“决出胜负”时立刻停止比较。
-
strcmp和strncmp这两个函数:两个字符串不相同返回的是 两个字符的ASCII码的差值
返回值: s1 > s2 ==> 结果 > 0
s1 == s2 ==> 0
s1 < s2 ==> < 0
-
-
示例:
strcmp("123", "asd");// ==> <0
strncmp("zhangsan","zhang", 5);// 只比较字符串前5个字符
// 字符串 后4个字符 为 .txt 的字符串
char*s = "1.txt";
strcmp(s+strlen(s)-4, ".txt") == 0
int main()
{
char *s1 = "1231";
char *s2 = "12w1";
printf("%d\n", strncmp(s1,s2,10));// -68
printf("%d\n", strncmp(s1,s2,-1));// -68
printf("%d\n", strcmp(s1,s2));//-68
}
函数strchr与strrchr
-
注意:
- 这两个函数的功能,都是在指定的字符串 s 中,试图找到字符 c。
- strchr() 从左往右找,strrchr() 从右往左找。
- 字符串结束标记 ‘\0’ 被认为是字符串的一部分。
-
示例:
char *p = strchr("123415",'1');
6.void型指针
通用指针,泛型指针
-
概念:无法明确指针所指向的数据类型时,可以将指针定义为 void 型指针
-
要点:
- void 型指针无法直接索引目标,必须将其转换为一种具体类型的指针方可索引目标
- void 型指针无法进行加减法运算
-
void关键字的三个作用:
- 修饰指针,表示指针指向一个类型未知的对象。
- 修饰函数参数列表,表示函数不接收任何参数。
- 修饰函数返回类型,表示函数不返回任何数据。
注意:
一般void* 指针都出现在 函数参数或者是函数返回值类型的时候
如果出现在 函数参数的时候 表示实参可以是任意类型的指针
如果出现在 函数返回值类型的时候 表示你可以用任意一个类型的指针来接收该函数的返回值
7.动态内存分布函数
堆内存 :随进程(程序)的持续性
用户在代码中告诉编译器你是否需要开辟空间,释放也由手动释放
malloc/realloc/calloc :用来动态开辟空间
//在 堆内存中 开空间
free:释放 动态内存空间
注意:释放 malloc/realloc/calloc 所开辟的空间
NAME
malloc, free, calloc, realloc - allocate and free
dynamic memory
SYNOPSIS
#include <stdlib.h>
malloc :用来在动态内存中分配size个字节的空间大小
void *malloc(size_t size);
size: 要开辟的空间大小,以字节为单位 >0
返回值:
成功返回开辟的空间首地址
失败返回NULL
注意:
malloc开辟的空间不会自动初始化
如果要把该空间全部设置为某个字符或者是0
memset/bzero
=======================================
#include <string.h>
void *memset(void *s, int c, size_t n);
===========================
#include <strings.h>
void bzero(void *s, size_t n);
================================
eg:
char *p = malloc(10);
memset(p,0,10);/ bzero(p, 10);
注意:
动态内存分配之后,不使用记得及时释放
若没有释放,又找不到该空间,该空间称为垃圾内存,该现象称为内存泄露
==========================================
free :释放 malloc/realloc/calloc 所开辟的动态空间
void free(void *ptr);
ptr: 指向要释放的空间
=========================
calloc :用来分配动态空间,分配nmemb个对象,每个对象size大小
void *calloc(size_t nmemb, size_t size);
nmemb:分配的对象个数
size:每个对象所占空间大小,以字节为单位
返回值:
成功返回开辟的空间首地址
失败返回NULL
注意:
calloc所分配的空间将会被初始化为0
==================================
realloc :将ptr所指向的空间扩大的size大小(ptr要指向一块动态内存)
扩大:
原址扩充
异地扩充
记得重新接收该函数的返回值
void *realloc(void *ptr, size_t size);
ptr == NULL
relloc(NULL,size) <==>malloc(size)
ptr != NULL
size > 原来大小
扩充
size == 原来大小
不动
size < 原来大小
未知情况,未定义
size == 0
free(ptr)
返回值:
成功返回开辟的空间首地址
失败返回NULL
int main()
{
int*p = malloc(100);
int a;
p = &a;// 此时 malloc的空间变成垃圾内存
}
int main()
{
int*p = malloc(100);
int a;
free(p);
// p = &a;
p = NULL;
}
8.main函数的形式参数
在Linux下面,程序运行时,main允许带参数,只不过所有的参数都当做是字符串来处理。
把所有的字符串传给main函数,所以在c程序中main里面可以带两个参数,第一个参数
int argc表示命令行参数的个数,第二个参数是一个字符串指针数组
里面每一个都是一个参数:字符串
int main(int argc,char*argv[])// int argc, char**argv
{
//argv[0][0]
}
练习:使用从命令行输入的方式计算两个数的和
./a.out 123 456
==> 123+456 = 379
#include <stdio.h>
int str_to_int(char*str)// str = NULL
{
//printf
if(str == NULL)
return -1;
if(*str < '0' || *str > '9')
{
return -1;
}
int num = 0;
while(*str)
{
if(*str >= '0' && *str <= '9')
{
num = num*10 + *str - '0';
}
else
{
return num;
}
str++;
}
return num;
}
//./a.out 111 222
int main(int argc, char**argv)
{
if(argc != 3)
{
printf("输入参数有误\n");
return -1;
}
int num1 = str_to_int(argv[1]);
int num2 = str_to_int(argv[2]);
printf("%d + %d = %d\n",num1, num2, num1+num2);
}