C语言P1_7,c语言基础补全

本文回顾了C语言的一些基础知识,包括关键字如`typedef`、`void`的使用,预处理命令,`sizeof()`的计算规则,内存分区,指针与函数,以及常用函数如`malloc`、`calloc`、`strcpy`等的使用。还讨论了结构体的内存对齐,函数指针,以及C++中使用C语言函数的注意事项。文章深入浅出地讲解了C语言的核心概念,是学习和复习C语言的好资料。
摘要由CSDN通过智能技术生成

大学学了c之后,一直没有好好复习过,一些特性和语法都忘了,这里总结下。

一:一些关键字

1:typedf的用法

给已知类型取别名

struct Person{

char name[64];

int age;

}

typedf struct Person myPerson;

typedf struct Person{

char name[64];

int age;

}myPerson;

同样是给已知类型取别名,但用于指针类型的时候有特殊效果

//p1是char* 类型,p2是char类型

char* p1,p2;

//p1 p2 都是 char* 类型

typedf char* pChar

pChar p1,p2;

//p1 p2 都是 char* 类型

char *p1, *p2;

2:void类型

不能用void定义一个类型,因为编译器不知道分配多少个字节

但是 void* 是可以的,因为所有指针都是四个字节

//定义了一个无类型指针,所有类型指针的祖宗

void* a

void还可以用做 形参 和返回值

//虽然输入和输出都是void,但是这样写,可以明确的定义一个函数(但是c++不需要,如果没有传参,会直接报错)。

void func(void){};

3:预处理命令

这里 IF 0之后的代码是无效的

IF 0

代码

END IF

4:sizeof() 计算一个变量占用内存空间大小

1:返回值要考虑字节对齐的问题

2:返回值是 unsigned int,大部分编译器,使用无符号整型计算的结果也是无符号整型

//这里结果是 4294967286

unsigned int a = 10;

cout << a - 20 << endl;

//但是如果用printf,结果会不一样,结果是-10,因为%d会强行将无符号数变为有符号数

printf("%d",a-20);

数组本身是一个指向头元素的指针,当数组作为参数传递给函数时,计算的是指针的大小,如果不是作为函数参数,计算的是数组本身的大小。

int calArrSize(int arr[]){

return sizeof(arr);

}

int main(){

int a[] = {1,2,3,4,5,6,7};

cout << sizeof(a) << endl;//结果是28

cout << calArrSize(a) << endl;//结果是4

return 0;

}

5:编译、extern、static

头文件不参与编译,在c语言中,每一个.c文件是一个编译单元,每一个编译单元是独立编译的,因此下面的代码不能够成功运行

文件A中

int a = 0;

文件B中

cout << a << endl;

B文件在独立编译时,不知道a变量的存在,因此会报错,如果需要通过编译,就需要使用extern关键字告诉编译器 a是存在的,在之后的链接过程中,链接器就会去找a。

extern int a;

cout << a << endl;

内部连接:变量只能在当前文件内使用

外部连接:变量可以被其他文件使用

本来全局变量是放在静态区的,可以被其他文件所访问,但如果使用 static 修饰全局变量,全局变量就变成了内部链接,只能被当前文件所访问。

所以,如果a确实没有定义,或者是static类型的变量,在链接的时候,还是会报错的。

extern 是单个声明,而头文件则是批量声明(include),编译的时候,会将头文件的内容直接拷贝进来。

6:常量区

(1):const全局和局部变量区别

const全局变量在常量区,常量区的变量都不能修改(直接或间接都不能修改)

const int a = 10;

int main(){

a = 20; //报错,不能直接修改

int* p = &a;

*p = 20;//报错,也不能间接修改

}

const局部变量在栈上,栈上的值都是可以修改的(可以间接修改)

int main(){

const int a = 100;

a = 200;//报错,不能直接修改

int* p = &a;

*p = 200;//可以间接修改

}

(2):字符串常量

字符串常量都在常量区,常量区的变量名就是字符串本身的名字

int main(){

char* p = "Hello world";

printf("%d",&("Hello world"));

printf("%d",p);

}

打印:

4214855

4214855

字符串常量能不能修改,看编译器,一般来说,不允许修改

字符串常量如果值相同,会不会共用一个,这个不同编译器也不一样

7:内存分区总结

总的来说,分为 代码区 和数据区

代码区:存放编译后的二进制代码,不可寻址

数据区:堆、栈、全局 \ 静态、常量(分字符串常量区和变量常量区)

二:指针和函数

1:宏函数

宏函数其实不是函数,类似c++的内联函数,编译的时候替换,由于不会有压栈操作,因此大多数时候比函数效率高

#define ADD(x,y) x+y

int main(){

cout << ADD(1,2) << endl;

}

2:函数调用惯例

惯例指的是函数的调用者和被调用者对函数调用有着一致的理解

(1)函数参数的传递顺序和方式

参数入栈的方式是从左到右还是从右到左?

(2)栈的维护方式

函数出栈的工作是由调用方来完成还是被调用方完成?

一般来说,c语言默认使用 cdecl 惯例,_如果没有手动指定,编译器会自动加上(windows默认stdcall)

int _cdecl func(){};

调用前后的两个函数,调用惯例需要一样。

3:指针

32位下,所有指针都是4个字节,里面存的是内存地址(64位操作系统是8个字节)

指针步长: 指针变量+1时,要向后跳多少字节,不同类型的指针就表现在指针步长不同。

解引用所取字节数 + 指针步长 = 指针类型

4:free使用后要记得把指针置空

free使用后要记得把指针置空

char a[] = "192.168.0.1";

char buf[1024] = {0};

strcpy(buf,a);

free(buf);

// buf = NULL

cout << buf << endl;//缺少buf = NULL,输出还是 192.168.0.1

三:一些c中常用函数

1:计算结构体中成员变量偏移量 offsetof

#include

cout << offsetof(struct Person, b) << endl;

2:在堆区分配内存 malloc

char* s = malloc(sizeof(char) * 100);

3:初始化 memset

memset(s, 0, 100);//将s指向内存的前100个字节设置为0

4:复制 strcpy

大长度字符串复制给小长度字符串小心内存溢出

strcpy(s,"Hello world")

5:使用占位符拼接字符串 sprintf (以字符串作为输出)

char buf[1024] = {0};

sprintf(buf,"hello%d world",1);

cout << buf << endl;

//输出:hello1 world

6:calloc 函数——分配连续的一段空间,并初始化

calloc(10,sizeof(int))

7:sizeof 函数

int a = 0;

cout << sizeof(a) << endl;

cout << sizeof(int) << endl;

这里两个都是输出 4

8:重新分配空间 realloc

如果原内存地址后续连续空间不够,就会重新分配一块内存,并将原内存内容复制过来

realloc(p, 10000)//p指针指向的内存

9:sscanf 输出匹配字符串

这里 %*d 是忽略整型的意思

int main(){

char a[] = "1234abcd";

char buf[1024] = {0};

sscanf(a,"%*d%s",buf);

cout << buf << endl;

}

int main(){

char a[] = "192.168.0.1";

int num1 = 0, num2 = 0, num3 = 0, num4 = 0;

sscanf(a,"%d.%d.%d.%d",&num1,&num2,&num3,&num4);

printf("%d,%d,%d,%d",num1,num2,num3,num4);

//输出:192,168,0,1

}

如果是 %*s则是忽略字符串,匹配直到空格或者制表符之类的才结束。

占位符的书写和正则表达式很像

%[a-z]表示a到z的所有字符

%[aBc]表示aBc中的某个字符

%[^a]表示除a之外的字符

%[^a-z]表示除a-z之外的所有字符

四:数组

1:数组在sizeof的时候,不是指针

int a[] = {1,2,3,4}

cout << sizeof(a) << endl; //16

2:数组名是一个常量指针

char a[] = "192.168.0.1";

a = NULL;//报错,a是常量指针

3:给函数传参的时候,数组的长度是不知道的,所以在传参的时候,一定要传长度

void func(int* a,int len){};

4:使用typedf定义数组指针类型

typedef作用于数组

typedef int(ARRAY)[5]

ARRAY a;

ARRAY* p = &a;//p是一个地址,是指向整个数组的指针,因此步长就是数组的大小

定义数组指针(是一个一级指针,但是步长是数组的长度)

typedef int(ARRAY*)[5]

int a[5] = {0};

//下面两句是等价的

ARRAY p = &a;

int(*p2)[5] = &a;

5:二维数组

二维数组也是一个二级指针,步长是指针大小(下面指向的类型是a[3])

int arr[3][3] = {1,2,3,4,5,6,7,8,9};

int(*p)[3] = arr;

五:结构体

1:结构体的一种特殊写法

简单的结构体的写法和 typedef 先不说,来个奇葩的

//定义结构体的同时,定义了一个结构体变量

struct Person{

char name[20];

int age;

}person = {"ttt",28};

2:结构体内存对齐

原因 :方便计算机寻址,计算机一个字节一个字节读数据,速度太慢,所以会一块一块的读,为了避免读取的数据被分割(前后两次读读了同一个变量的不同部分),就有了字节对齐。

对齐规则:

第一个数据成员应该放在 offset 为0的地方

之后的数据成员应该放在 min(当前成员大小,#pragma pack(n))

整数倍的地方,比如当前成员是int,默认对齐模式是8,则应该放在4的整数倍的位置。

最后结构体所占的字节数,应该是最大成员的整数倍,比如最大元素是 double,那么结构体所占字节数应该是8的整数倍。

使用 pragma pack(n) 设置默认对齐模式为n

六:函数指针

函数名是函数的入口地址或者说本质上是一个存了函数地址的指针

定义函数指针:

void func(int a,char b){

cout << "hello world" << endl;

}

int main(){

typedef void(FUNC)(int,char);//定义函数指针类型

//typedef void(*FUNC)(int,char);也可以直接定义为指针类型

FUNC* p = func;//生成函数指针

p(1,'a');

//(*p)(1,'a');//解引用也可以,上面写法可以理解为编译器加上了 *

//直接定义一个函数指针

void(*p2)(int,char) =func;

}

七:其他

1:c++中使用c语言

//防止头文件重复包含

#pragma once

//在c++中能够使用c语言函数(c++中包含c语言的头文件会报错,编译的过程不同,比如对函数的修饰等)

#ifdef __cplusplus

extern "C"{

#endif

#ifdef __cplusplus

}

#endif

2:预处理的基本概念

c语言对源程序处理的四个大步骤:预处理、编译、汇编、链接

预处理:# 开头的就是预处理指令

普通宏定义

#define PI 3.14//宏定义,作用域是当前文件(就算定义在函数里面,作用域也是整个文件)

#undef PI//销毁宏定义,销毁之前可以使用,销毁之后就不能使用了

#define SUM(x,y) (x+y)//宏函数

条件编译

//条件编译 1

#ifdef FLAG

满足条件执行的代码

#else

不满足条件执行的代码

#endif

//条件编译 2

#ifndef FLAG

#else

#endif

//条件编译3

#if 表达式

#else

#endif

打印错误和位置

printf("%s的%d行出错!",__FILE__,__LINE__)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值