C语言基础

C语言基础知识点

一、入门知识

程序:一系列的代码的有序集合

进程:一个正在运行的程序

1、vim的使用

vi/vim 文件名
vi 打开文件有三种模式:
1、命令行模式:
复制、粘贴、剪切、撤销…
按 i、a、o切换到插入模式
按shift + : 进入底行模式
2、插入模式:
编辑内容
按Esc进入命令行模式
3、底行模式:
(/)查找、字符串替换、保存(w)、退出(q)
按ESC退出底行模式进入命令行模式

2、gcc编译过程

预处理、编译、汇编、链接

预处理(宏展开、头文件包含)

编译(翻译成汇编语言)

汇编(汇编器将编译生成的汇编代码翻译成机器指令)

链接:将.o文件和一些库文件链接在一起生成可执行文件

二、C语言基础

1、关键字和符号
  1. 关键字:while if int …
  2. 标识符: int a = 10; a就是标识符
  3. 分隔符:;
  4. 运算符:+ … sizeof &
  5. 标点符号:{} ()
2、C语言基础数据类型
  1. char:字符类型(占一个字节)

    占用一个字节,一个字节有8个bit位
    有符号数:最高位为符号位0表示正数1表示数
    不考虑正负:0000 0000 ~ 1111 1111 -> 0~255
    
    有符号数的值域:-128~127
    
  2. == short:短整型(占2个字节)==

    不考虑正负值域:0 ~ 2^16 - 1(65535)
    有符号数的值域:-32768 ~ 32767
    
  3. 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后缀

  1. 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修饰的全局变量具有以下特征:

  1. 静态存储:静态全局变量在程序运行期间一直存在,不会因为函数调用的结束而被销毁,直到程序结束才会释放内存。

  2. 作用域限制:静态全局变量的作用域仅限于定义它的源文件,其他源文件无法访问该变量,可以说是私有的。

  3. 默认初始化:静态全局变量没有被显式初始化时,会被默认初始化为0,或者指针类型被初始化为NULL。

  4. 全局可见性:虽然静态全局变量的作用域受限,但其在定义源文件中的所有函数中都可见,可以被多个函数共享和访问。

  5. 生命周期延长:静态全局变量的生命周期比非静态全局变量长,即使在函数调用结束后,其值仍保持不变,下次调用该函数时可以继续使用原来的值。

  6. 可以被修改:静态全局变量在定义之后可以被修改,但修改操作会影响到所有使用该变量的地方。

需要注意的是,在多线程环境下使用静态全局变量需要进行同步操作,以避免数据竞争的问题。

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修饰的全局变量具有以下特征:

  1. 静态存储:静态全局变量在程序运行期间一直存在,不会因为函数调用的结束而被销毁,直到程序结束才会释放内存。

  2. 作用域限制:静态全局变量的作用域仅限于定义它的源文件,其他源文件无法访问该变量,可以说是私有的。

  3. 默认初始化:静态全局变量没有被显式初始化时,会被默认初始化为0,或者指针类型被初始化为NULL。

  4. 全局可见性:虽然静态全局变量的作用域受限,但其在定义源文件中的所有函数中都可见,可以被多个函数共享和访问。

  5. 生命周期延长:静态全局变量的生命周期比非静态全局变量长,即使在函数调用结束后,其值仍保持不变,下次调用该函数时可以继续使用原来的值。

  6. 可以被修改:静态全局变量在定义之后可以被修改,但修改操作会影响到所有使用该变量的地方。

需要注意的是,在多线程环境下使用静态全局变量需要进行同步操作,以避免数据竞争的问题。

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;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值