C语言基础知识点
一、入门知识
程序:一系列的代码的有序集合
进程:一个正在运行的程序
1、vim的使用
vi/vim 文件名
vi 打开文件有三种模式:
1、命令行模式:
复制、粘贴、剪切、撤销…
按 i、a、o切换到插入模式
按shift + : 进入底行模式
2、插入模式:
编辑内容
按Esc进入命令行模式
3、底行模式:
(/)查找、字符串替换、保存(w)、退出(q)
按ESC退出底行模式进入命令行模式
2、gcc编译过程
预处理、编译、汇编、链接
预处理(宏展开、头文件包含)
编译(翻译成汇编语言)
汇编(汇编器将编译生成的汇编代码翻译成机器指令)
链接:将.o文件和一些库文件链接在一起生成可执行文件
二、C语言基础
1、关键字和符号
- 关键字:while if int …
- 标识符: int a = 10; a就是标识符
- 分隔符:;
- 运算符:+ … sizeof &
- 标点符号:{} ()
2、C语言基础数据类型
-
char:字符类型(占一个字节)
占用一个字节,一个字节有8个bit位 有符号数:最高位为符号位0表示正数1表示数 不考虑正负:0000 0000 ~ 1111 1111 -> 0~255 有符号数的值域:-128~127
-
== short:短整型(占2个字节)==
不考虑正负值域:0 ~ 2^16 - 1(65535) 有符号数的值域:-32768 ~ 32767
-
int:整形(占4个字节)
不考虑正负值域:0 ~ 2^32 -1 有符号数的值域:-2^31 ~ 2^31 -1
int a = 10; // 十进制表示 int b = 012; // 八进制表示,等价于十进制的10 int c = 0xA; // 十六进制表示,等价于十进制的10
4.float类型(占4个字节)
float f = 3.14; // 浮点型数据类型,必须加上f后缀
- double类型(占8个字节)
double d = 3.1415926; // 双精度浮点型数据类型
3、负数的存储
注:在C语言中负数以补码的形式存储
- 原码:对应数据的二进制编码
- 反码:原码符号位不变数据位按位取反
- 补码:在反码的基础上加1
如:-20负数最高位的符号位要置1
原码:1001 0100
反码:1110 1011
补码:1110 1100
根据补码获得源码反过来即可
4、宏定义
注意宏替换,只是文本替换,不会对数据进行处理
#include <stdio.h>
#define A (3)
#define B (A+A)
#define C (B*B)
int main(int argc, char *argv[])
{
printf("%d\n", C); //B*B --> A+A*A+A --> 3+3*3+3
return 0;
}
3、进制转换
1、十进制转二进制(凑数法)
20–>16+4 ->24+22 -->1 0100
250–>128+64+32+16+8+2–>27+26+25+24+23+21 1111 1010
2、八进制、十六进制转二进制(三凑一、四凑一)
每个八进制用三个二进制位存储
每个十六进制用四个二进制位存储
如:八进制017、十六进制0x17(0和0x为八进制和十六进制前缀)
017 --> 001 111
0x17 --> 0001 0111
3、八进制和十六进制转十进制(八进制逢八进一,十六进制逢十六进一)
017 --> 8+7 = 15
0x17 --> 16+7 = 23
4、十进制转八进制和十六进制(反过来即可)
如:十进制23转八进制 2 * 8+7 = 027
如:十进制23转十六进制 1 * 16+7 = 0x17
5、C语言运算符
1、算术运算符
加(+)、减(-)、乘(*)、除(/)、求余(或称模运算,%)、自增(++)、自减(--)共七种
a += b // a = a + b
a -= b; a *= b; a /= b;
a %= b //模运算要求两数必须为 整数
a++ 和 ++a //a++运算后自加 ++a先自加再运算
2、关系运算符:>、<、>=、<=、==、!= 表达式返回真或假
常用于条件判断
3、逻辑运算符 : 包括与(&&)、或(||)、非(!)三种
逻辑与:&& -->遇假则假
逻辑或:|| --->遇真则真
逻辑非:! --->真为假假为真
短路法则:
当条件满足后,将不再执行后面的代码
&&:
int a = 0, b = 2, c;
c = a++ && b++;
printf("%d %d %d\n", a, b, c);
//1 2 0
解释:因为a++等于0,&&运算已经为假,不再执行后面的代码
||:
int a = 0, b = 2, c;
c = ++a || b++;
printf("%d %d %d\n", a, b, c);
//1 2 1
解释因为++a的值为1,||运算已经为真,不再执行后面的代码
4、位运算:
参与运算的量,按二进制位进行运算。包括位与(&)、位或(|)、位非(~)、位异或(^)、左移(<<)、右移(>>)六种
<1> 位与:&双目运算,左右操作数按位进行与运算(同为1则为1,否则为0)
左右操作数对应位相与:
左:1000 1111
右:0111 1011
结:0000 1011
<2> 位或:|双目运算符,左右操作数按位进行或运算(同为0则为0,否则为1)
左右操作数对应位相或:
左:1000 1111
右:0111 1011
结:1111 1111
<3> 位异或:^双目运算符,左右操作数按位进行异或运算(不同为1,相同为0)
左:1000 1111
右:0111 1011
结:1111 0100
//可以用异或运算进行数据交换
int a = 6, b = 7;
a ^= b;
b ^= a;
a ^= b;
//结果:a = 7, b = 6;
<4> 位反:~单目运算符,操作数进行按位取反
<5> 移位运算: << >>
无符号数左移:低位补0
unsigned char a = 6, b;
b = a << 2;
b = 0000 0110 << 2
b = 0001 1000
无符号数右移:高位补0
unsigned char a = 6, b;
b = a >> 2;
b = 0000 0110 >> 2
b = 0000 0001
有符号数左移:符号位不变,低位补0
char a = -8, b;
b = a << 3;
b = 1111 1000 << 3; //1111 1000 是-8的补码
b = 1100 0000 //移位的结果的补码
b = 1011 1111 //反码
b = 1100 0000 //存储数据的原码
b = -64
有符号数右移:高位补符号位
char a = -8, b;
b = a >> 2;
b = 1111 1000 >> 2; //1111 1000 是-8的补码
b = 1111 1110 //移位的结果
b = 1000 0010
b = -2
案例一:
#include <stdio.h>
int main(int argc, char *argv[])
{
char a = 236;
printf("%d\n", a);
return 0;
}
运算结果为 -20
解释:因为数据为有符号类型(未声明符号类型,默认为有符号类型),char类型数据取值范围为-128~127,236显然超出了,所以为负数,而负数在计算机中存储为补码,236的二进制为1110 1100(补码),将其转换为源码为1001 0100(最高位1为符号位,表示该数为负数),结果为-20
6、输入输出函数
概括:
putchar(); //输出一个字符
getchar(); //输入一个字符
printf(); //标准输出函数
scanf(); //标准输入函数
//扩展函数:
gets();
puts();
sprintf();
snprintf();
write();
read();
<1>数据输出
C语言无I/O语句,I/O操作由函数实现,要使用这些函数必须包含如下头文件
#include <stdio.h>
(1)putchar:输出单个字符
函数原型: int putchar(int c);
参数:要输出的字符或字符变量
返回值:成功返回输出字符的ASCII码,失败返回EOF(-1)
如:
#include <stdio.h>
int main(int argc, char *argv[])
{
char a = 'A';
int ret;
ret = putchar(a);
//putchar(10);
putchar('\n');
printf("ret:%d\n", ret);
return 0;
}
//结果为A
//ret:65
(2)printf:格式化输出数据
函数原型: int printf(const char *format, ...);
printf是一个不定参数的函数
const char *format:格式化化输出的字符串(1、%[修饰符]格式符 指定输出格式;2、普通字符原样输出)
...:指定输出的数据
(3)格式控制符:
4、附加格式说明符:
<2>数据输入
(1)getchar:从终端输入一个字符
函数原型:int getchar(void);
返回值:成功返回对应字符的ASCII码,失败或结束返回EOF(-1)
如:
#include <stdio.h>
int main()
{
char ch;
ch = getchar();
printf("input char %c\n", ch);
return 0;
}
//结果输入c
//输出input char c
(2)scanf:格式化输入
函数原型:int scanf(const char *format, ...);
const char *format:格式化的控制串,要输入的数据的类型
...:输入的数据存放的空间地址
返回值:成功是成功输入数据的个数,失败0, 错误-1
注意输入结束的标志:' ', '\t', '\n'
解决脏字符('\n'):
%*c
getchar() 具体示例见 scanf_1.c
scanf_1.c:
#include <stdio.h>
int main(int argc, char *argv[])
{
int a;
char b, c;
//scanf("%d %c %*c", &a, &b);
/* //第一次输入结束会有'\n'残留,导致第二次输入将'\n'当作输入
scanf("%c", &b);
scanf("%c", &c);
*/
//如下三种解决方法:
#if 0
scanf("%c%*c", &b);
scanf("%c%*c", &c);
#else
scanf("%c", &b);
getchar();
scanf("%c", &c);
getchar();
#endif
printf("b:%c c:%c\n", b, c);
return 0;
}
注:在Ubuntu终端 键入 Ctrl + d表示结束输入
数学函数库的头文件 <math.h>,在编译时需要链接数学库(gcc xxx.c -o xxx -lm )
7、控制语句
**判断语句中可能用到的函数
strcmp(); //字符串比较函数
strstr(); //获取子字符串首地址
strncmp(); //字符串比较函数取n个字符
1、分支语句
if~else也是条件控制语句
2、阶梯式
if~else if~else
3、嵌套式
if语句中再嵌套if语句
4、选择分支语句switch
switc(整数表达式){
case 常量条件1:
语句序列1;
break;
case 常量条件2:
语句序列2;
break;
...
case 常量条件n:
语句序列n;
break;
default:
语句序列n+1;
}
整数表达式:结果必须要是整数
8、循环语句
//前++运算效率更高
for(int i = 0; i< n; ++i);
1、for循环
for(;;); //死循环
2、do…while()
先执行一次,再判断真假,为真继续执行,为假退出循环。
一般形式:
do{
循环体;
}while(条件表达式);
3、while
先判断真假,为真执行循环为假跳过循环
一般形式:
while(条件表达式){
循环体;
}
int sum = 0, i = 1;
while(i < 101){
sum += i;
i++;
}
4、goto(不建议使用)
它是跳转语句,它会指定跳转到对应标号.(只能在函数内部进行跳转)
goto XXX;
XXX:
5、辅助控制语句
1、break:结束本层循环
2、continue:结束本次循环,直接进入下一次循环
三、数组
相同类型的元素集合。
<存储类型> <数据类型> <数组名>[整形表达式或整形常量];
[]:变址运算符
//定义一个数组
int arr[10]; //在内存中开辟10*sizeof(int)这么大的空间,10表示可以存储10个元素
static int arr[10];
extern int arr[10];
//在使用数组时,应该一个元素一个元素的访问:
1、数组的初始化
1、完全初始化:
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int arry[10] = {0}; //将数组所有元素都初始化为0
2、部分初始化:
int arr[10] = {1,2,3}; //部分初始化:将arr[0] = 1, arr[1] = 2; arr[2] = 3
2、数组的操作
数组元素的访问可以通过数组名加下标的方式去访问:
int arr[3] = {0};
//arr[3] = {1,2,3}; //err 脱离定义时,数组元素只能一个一个的访问
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
//遍历数组
for(int i = 0; i < 3; i++)
arr[i] = i+1;
sizeof(数组名):求数组在内存中所占字节数
数组元素的个数:sizeof(数组名)/sizeof(数组元素的类型)
int arr[5];
int num = sizeof(arr)/sizeof(int); //num == 5
一维数组的数组名表示该数组在内存中的起始地址:
int arr[5];
arr == &arr[0]; //一定等价
3、冒泡排序
将数组元素依次比较,将大数后移(升序排列)
#define N 10
int a[N], tmp;
...
for(int i = 0; i < N-1; i++){
for(int j = 0; j < N-i-1; j++){
if(a[j] > a[j+1]){
tmp = a[j];
a[j] = a[j+1];
a[j+1] = tmp;
}
}
}
4、多维数组
数组下标有多个
不管是纪委数组,在内存中仍然是一维的
一般形式:
<存储类型> <数据类型> <数组名>[整形常量表达式1][整形常量表达式2][整形常量表达式3]...
5、二维数组
一般形式:
<存储类型> <数据类型> <数组名>[整形常量表达式1][整形常量表达式2];
int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
二维数组定义及初始化:(行可以省略,列不可以省略)
int a[2][3] = {{1,2,3}, {4,5,6}};
int a[][3] = {1,2,3,4,5,6,7,8,9};
二维数组的遍历:
int a[3][4];
for(int i = 0; i < 3; i++){
for(int j = 0; j < 4; j++){
printf("%d ", a[i][j]);
}
puts("");
}
四、指针
1、指针的概念
在计算机内部存储器(简称内存)中最小单位是字节,每个字节都有一个唯一编号,这个编号就是地址。
地址就是指针!
用来存放地址的变量叫做指针变量。
普通变量存数据,指针变量存地址。
通常地址编号用16进制表示,但是不是说地址只能用16进制来表示
2、指针的一般形式
<存储类型> <数据类型> *<指针变量名>;
int *p;
char *q;
野指针:定义的指针变量没有初始化,里面是随机值
空指针:NULL,它是一个特殊地址(0),为了解决野指针的问题
3、指针与普通变量的联系
char a;
int b;
char *pa = &a; //定义和初始化
int *pb = &b; //定义一个指针变量pb让它指向整形变量
char *p = &b;
*pb == b;
pb的类型是:int *
&b的类型是: int *
int a = 10, b;
int *p = &a;
printf("%d %d\n", a, *p); //10 10
a = 20;
printf("%d %d\n", a, *p); //20 20
*p = 30;
printf("%d %d\n", a, *p); //30 30
p = &b; //OK,指针变量p指向b
*p == b;
p == &b;
/*
*错误示范
int a = 10, b = 20;
int *p;
*p = a; //这里会产生段错误,p没有具体指向的空间,没办法存储a的数据
a = b;
b = *p;
*/
注: 在指针变量定义时赋值,一定用地址去赋值。在脱离定义时,有*表示具体数据,没有*表示地址。
4、指针支持的运算
1、算数运算:
+、-、++、--
+: p+n结果表示从p存储的地址向大的方向偏移n个元素,具体偏移n*sizeof(指针的数据类型)字节
-: p-n结果表示从p存储的地址向小的方向偏移n个元素,具体偏移n*sizeof(指针的数据类型)字节
p-q:指针与指针相减,前提两指针类型必须相同,它的结果表示两指针之间间隔元素的个数,具体间隔字节 结果*sizeof(指针的数据类型)
++: p++表示p存储的地址向大的方向偏移1个元素,具体偏移 sizeof(指针指向对象的数据类型)字节,并且将结果重新存储到p中。
--:同上
2、关系运算:
> < >= <= == !=:
比较两个指针的(地址编号)大小
3、逻辑运算:
&& || !:
指针是否指向空指针(NULL)
5、指针与一维数组的关系
因为数组是同种数据类型的有序集合,每个元素都可以看做是一个变量,这些变量的地址连续。所以可以通过指针来遍历一维数组。
int a[5] = {1,2,3,4,5};
int *p = &a[0];
for(int i = 0; i < 5; i++){
printf("%d ", *(p+i));
}
puts("");
6、指针与字符数组字符串的关系
char *p = “hello”; //将“hello”的首地址给p
//*p = ‘H’ ; //err
p = “world”; // 将“world”的首地址给p
char str[100] = “hello”; //将“hello”字符串存储在str中。
p = str; //将str的首地址给p
*p = ‘H’; //OK str[0] = ‘H’;
char str[] = {"hello"};
//char str[6]; str = "hello"; //err str是地址常量
6、一些符号的特殊关系
*、& 、[]:
*与&互为逆运算:当*与&同时存在时可以相互抵消
int a[5] = {1,2,3,4,5};
*&a[0] == a[0]
*与[]等价:*与[]可以相互转换
int a[5] = {1,2,3,4,5}, *p = a;
a[0] == *p; -->*(a+0) == p[0];
a[1] == *(p+1); -->*(a+1) == p[1];
printf("%d %d %d %d\n", a[2], *(a+2), *(p+2), p[2]); //3 3 3
[]与&互为逆运算: 当[]与&同时存在时可以相互抵消
int a[5] = {1,2,3,4,5};
int *p = &a[0]; //int *p = a;
int *q = &a[4]; //int *q = a+4;
总结:
当指针px指向数组a的首元素时(0<=i<a的元素个数),那么如下等式恒成立:
a[i] == *(a+i) == *(px+i) == px[i]
对一维数组的数组名取地址:将整个数组看做一个整体
int a[4] = {1,2,3,4};
&a + 1 --->表示移动整个数组
int a[5] = {1,2,3,4,5};
printf("%d\n", *((a+1)-1)); //1
printf("%d\n", *((int *)(&a+1)-1)); //5
printf("%d\n", *(int *)(&a)); //1
6、指针与二维数组的联系
因为二维数组在内存中是连续且按行优先存储,所以可以通过一级指针将二维数组当做一个一维数组来遍历
int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
int *p = &a[0][0]; //int *p = a[0];
for(int i = 0; i < 3*4; i++)
printf("%d ", *p++);
puts("");
7、数组指针(行指针)
本质上是一个指针,只不过这个指针指向的是整个数组。
一般形式:
<存储类型> <数据类型> (*指针变量名)[下标];
//数据类型表示指向的数组存储的类型
//下标值是指向数组的下标值
int a[5] = {1,2,3,4,5};
int (*p)[5] = &a;
int (*p)[5];
p = &a;
1、数组指针如何访问一维数组
double a[5] = {1.1, 2.2, 3.3, 4.4, 5.5};
double (*p)[5] = &a; //p == &a -> *p == *&a == a
for(int i = 0; i < 5; i++){
printf("%.1f ",(*p)[i]);//(*p)[i] == p[0][i] == *(*p+i)
}
puts("");
2、数组指针如何访问二维数组:
int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
int (*p)[4] = &a[0]; //int (*p)[4] = a;
for(int i = 0; i < 3; i++){
for(int j = 0; j < 4; j++){
printf("%d ", (*(p+i))[j]); //(*(p+i))[j] == *(*(p+i)+j) == p[i][j] == *(p[i]+j)
}
puts("");
}
注意:
最先念的最后结合,最后念的最先结合。
最先结合的就是本质。
二维数组的数组名就是行指针(数组指针)
8、void*指针
不确定类型的指针,即指针指向的变量类型不确定,意味着指针可以指向任意确定类型的变量;
int i = 123;
void *p = &i;
char ch[] = {"hello"};
void *p = ch;
在使用void指针之前,需要将void指针强制类型转换成确定类型的指针再使用
int main(int argc, char *argv[])
{
int i = 0;
int a[10] = {1,2,3,4,5,6,7,8,9,10};
void *p = a;//void *p = &a[0]
for(i=0;i<sizeof(a)/sizeof(a[0]);i++)
printf("%d ",*((int *)p+i));//void * 强制类型转换成int *
printf("\n");
return 0;
}
9、const指针
1.const修饰变量(只读变量/常变量)
const int i = 123;//只读变量:只能初始化,初始化之后不能再修改
i = 321;//error
2.const修饰指针(重点)
1)const修饰指针指向的变量
const 数据类型 *指针变量;//含义:不能通过指针修改指针指向的变量的值
eg:
int i = 123;
int j = 321;
const int *p = &i; //等价形式: int const *p = &i;
*p = 124;//error 不能通过p指针修改指针指向的变量的值
p = &j;//right p的指向可以修改
2)const修饰指针
数据类型 * const 指针变量;//含义:不能修改指针的指向
eg:
int i = 123;
int j = 321;
int * const p = &i;
*p = 124;//right 可以通过p修改p指向空间的值
p = &j;//error 不能修改指针p的指向
3)const既修饰指针指向的变量,同时又修饰指针
const 数据类型 * const 指针变量;//含义:既不能通过p指针修改指针指向的变量的值,也不能修改指针的指向
eg:
int i = 123;
int j = 321;
const int * const p = &i;
*p = 124;//error 不可以通过p修改p指向空间的值
p = &j;//error 不能修改指针p的指向
五、函数
1、概念
1、概念:
为了实现某一功能而封装的模块叫函数;
2、语法形式:
返回值类型 函数名(数据类型 形参1, 数据类型 形参2,…,数据类型 形参n)//函数的定义
返回值类型 函数名(数据类型 形参1, 数据类型 形参2,....,数据类型 形参n)//函数的定义
{
//语句块
return 返回值;
}
eg:
void main(void)//无返回值无参数
{
[return ;]//可省略
}
eg1:
void add(int a, int b,int *c)//定义函数add
{
*c = a+b;
return ;
}
eg2:
int sum(int a, int b)
{
return a+b;
}
int ret = sum(10,20);
说明:
1)返回值类型:与return后的 返回值 类型一致
2)函数名:
符合命名规则;
是函数的入口地址(重点)
3) ()是形参列表,可以没有形参,但是()一定不能省略
4){}及里边的语句块统称"函数体"
5)函数体中的语句块可以是一条语句,也可以是多条语句,还可以没有语句
6)return:用来结束函数模块
7)若函数无返回值,则返回值类型为void,return 后无表达式 或 return直接不写;
3、函数声明
返回值类型 函数名(数据类型 形参1, 数据类型 形参2,....,数据类型 形参n);
eg:
void add(int a, int b,int *c);
声明位置:
1)声明在全局位置
2)声明在*.h文件
4、函数的调用
返回值类型 变量 = 函数名(实参1,实参2,实参3,…,实参n);
eg:
int m = 10;
int n = 20;
int sum = 0;
add(m,n,&sum);
注意:
a.形参和实参得个数要相同
b.对应位置的类型必须一致
5、函数传参
值传参:将实参空间的值拷贝一份到形参空间,所以,形参的改变一定不会影响到实参本身;
地址传参:将实参的地址传递给形参,所以,通过形参可以修改到实参
eg1: //值传参
void fun(char *p)
{
p = "world";
printf("%s\n",p);//world
}
int main()
{
char *p = "hello";
fun(p);//值传参
printf("%s\n",p);//hello
return 0;
}
eg2: //地址传参
void fun(char **ptr)
{
*ptr = "world";
printf("%s\n",*ptr);//world
}
int main()
{
char *p = "hello";
fun(&p);//地址传参
printf("%s\n",p);//world
return 0;
}
eg3: //局部变量造成的非法访问内存
char * fun() //指针函数
{
char buf[64]={0};
strcpy(buf,"hello world\n");
return buf;//error 不能返回局部变量的地址 原因:局部变量在函数模块结束之后系统会自动回收
}
int main()
{
char *p = fun();
puts(p);//非法访问内存地址空间
return 0;
}
2、数组传参
1、传参方式
eg:冒泡排序 指针接收
void sort(int *p, int len)//常见形式
{
printf("%d\n",sizeof(p)); //一个指针大小,32位机为4个字节,64位机为8个字节
}
int main()
{
int i = 0;
int a[] = {12,45,34,23,678,90,12,5,76,54};
int len = sizeof(a) / sizeof(int);
sort(&a[0],len);
for(i=0;i<len;i++)
printf("%d ",a[i]);
printf("\n");
return 0;
}
2、指针数组传参
void test(char *p[],int len)//数组形式
void test(char **p,int len)//指针形式
{
int i = 0;
for(i=0;i<len;i++)
printf("%s ",*(p+i));
printf("\n");
}
int main()
{
char *p[] = {"red","green","blue"};
int len = sizeof(p) / sizeof(p[0]);
test(&p[0],len);
return 0;
}
3、二维数组传参
void test(int (*p)[4], int len) //二维数组名:行指针
{
int i,j;
int row = len / 4;//计算行数
for(i=0;i<row;i++)
for(j=0;j<4;j++)
printf("%d ",*(p[i]+j));//p[i][j] *(*(p+i)+j)
printf("\n");
return ;
}
int main()
{
int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int len = sizeof(a) / sizeof(int);//计算元素个数
test(a,len);
return 0;
}
3、指针函数
1、本质:
是函数,函数的返回值是指针;
2、语法形式
数据类型 *函数名(形参列表)
{
//语句块
}
eg://指针函数
char *strcpy(char *dest, const char *src);
char *strcat(char *dest, const char *src);
eg1:
char * fun(char *p,int len)//指针函数
{
strcpy(p,"hello world");
return p;
}
int main()
{
char ch[64] ;
int len = sizeof(ch)/sizeof(char);
memset(ch, 0, sizeof(ch));//清0函数
//bzero(ch,sizeof(ch));//内存清0函数
char *p = fun(&ch[0],len);//地址传参
puts(p);
return 0;
}
4、函数指针(重)
1、本质
是指针,指针指向的是函数;
2、语法形式
返回值类型(*函数指正变量名)(数据类型形参1,数据类型 形参2,…,数据类型形参n);
例如:
#include <stdio.h>
// 定义一个函数指针类型
typedef int (*MathFunc)(int, int);
//这个语句将MathFunc定义为一个函数指针类型,指向任意两个int类型参数和一个int类型返回值的函数。
// 加法函数
int add(int a, int b) {
return a + b;
}
// 减法函数
int subtract(int a, int b) {
return a - b;
}
// 乘法函数
int multiply(int a, int b) {
return a * b;
}
// 执行数学运算函数
int performMathOperation(MathFunc mathFunc, int a, int b) {
return mathFunc(a, b);
}
int main() {
int a = 5, b = 3;
// 定义函数指针变量并赋值
MathFunc mathFunc = add;
// 使用函数指针调用函数
int result = mathFunc(a, b);
printf("加法结果: %d\n", result);
// 更改函数指针指向减法函数
mathFunc = subtract;
// 使用函数指针调用函数
result = mathFunc(a, b);
printf("减法结果: %d\n", result);
// 使用函数指针作为参数传递给另一个函数
result = performMathOperation(multiply, a, b);
printf("乘法结果: %d\n", result);
return 0;
}
面试题:当知道入口地址,返回值类型,参数类型去调用函数,可以使用函数指针去调用
5、回调函数
1.概念
指:当一个函数作为另外一个函数的参数时,此函数叫回调函数
eg:
void fun(void)
{
printf("fun...\n");
}
void test(void (*p)(void))
{
p();//调用fun()函数
}
int main()
{
test(fun);//fun()函数叫回调函数
return 0;
}
6、递归函数
1.概念
自己直接或间接调用自己,这种函数叫递归
2.示例:
//n:数据
int fac(int n)
{
if(n==1 || n==0)
return 1;
return (n*fac(n-1));
}
int main()
{
int n = 5;
int ret = fac(n);
printf("ret=%d\n",ret);
return 0;
}
7、函数指针数组
1.本质
是数组,数组元素是函数指针
2.语法形式
数据类型 (*数组名[元素个数]) (形参列表);
eg:
int add(int a,int b)
{
return a+b;
}
int sub(int a, int b)
{
return a-b;
}
int mul(int a, int b)
{
return a*b;
}
int main()
{
int (*a[3])(int,int); //函数指针数组
a[0] = add;
a[1] = sub;
a[2] = mul;
//add(10,20); (a[0])(10,20); (*a)(10,20); //等价
printf("%d %d %d\n",add(10,20),(a[0])(10,20),(*a)(10,20));
printf("%d %d %d\n",sub(20,10),(a[1])(20,10),(*(a+1))(10,20));
return 0;
}
8、typedef关键字
1.功能
给已有数据类型取别名;
2.语法形式
typedef 已有数据类型 别名;
eg:
typedef int INT;//给int取别名INT
typedef int * INT_T;//给int *取别名INT_T
typedef int (*fun_t)[4] ;//给 int(*)[4]取别名fun_t
typedef void (*fun_point)(int);//给 void (*)(int)取别名fun
eg:
fun_point a[3];//函数指针数组a
eg:
//void test(int (*p)[4],int len)
void test(fun_t p,int len)
{
}
int main()
{
int a[3][4] = {0};
int len = sizeof(a)/sizeof(int)
test(a,len);
return 0;
}
六、结构体
1、概述
1.理解
是一种数据类型,类似于:int char short
2.语法形式
struct [结构体名]{
数据类型 成员变量1;
数据类型 成员变量2;
…
数据类型 成员变量n;
};
eg:学生结构体:
struct student{
int id;//学号
char name[20];//姓名
char sex;//性别
int age;
float score;
};
3.结构体变量的定义
1)形式1 定义结构体时定义结构体变量
struct student{
int id;//学号
char name[20];//姓名
char sex;//性别
int age;
float score;
}stu1={1,"xiaoming",'m',18,99.5},stu2;//定义结构体时定义结构体变量
int main()
{
printf("%d\n",sizeof(stu1));
return 0;
}
2)形式2 先定义结构体,再定义结构体变量
eg1:
#include <iostream>
using namespace std;
// 定义一个结构体
struct Person {
string name;
int age;
string gender;
};
int main() {
// 定义结构体变量并初始化
Person p1 = {"Tom", 20, "male"};
// 访问结构体变量的成员
cout << "Name: " << p1.name << endl;
cout << "Age: " << p1.age << endl;
cout << "Gender: " << p1.gender << endl;
return 0;
}
eg2:
//定义在全局 struct student 描述学生结构体类型
struct student{
int id;//学号
char name[20];//姓名
char sex;//性别
int age;
float score;
};
int main()
{
struct student stu = {
.id = 1,
.name = "xiaoming",
.sex = 'm',
.age = 18,
.score = 99.5
};//定义结构体变量stu并初始化成员
printf("%d\n",stu);//结构体占空间大小=各个成员占空间大小之和(注意字节序对齐)
stu.score = 59.5;//赋值
stu.name = "xiaomin";//error 数组名是地址常量,不能作左值
strcpy(stu.name,"xiaomin");
printf("%d %s %c %d %.1f\n",stu.id,stu.name,stu.sex,stu.age,stu.score);
return 0;
}
4.结构体指针
1)语法形式
struct 结构体名 *指针变量名;
eg:
//结构体定义
struct student{
int id;//学号
char name[20];//姓名
char sex;//性别
int age;
float score;
};
int main()
{
struct student stu = {1,"xiaoming",'m',18,99.5};
struct student *p = &stu;
//结构体指针访问成员: 指针->成员名 即可
p->score = 59.5;//等价: (*p).score = 59.5;
p->name = "xiaomin";//error
strcpy(p->name, "xiaomin");
printf("%d %s %c %d %.1f\n",p->id,p->name,p->sex,p->age,p->score);
return 0;
}
2、malloc()/free()函数
1.功能
malloc:堆区申请空间
free:释放堆区空间
2.函数原型
#include <stdlib.h>
void *malloc(size_t size);//堆区申请空间
void free(void *ptr);//释放空间
eg1://给int变量申请空间
int main()
{
int *p = (int *)malloc(sizeof int);//在堆区给int变量申请空间
if(NULL == p)
{
printf("malloc failed\n");
return -1;
}
*p = 0x12345678;
free(p); //释放堆区空间
return 0;
}
eg2:给字符数组申请空间
int main()
{
char *p = (char *)malloc(sizeof(char [64]));
if(NULL == p)
{
printf("malloc failed\n");
return -1;
}
//p = "hello world";//修改p的指向
strcpy(p,"hello world");
puts(p);
free(p);
return 0;
}
eg3:给结构体申请空间
//结构体定义
struct student{
int id;//学号
char name[20];//姓名
char sex;//性别
int age;
float score;
};
int main()
{
struct student *p = (struct student *)malloc(sizeof(struct student));//堆区给结构体申请空间
if(NULL == p)
{
printf("malloc failed\n");
return -1;
}
//结构体指针访问结构体成员
p->id = 1;
strcpy(p->name,"zhangs");
p->sex = 'm';
p->age = 18;
p->score = 88.5;
printf("%d %s %c %d %.1f\n",p->id,p->name,p->sex,p->age,p->score);
free(p);
p = NULL;
return 0;
}
3、结构体数组
1.本质
是数组,数组中元素是结构体;
2.语法形式
struct 结构体名 数组名[元素个数];
struct student{
int id;//学号
char name[20];//姓名
char sex;//性别
int age;
float score;
};
int main()
{
struct student a[100];//定义结构体数组a,a中有3个元素,每个元素是结构体变量
a[0].id=1;
strcpy(a[0].name,"xiaoming");
a[0].sex = 'm';//等价: (*a).sex = 'm';
a[0].age = 18;
a[0].score = 90;
a[1] = a[0];
a[2] = a[0];
return 0;
}
4、结构体指针数组
1.本质
是数组,数组中各个元素类型是结构体指针
2.语法形式
struct 结构名 * 数组名[元素个数];
typedef struct student{
int id;
char name[20];
char sex;
int age;
float score;
}stu_t; //stu_t <=> struct student
int main()
{
stu_t stu1 = {1,"xiaom",'m',18,90};
stu_t stu2 = {2,"xiaoh",'w',18,65.5};
stu_t stu3 = {3,"xiaod",'m',18,59.5};
stu_t *a[3];//a[0] a[1] a[2]
a[0] = &stu1;
a[1] = &stu2;
a[2] = &stu3;
//*a <=>a[0]
printf("%d %s %c %d %.1f\n",a[0]->id,a[0]->name,a[0]->sex,a[0]->age,a[0]->score);
printf("%d %s %c %d %.1f\n",(*a[0]).id,(*a[0]).name,(*a[0]).sex,(*a[0]).age,(*a[0]).score);
return 0;
}
5、无名结构体
1.语法形式
struct {
数据类型 成员变量1;
…
数据类型 成员变量n;
};
typedef struct{
int id;
char name[20];
char sex;
int age;
float score;
}stu_t;
struct stu1;//error
stu_t stu1;//right
八、union共用体
1.理解
是一种自定义数据类型,与结构体类似。
2.语法形式
union 共用体名{
数据类型 成员变量;
数据类型 成员变量;
....
数据类型 成员变量n;
};
//定义共用体
union test{
char x;
int a;
char ch[20];
float f;
double e;
};
int main()
{
union test t;//共用体变量
printf("%d\n",sizeof(t));//共用体占空间大小=占空间最大的成员空间
t.x = 'x';
t.a = 123;
strcpy(t.ch,"hello world");//共用体各个成员共用同一片空间
printf("%p %p %p\n",&(t.x),&(t.a),&(t.ch));
return 0;
}
九、static关键字
1.修饰局部变量
改变了局部变量的生命周期
void fun()
{
int a = 123;//栈区 作用域:局部域 生命周期:定义开始 函数模块结束时结束
static int b = 321;//静态存储区 作用域:局部域 生命周期:定义开始 程序结束时结束
a++;
b++;
printf("a=%d b=%d\n",a,b);
}
int main()
{
fun();//a=124 b=322
fun();//a=124 b=323
return 0;
}
2.修饰全局变量
static修饰全局变量,修改了全局变量的作用域
static修饰的全局变量具有以下特征:
-
静态存储:静态全局变量在程序运行期间一直存在,不会因为函数调用的结束而被销毁,直到程序结束才会释放内存。
-
作用域限制:静态全局变量的作用域仅限于定义它的源文件,其他源文件无法访问该变量,可以说是私有的。
-
默认初始化:静态全局变量没有被显式初始化时,会被默认初始化为0,或者指针类型被初始化为NULL。
-
全局可见性:虽然静态全局变量的作用域受限,但其在定义源文件中的所有函数中都可见,可以被多个函数共享和访问。
-
生命周期延长:静态全局变量的生命周期比非静态全局变量长,即使在函数调用结束后,其值仍保持不变,下次调用该函数时可以继续使用原来的值。
-
可以被修改:静态全局变量在定义之后可以被修改,但修改操作会影响到所有使用该变量的地方。
需要注意的是,在多线程环境下使用静态全局变量需要进行同步操作,以避免数据竞争的问题。
3.修饰函数
修改了函数的作用域
vi fun.c:
static void prnmsg(const char *str);
void fun()
{
printf("fun....\n");
prnmsg("fun:hello fun!");//right static函数只能在本文件调用
}
static void prnmsg(const char *str)//静态函数:只能在本文件调用 修改了函数的作用域
{
printf("str:%s\n",str);
}
vi main.c:
void fun();
int main(int argc, char *argv[])
{
fun();
//prnmsg("hello fun!");//error prnmsg()是静态函数 不能在其他文件中调用
return 0;
}
int age;
float score;
}stu_t;
struct stu1;//error
stu_t stu1;//right
## 八、union共用体
1.理解
是一种自定义数据类型,与结构体类似。
2.语法形式
```c
union 共用体名{
数据类型 成员变量;
数据类型 成员变量;
....
数据类型 成员变量n;
};
//定义共用体
union test{
char x;
int a;
char ch[20];
float f;
double e;
};
int main()
{
union test t;//共用体变量
printf("%d\n",sizeof(t));//共用体占空间大小=占空间最大的成员空间
t.x = 'x';
t.a = 123;
strcpy(t.ch,"hello world");//共用体各个成员共用同一片空间
printf("%p %p %p\n",&(t.x),&(t.a),&(t.ch));
return 0;
}
九、static关键字
1.修饰局部变量
改变了局部变量的生命周期
void fun()
{
int a = 123;//栈区 作用域:局部域 生命周期:定义开始 函数模块结束时结束
static int b = 321;//静态存储区 作用域:局部域 生命周期:定义开始 程序结束时结束
a++;
b++;
printf("a=%d b=%d\n",a,b);
}
int main()
{
fun();//a=124 b=322
fun();//a=124 b=323
return 0;
}
2.修饰全局变量
static修饰全局变量,修改了全局变量的作用域
static修饰的全局变量具有以下特征:
-
静态存储:静态全局变量在程序运行期间一直存在,不会因为函数调用的结束而被销毁,直到程序结束才会释放内存。
-
作用域限制:静态全局变量的作用域仅限于定义它的源文件,其他源文件无法访问该变量,可以说是私有的。
-
默认初始化:静态全局变量没有被显式初始化时,会被默认初始化为0,或者指针类型被初始化为NULL。
-
全局可见性:虽然静态全局变量的作用域受限,但其在定义源文件中的所有函数中都可见,可以被多个函数共享和访问。
-
生命周期延长:静态全局变量的生命周期比非静态全局变量长,即使在函数调用结束后,其值仍保持不变,下次调用该函数时可以继续使用原来的值。
-
可以被修改:静态全局变量在定义之后可以被修改,但修改操作会影响到所有使用该变量的地方。
需要注意的是,在多线程环境下使用静态全局变量需要进行同步操作,以避免数据竞争的问题。
3.修饰函数
修改了函数的作用域
vi fun.c:
static void prnmsg(const char *str);
void fun()
{
printf("fun....\n");
prnmsg("fun:hello fun!");//right static函数只能在本文件调用
}
static void prnmsg(const char *str)//静态函数:只能在本文件调用 修改了函数的作用域
{
printf("str:%s\n",str);
}
vi main.c:
void fun();
int main(int argc, char *argv[])
{
fun();
//prnmsg("hello fun!");//error prnmsg()是静态函数 不能在其他文件中调用
return 0;
}