C语言提高

本文详细探讨了C语言中的内存管理,包括客户端服务器(CS/BS)模型,标准学习,API接口,void类型特性,内存四区的工作原理,函数调用模型,指针的使用和常见错误,以及const常量的使用。还讲解了二级指针、指针数组、二维数组、结构体、链表和函数指针的应用,特别是回调函数的概念及其在程序设计中的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

//头文件函数声明
//防止头文件重复包含(相互包含陷入包含死循环)
#pragma once
//兼容C++编译器
//如果是C++编译器,按c标准编译
#ifdefine __cplusplus  //C语言编译器中已有的宏,不是自定义宏
extern "C"
{
#endif



//接口函数


#ifdefine __cplusplus 
extern "C"
}
#endif

//在a.cpp文件中调用b.c文件中函数,而又不想将b.c文件改成b.cpp文件,则需要在b.h文件加入上述代码,让其按照c标准编译

CS:客户端服务器

BS:浏览器和服务器(可以认为其是一种特殊的CS)

学习标准

第一套api接口

//初始化socket句柄的接口
int  socket_client_init(void **handle);
//发送socket网络报文
int socket_client_send(void *handle, unsigned char *buf, int buf_len);
//接收socket网络报文
int socket_client_recv(void *handle, unsigned char *buf, int *buf_len);
//释放socket句柄资源
int destory_socket(void *handle);

第二套api接口

//初始化socket句柄的接口
int  socket_client_init(void **handle);
//发送socket网络报文
int socket_client_send(void *handle, unsigned char *buf, int buf_len);
//接收socket网络报文
int socket_client_recv(void *handle, unsigned char **buf, int *buf_len);
//
socket_client_free(unsigned char **buf);
//释放socket句柄资源
int destory_socket(void **handle);

void类型

①函数参数为空,定义函数时,可以用void修饰;

②函数没有返回值;

③不能定义void类型的普通变量:void a;//err,无法确定类型,不同类型分配空间不一样(数据类型本质,固定内存块大小别名)

④可以定义void *变量: void * p;//合法,32位机器占4个字节,64位机器占8个字节

⑤void *p 万能指针,函数返回值,函数参数。

内存四区

//"abcdef"保存在字符串常量区
char *get_str1()
{
    char *p ="abcdef";//p指向文字常量区

    return p;
}

char *get_str2()
{
    char str[] ="abcdef";//str空间为栈区,将文字常量区的内容拷贝到str空间,此函数调用完成后str所指向的内存区域被释放

    return str;
}

char *get_str3()
{
    char *tmp = (char *)malloc(100);//堆区,由程序员释放
    if(tmp == NULL)
    {
        return NULL;
    }
    strcpy(tmp,"abcdef");
    return tmp;
}

int main(void)
{
    char *p1 =NULL;
    char *p2 =NULL;
    char *p3 =NULL;

    p1=get_str1();
    printf("%s\n",p1);

    p2=get_str2();
    //get_str2()运行完毕,str空间自动回收,str的空间内容未知,有可能保留之前的内容,也有可能是乱码
    printf("%s\n",p2);

    p3=get_str3();
    if(p3!=NULL)
    {
       printf("%s\n",p3);
       free(p3);//由程序员释放
       p3=NULL;//养成良好习惯
    }
    return 0;
}

函数的调用模型:先进后出

栈的生长方向和内存的存放方向:栈的生长方向为由高到底,而内存以及堆的生长方向由底到高

程序验证

#include<stdio.h> 
#include<stdlib.h> 
#include<string.h> 

char *get_str1()
{
    char *tmp = (char *)malloc(100);//堆区,由程序员释放
    if(tmp == NULL)
    {
        return NULL;
    }
    strcpy(tmp,"abcdef");
    return tmp;
}

char *get_str2()
{
    char *tmp = (char *)malloc(100);//堆区,由程序员释放
    if(tmp == NULL)
    {
        return NULL;
    }
    strcpy(tmp,"taylen");
    return tmp;
}
int main()
{
	//栈的生长方向 
	int a; 
	int b; 
	printf("栈的生长方向\n"); 
	printf("&a: %p\n", &a); 
	printf("&b: %p\n", &b);
	
	//堆的生长方向 
	char * p1=NULL; 
	char * p2=NULL; 
	p1 = get_str1();
	p2 = get_str2();
	printf("堆的生长方向\n");
	printf("p1的地址:%p\n",p1);
	printf("p1+1地址:%p\n",p1+1);
	
	printf("p1的地址:%p\n",p1);
	printf("p2的地址:%p\n",p2);
	free(p1);
	free(p2);
	
	//内存的生长方向
	char buf[4]; 
	printf("内存的生长方向\n");
	printf("buf的地址: %p\n", &buf[0]); 
	printf("buf+1地址: %p\n", &buf[1]); 
	return 0; 
}

输出结果:

①栈区地址生长方向:地址由上往下递减

②堆区地址生长方向:地址由下往上递增

③数组buf,buf+1地址永远递增

指针易错点

①写内存时,一定要内存可写

char *buf1 = "abcdefg";//文字常量区,内存不可改

buf1[2] = '1';//err

char buf2[] = "abcdefg";/

buf2[2] = '1';//ok

②不允许向NULL和未知非法地址拷贝内存

指针强化

如何定义合适类型的指针变量:某个变量地址需要定义一个怎样类型的变量保存;在这个类型的基础上加一个*

int b;
int *q = &b;
int ** t =&q

如果想通过函数形参改变实参的值,必须传地址:

1、值传递,形参的任何修改不会影响到实参

2、地址传递,形参(通过*操作符号)的任何修改会影响到实参

主调函数与被调函数:

a)主调函数可把堆区、栈区、全局数据内存地址传给被调函数

b)被调函数只能返回堆区、全局数据

内存分配:

a)指针做函数参数,是输入与输出特性的(输入:主调函数分配内存;输出:被调函数分配内存)

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

void fun1(char *p /*in*/)
{
    if(p == NULL)
    {
        return;
    }
    //给p指向的内存区域拷贝
    strcpy(p,"PointerAsInput");
}
void fun2(char **p /*out*/,int *len)
{
    if(p == NULL)
    {
        return;
    }
    char *tmp = (char *)malloc(100);
    if(tmp == NULL)
    {
        return;
    }
    strcpy(p,"PointerAsOutput");
    //简洁赋值
    *p =tmp;
    *len =strlen(tmp);

}
int main(void)

{
    //输入,主调函数分配内存
    char buf[100]={0};
    fun1(buf);
    printf("buf = %s\n",buf);

    //输出,被调用函数分配内存,地址传递
    
    char *p = NULL;
    int len = 0;
    fun2(&p);
    if(p!=NULL)
    {
        printf("p = %s,len = %d\n",p,len);
        free(p);
        p=NULL;
    }
    system("pause");
    return 0;
}

字符串

/*C语言没有字符串类型,通过字符数据模拟

C语言字符串,以字符'\0'结尾,数字0*/

//不指定⻓长度,C编译器会⾃自动帮程序员求元素的个数 
//buf1是⼀一个数组  不是⼀一个以0结尾的字符串 
char  buf1[]  =  {'a',  'b',  'c',  'd'};  
printf("buf1 = %s\n",buf1)  //结果会是abcd+乱码

//用字符串初始化字符数组 ,常用
    char  buf2[]  =  "abcd";        //buf2  作为字符数组  有5个字节 
                                //       作为字符串有  4个字节 
    int  len  =  strlen(buf2); 
    printf("buf2字符的⻓长度:%d  \n",  len);     //4 
    //buf3  作为数组  数组是⼀一种数据类型  本质(固定⼩小⼤大内存块的别名) 
    int  size  =  sizeof(buf2);  // 
    printf("buf2数组所占内存空间⼤大⼩小:%d  \n",  size);  //5 

指针和字符串数组名是否等价?

char buf[] = "abcdefg";
char *p =NULL;
p = buf;
p++;//没问题
buf++;//报错
/*buf只是一个常量,不能修改,主要是为了确保最终内存可以回收*/

const的使用

const声明的变量只能被读 ,且必须被初始化。但可以通过指针间接修改。

1)const声明变量为只读

const int a = 10;

a=100;//err

char buf[100]="abcd";
const char *p=buf;
char const *p=buf;//修饰*,指针指向能变,指针指向的内存不能变
//p[0]='1';//err
p = "123456";//ok

char *const p1 = buf;//修饰指针变量,指针指向的内存能变,指针指向不能变

//p1="123456";//err

p1[0]='1';//ok


const char* const p2=buf;//p2,只读

 

二级指针

如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针 变量。也称为“二级指针”。

指针数组:

//指针数组,指针的数组,它是一个数组,每个元素都是char*
char *p[] = {"1111","0000","aaaa","bbbb"};
char **q = {"1111","0000","aaaa","bbbb"};//err

//只有在作为函数参数时两者可通用

二级指针做函数参数输出特性

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int getMem(char *p)
{
	p = (char *)malloc(sizeof(char) * 100);
	if (p == NULL)
	{
		return -1;
	}
	//p = "abcdef";
	strcpy(p, "abcdef");
	printf("p = %s\n", p);
	return 0;
}
int main(void)

{
	char *p = NULL;
	int ret = 0;
	ret = getMem(p);
	if (ret != 0)
	{
		printf("getMem err: %d\n", ret);
		return ret;
	}
	printf("p = %s", p);
	printf("\n");
	system("pause");
	return 0;
}
//输出结果
/*
p = abcdef
p = (null)
*/

上面的例子,在执行语句ret = getMem(p)时,只是值传递,拷贝了实参p的值赋值给形参p,即形参p的值一开始即为NULL,随后通过malloc分配了内存,并将该内存中填入“abcd”,但其并未与实参p有实际对应关系。相应的正确代码如下(地址传递,形参修改会影响到实参):

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int getMem2(char **p)
{
	if (p == NULL)
	{
		return -1;
	}
	char * tmp;
	tmp = (char *)malloc(sizeof(char) * 100);
	if (tmp == NULL)
	{
		return -2;
	}
	//p = "abcdef";
	strcpy(tmp, "abcdef");
	
	*p = tmp;
	return 0;
}
int main(void)

{
	char *p = NULL;
	int ret = 0;
	ret = getMem2(&p);
	if (ret != 0)
	{
		printf("getMem err: %d\n", ret);
		return ret;
	}
	printf("p = %s", p);
	if (p != NULL)
	{
		free(p);
		p = NULL;
	}

	printf("\n");
	system("pause");
	return 0;
}

对应的内存四区图如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值