C语言(三)

8.指针

8.1引入

一个变量有几种含义

  1. 空间

当我们顶一个一个变量之后,这个变量就可以代表这个空间本身,也可以表示空间中的值

在内存中,为了方便对内存的管理,计算机给每个字节都进行了编址。

对于整型数据,我们可以使用 intshort 等类型进行保存,对于字符我们可以用 char 类型进行保存,但是我们要保存地址呢?

  • 地址的本质就是数据,只不过这个数据有特殊的含义
  • 保存这种特殊含义的数据,我们需要指针变量

8.2指针

如果一个指针类型的变量p,保存了某个变量a的地址,我们就称p指向了a

int a;
int *p = &a;

语法

基类型 *指针变量名{=地址};

  • 基类型:指针指向的类型
  • * :指示这是一个指针变量
  • 指针变量名:符合C语言标识符命名规则即可

指针变量的字节数:

  • 指针变量的字节数是固定的,不随着指向类型的变化而变化,字节数一般与long类型相同(取决于编译器)。
  • 指针的字节数决定了寻址范围。

8.3*的含义

*运算符读作 指针运算符/解引用运算符(指向运算符)

  • 指针运算符:int *p;表示这是一个指针

  • 解引用运算符:

    int a;
    int *p = &a;
    //&a 引用;*(&a) 解引用
    //*p == a; p == &a;
    

写一个函数,交换两个变量的值:

void swap(int *a, int *b)
{
    int t = *a;
    *a = *b;
    *b = t;
}

int main()
{
    int a = 3;
    int b = 4;
    swap(&a, &b);
}

8.4指针的初始化

int a;					//a 空间中的值是未知的
int *p;					//p 空间中的值是未知的
int a = 10;				//将10 放到a 空间中
int *p = &a;			//将a的地址,存放在p所对应的空间中
int *p = 10;			//合法但不合理
int *p = (int *)10;		//(int *)10 代表一个地址,指针这么指其实没什么意义

空指针:

int *p = NULL;			//p为空指针,NULL表示空,也就是说这个东西合法,但不可访问.
*p = 10;				//访问会导致程序异常,段错误。

野指针:

int *p;					//p 空间中的值是未知的
*p = 10;				//访问大概率会导致程序异常,段错误。
  • 建议指针变量进行定义时,如果没有具体的指向,请初始化为NULL;NULL是一种检测手段,可以检测这个指针是否被使用,可以通过 if(p == NULL) 判断指针是否被使用

8.5一维数组和指针

数组各个元素都有自己的地址,并且地址是连续的

int a[5] = {1, 2, 3, 4, };
//申请了五个整型元素的空间,并且将a和空间首地址绑定,并且可以用a来表示这个数组,也就是说我可以定义一个指针来指向这个数组
int *p = &a[0];
int *p = a;
//这两种方法都可以让p指向a[0],*p == a[0];
//将a[0]设置为1的方法
a[0] = 1;
*a = 1;
*p = 1;
p[0] = 1;
//将a[1]设置为1的方法
a[1] = 1;
*(a+1) = 1;
*(p+1) = 1;
p[1] = 1;
  • 指针/地址与数值的加减,不再是数值的加减,而是加减基类型的字节数
  • 指针/地址之间的加法/乘法/除法是毫无意义的。
  • 指针/地址之间的减法(基类型相同的情况下),表示两个地址之间相差了多少个基类型
int a[5];
/*
数值上,a 和 &a[0] 和 &a 是相同的
a表示数组的首地址
&a[0]表示a[0]的地址
&a表示整个数组的地址
事实上,a的类型是int [5]
*/

int (*p)[5] = &a;	//数组指针

8.6二维数组与指针

int a[3][4];
//二维数组可以看作3个一维数组,并且数组名为a[0] a[1] a[2]
//所以a[0]的类型是int [4]
int (*p)[4] = a;	//此时p代表a
//设置a[0][0]为1
a[0][0] = 1;
p[0][0] = 1;
*(a[0] + 0) = 1;
*(*(p+0) + 0) = 1;
//设置a[2][0]为1
a[2][0] = 1;
p[2][0] = 1;
*(a[2] + 0) = 1;
*(*(p+2) + 0) = 1;
int a[3][4]
/*
&a  a  &a[0]  &a[0][0]  在数值上是相同的,但含义不同
&a[0][0]:代表a[0][0]这个元素的地址
&a[0]   :代表a[0]这个数组的地址
a       :代表a[0]这个数组的地址
&a      :代表a这个数组的地址
*/

二维数组作为函数参数

int a[3][4];

int waterPools(int (*a)[4], int row, int col)
    
int waterPools(int a[][4], int row, int col)

8.7指针数组与数组指针

int *a[4];		//指针数组
int (*a)[4];	//数组指针
  • int *a[4]本质上是一个数组,这个数组内保存的都是地址
  • int (*a)[4];本质上是一个指针,可以指向一整个数组

指针数组的常见用法:

char *str[] = {"hello world", "123456", "abcdefg"};
//str[0] 存了"hello world"的地址
//str[1] 存了"123456"的地址
//str[2] 存了"abcdefg"的地址

作业:

1.输入一串小写字母,对小写字母按照"abcdefg"这种规则进行排序

#include <stdio.h>

void letSort(char *p)
{
	int i = 0;
	while(*(p+i) != '\0')
	{
		if(*p < 'a' || *p > 'z')
		{
			printf("please inpute letter\n");
			return;
		}
		i++;
	}
	for(int j = 0; j < i-1; j++)
	{
		for(int k = j+1; k < i; k++)
		{
			if(*(p+j) > *(p+k))
			{
				char t = *(p+j);
				*(p+j) = *(p+k);
				*(p+k) = t;
			}
		}
	}
}

int main()
{
	char letter[100];
	scanf("%s",letter);
	printf("%s\n", letter);
	letSort(letter);
	printf("%s\n", letter);
}

2.输入一串字符,将其中重复的字符删除只剩一个

例如:输入 aaaaabbbbbddd1111ddd

​ 输出 abd1

#include <stdio.h>

void delChar(char *str, char *result)
{
	int i = 0;
	int index = 0;
	int state = 0;
	while(*(str+i) != '\0')
	{
		i++;
	}
	for(int j = 0; j < i; j++)
	{
		for(int k = 0; k < i; k++)
		{
			if(*(str+j) == *(result+k))
			{
				state = 1;
				break;
			}
		}
		if(state == 0)
		{
			*(result+index) = *(str+j);
			index++;
		}
		state = 0;
	}
}

int main()
{
	char str[100] = {'\0'};
	char result[100] = {'\0'};
	gets(str);
	puts(str);
	delChar(str, result);
	puts(result);
}

8.8const关键字

const :只读;被该关键字修饰的变量的值不能被改变

int a = 10;
a = 11;				//可以正常修改a的值

const int a = 10; /*等价*/  int const a = 10;
a = 11;				//不可以正常修改a的值

const修饰指针

int * const p;		//const修饰指针变量p,p的值不能被改变,意味着p的指向不能被改变
int const *p;		//const修饰*p,p指向的地址的值不能被修改,p的指向可以被修改
const int *p;		//const修饰*p
const int * const p; //const既修饰p又修饰*p

const所添加的属性是伪只读属性:

const int a = 10;
printf("%d\n", a);
int *p = &a;
*p = 20;
printf("%d\n", a);
//用指针是可以修改被const修饰的变量的值的

8.9二级指针和多级指针

保存一个普通变量的地址,我们可以用一个一级指针来保存,对于p来说,它本身的空间也有地址编码,我们要定义一个指针指向p,如何定义

int a;
int *p = &a;		//一级指针
int **pp = &p;		//二级指针
int ***ppp = &pp;	//三级指针

8.10字符串与指针

字符串就是一串以'\0'结尾的字符集,C语言并没有字符串类型,在C语言中字符串的表现方式是指针/地址。

char *str = "hello world";
char str1[] = "hello world";

指针str和数组str1[]有没有区别

  • str是一个指针,指向了保存"hello world"空间的地址,str指向的"hello world"是只读的,修改会段错误
  • str1[]是一个数组,是用自己的空间保存了"hello world"字符串

字符串的表示

  • " "引起来的字符集,结尾有隐藏的'\0'
  • 保存在字符数组中的带有'\0'的字符集char shr[] = {'h', 'e', 'l', 'l', 'o', '\0'};

8.11字符串相关函数

在C标准库中有一系列处理字符串的函数,声明一般在string.h

  1. 求字符串长度 strlen

    #include <string.h>
    size_t strlen(const char *str);
    /*
    str:指针,指向待计算的字符串
    返回值:字符串的长度
    */
    
  2. 比较字符串是否相等 strcmp strncmp

    int strcmp(const char *s1, const char *s2);
    /*
    s1:指针,指向待比较的字符串
    s2:指针,指向待比较的字符串
    返回值:
        s1 == s2:0
        s1 < s2 :负数
        s1 > s2 :整数
    */
    int strncmp(const char *s1, const char *s2, size_t n);
    /*
    s1:指针,指向待比较的字符串
    s2:指针,指向待比较的字符串
    n:需要比较几个字符
    返回值:
        s1 == s2:0
        s1 < s2 :负数
        s1 > s2 :整数
    */
    
  3. 拷贝字符串 strcpy strncpy

    #include <string.h>
    char *strcpy(char *dest, const char *src);
    /*
    拷贝src中的字符串包括'\0'到dest指向的空间中,注意dest的空间要足够大,以防止越界。
    dest:指针,指向拷贝的目的空间
    src:指针,指向拷贝的源空间
    返回值:返回dest
    */
    char *strncop(char *dest, const *src, size_t n);
    /*
    拷贝src中的字符串至多n个字符到dest指向的空间中,注意dest的空间要足够大,以防止越界。
    dest:指针,指向拷贝的目的空间
    src:指针,指向拷贝的源空间
    n:要拷贝多少个字符
    返回值:返回dest
    */
    
  4. 字符串拼接 strcat strncat

    #include <string.h>
    char *strcat(char *dest, const char *src);
    /*
    dest:指针,指向被拼接的字符串空间
    src:指针,指向要拼接的字符串
    返回值:返回dest
    */
    char *strncat(char *dest, const char *src, size_t n);
    /*
    dest:指针,指向被拼接的字符串空间
    src:指针,指向要拼接的字符串
    n:要拼接的字符的个数
    返回值:返回dest
    */
    
  5. 字符串输入 gets fgets

    #include <stdio.h>
    char *gets(char *s);
    /*
    s:指针,指向保存被输入数据的空间
    返回值:成功,返回s;失败,返回NULL。
    */
    char *fgets(char *s, int size, FILE *stream);
    /*
    s:指针,指向保存被输入数据的空间
    size:要读取的数据的个数
    stream:文件指针,从哪里获取数据
    返回值:成功,返回s;失败,返回NULL。
    */
    
  6. 字符串输出 puts fputs

    int puts(const char *s);
    /*
    s:指针,指向保存被输出数据的空间
    返回值:输出成功返回非负整数,失败返回EOF
    */
    int fputs(const char *s, FILE *stream);
    /*
    s:指针,指向保存被输出数据的空间
    返回值:输出成功返回非负整数,失败返回EOF
    stream:文件指针,表示要输出的目标文件
    */
    

作业:自己实现 strlen strcpy strcat strcmp

strlen

#include <stdio.h>

int myStrlen(char *str)
{
	int len = 0;
	while(*(str+len))
		len++;
	return len-1;
}

int main()
{
	char str[100];
	fgets(str, 100, stdin);
	int len = myStrlen(str);
	printf("strLen:%d\n", len);
}

strcpy

#include <stdio.h>

char *myStrcpy(char *dest, char *src)
{
	int i = 0;
	do
	{
		*(dest+i) = *(src+i);
		i++;
	}while(*(src+i));
	return dest;
}

int main()
{
	char src[] = "hello world";
	char dest[100] = {0};
	myStrcpy(dest, src);
	printf("%s\n", dest);
}

strcat

#include <stdio.h>

char *myStrcat(char *dest,const char *src)
{
	int i = 0, j = 0;
	while(*(dest+i))
		i++;
	do
	{
		*(dest+i) = *(src+j);
		i++;
		j++;
	}while(*(src+j));
}

int main()
{
	char src[] = "hello world";
	char dest[100] = "world hello";
	printf("%s\n%s\n", src, dest);
	myStrcat(dest, src);
	printf("%s", dest);
}

strcmp

#include <stdio.h>

int myStrcmp(const char *s1, const char *s2)
{
	int i = 0;
	do
	{
		if(*(s1+i) < *(s2+i))
			return -1;
		else if(*(s1+i) > *(s2+i))
			return 1;
		i++;
	}while(*(s1+i));
	return 0;
}

int main()
{
	char s1[100];
	char s2[100];
	int compare;
	printf("please input str1\n");
	fgets(s1, 100, stdin);
	printf("please input str2\n");
	fgets(s2, 100, stdin);
	compare = myStrcmp(s1, s2);
	printf("%d\n", compare);
}

8.12函数指针

C程序中,每一个函数都有函数空间,每个函数空间都有一个地址,这个地址叫函数的入口地址(函数名代表了函数的首地址)

顾名思义,函数指针就是指向函数的指针。

函数的类型

void func(void);
//func的类型:void(void)
char *strcpy(char *dest, char *src);
//strcpy的类型:char *(char *, char *)

定义一个指针指向函数

//指向函数strcpy
char *(char *, char *) *pstrcpy = strcpy;//不是这么写的
//变成
char *(*pstrcpy)(char *, char *) = strcpy;
//或者
char *(*pstrcpy)(char *, char *) = &strcpy;

函数指针的使用

char *str = "hello";
char buf[10] = {0};
pstrcpy(buf, str);
//或者
(*pstrcpy)(buf, str);

练习:

1.定义一个函数指针指向gets函数,利用该函数指针从终端获取一串字符并输出

#include <stdio.h>

int main()
{
    char str[100];
    char *(*pGets)(char *);
    pGets = gets;
    pGets(str);
    printf("%s", str);
}

2.利用函数指针实现一个既可以做整数加法,也可以做小数加法的函数


函数指针的使用场景

回调函数

....

示例:

假设我们有一个排序库,这个库里面有很多中实现排序的方法(冒泡、选择、快排....),现在需要实现一个函数,这个函数的功能就是排序,要求使用排序库中的函数来实现排序

//以下是排序库中的函数的声明
void BubbleSort(int *a, int n);
void InserSort(int *a, int n);
void SelectSort(int *a, int n);
void NumSort(int *a, int n);
void QuickSort(int *a, int n);

void Sort(int *a; int n, void(*func)(int *, int))
{
    func(a, n);
}

int main()
{
    int a[5] = {1, 3, 7, 5, 6};
    sort(a, 5, QuickSort);
}

函数指针数组:保存函数指针的数组。

//函数声明
void func1(void);
void func2(void);
void func3(void);
//定义函数指针数组
void (*a[3])(void);
//初始化函数指针
a[0] = func1;
a[1] = func2;
a[2] = func3;
//利用函数指针调用函数
a[0]();
a[1]();
a[2]();

函数指针数组指针:指向函数指针数组的指针

void (*(*p)[3])(void) = &a;

指针函数

返回值类型为指针的函数

char *strcpy(char *dest, char *src);

8.13动态内存分配

int a[10] = {0};

a所代表的内存,是固定长度的,没办法变化

假设我们可以将内存的长度进行变化的申请,如果有一组数据一开始需要200字节,那我们定义内存大小为200字节,后来又需要400字节,我们又可以申请400个字节。

动态内存实际上是一块空间,这块空间随着需要而被申请相应的字节数,当不需要的时候,可以释放这块空间。

动态内存的特点:

  • 手动申请,手动释放。
  • 生存周期随进程持续(只要没有手动释放)
  • 作用域全局

动态内存的获取:有三个函数 malloc calloc realloc

释放:free

void *malloc(size_t size);
/*
size:要申请的字节数
返回值:成功,返回动态内存空间的地址,如果size为0,返回NULL;
       失败,返回NULL;
malloc分配的空间,空间中的内容未知
*/
void free(void *ptr);
/*
ptr:要释放的动态内存的地址
返回值:无
*/
void *calloc(size_t nmemb, size_t size);
/*
nmemb:数组元素的个数
size:每个元素所占的字节数
返回值:成功,返回动态内存空间的地址,如果size为0,返回NULL;
       失败,返回NULL;
calloc分配的空间,会全部初始化为0
*/
void *realloc(void *ptr, size_t size);
/*
对原来的空间重新分配
ptr:要重新分配的空间
size:重新分配后的空间大小
返回值:成功,返回动态内存空间的地址,如果size为0,返回NULL;
       失败,返回NULL;
*/
  • 内存的申请和释放要成对出现,否则会造成内存泄漏的问题

void * :通用指针

void a;		//不合法,计算机不知道用什么方式解析a
void *p;	//合法,指针的长度恒为机器位数对应的字节数,如果不知道指针将来会指向什么,可以使用void *

8.14 main函数的参数问题

main函数也是可以进行参数传递的,main函数的实参由终端给出,实参类型是字符串

int main(int argc, char *argv[])
{
    
}
/*
argc:参数个数
argv:指针数组,保存实参字符串的首地址
*/

在终端输入./a.out ./a.out 就是argv[0]

posted @ 2023-07-06 19:59  乐情在水静气同山  阅读(25)  评论(0编辑  收藏  举报