typedef关键字
typedf是类型重定义
#include <stdio.h>
typedef unsigned int uint;
typedef struct Node
{
int data;
struct Node *next;
} Node;
int main()
{
uint i = 11;
printf("%d\n", i);
Node n;
n.data = 12;
printf("%d\n", n.data);
return 0;
}
static关键字
在C语言中:
static
1.修饰局部变量
2.修饰全局变量
3.修饰函数
test.c
#include <stdio.h>
// extern int g_val;
extern int add(int i);
void test()
{
// 修饰局部变量
// static修饰局部变量时,局部变量出了作用域也不会销毁
// 本质上是改变了变量的存储位置,影响了变量的生命周期,使得变量的生命周期变长和程序的生命周期一样
static int a = 1;
a++;
printf("a = %d\n", a);
}
int main()
{
int i = 0;
while (i < 10)
{
/* code */
test();
i++;
}
// printf("g_val = %d\n", g_val);
i = add(i);
printf("i = %d\n", i);
return 0;
}
add.c
// 修饰全局变量
// 全局变量具有外部链接属性
// static修饰的时候就会变成内部链接属性,其他源文件不可使用
static int g_val = 2024;
// 修饰函数
// 函数具有外部链接属性
// static修饰的时候就会变成内部链接属性,其他源文件不可使用
int add(int i)
{
return i++;
}
sizeof和length的区分
指针
指针偏移的使用场景
#include<stdio.h>
void change(int *d);
int main(){
// 数组名作为实参传递给子函数时,数组名弱化为指针
char c[10]="Hello";
change(c);
puts(c);
return 0;
}
void change(int *d){
*d='H';
d[1]='S';
*(d+2)='P';
}
指针与malloc动态内存申请
#include <stdio.h>
#include <stdlib.h> //malloc使用的头文件
#include <string.h> //strcpy使用的头文件
int main(){
int size;//size代表我们要申请多大的字节空间
char *p;//void*类型的指针不能偏移,因此不会定义无类型指针
scanf("%d",&size);//输入要申请的空间大小
//malloc返回的void*表示无类型指针
p=(char*)malloc(size);
strcpy(p,"malloc success!");
puts(p);
free(p);//释放申请的空间
return 0;
}
栈和堆的差异
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 栈空间————计算机系统提供的数据结构,效率比较高
// 字符串存放在栈空间,函数执行完后,栈空间会被释放
char *print_stack() // 栈空间
{
char c[100] = "I am print_stack func";
char *p;
p = c;
puts(p);
return p;
}
// 堆空间————C/C++函数库提供的数据结构,效率比栈低
// malloc的字符串存放在堆空间,而堆空间只有在执行free操作后才释放
char *print_malloc() // 堆空间
{
char *p = (char *)malloc(100); // 堆空间在整个进程中一直有效,不因为函数结束而消亡
strcpy(p, "I am print_malloc func");
puts(p);
return p;
}
int main()
{
char *p;
p = print_stack();
puts(p);
p = print_malloc();
puts(p);
free(p); // 只有free的时候堆空间才会释放
return 0;
}
复杂指针类型说明(拓展)
- int p; //这是一个普通的整型变量
- int *p; //首先从P 处开始,先与*结合,所以说明P 是一个指针,然后再与int 结合,说明指针所指向的内容的类型为int 型.所以P是一个返回整型数据的指针
- int p[3]; //首先从P 处开始,先与[]结合,说明P 是一个数组,然后与int 结合,说明数组里的元素是整型的,所以P 是一个由整型数据组成的数组
- int *p[3]; //首先从P 处开始,先与[]结合,因为其优先级比*高,所以P 是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以P 是一个由返回整型数据的指针所组成的数组——即指针数组
- int (*p)[3]; //首先从P 处开始,先与*结合,说明P 是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的.所以P 是一个指向由整型数据组成的数组的指针 ——即数组指针
- int **p; //首先从P 开始,先与*结合,说是P 是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据.由于二级指针以及更高级的指针极少用在复杂的类型中,所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针
- int p(int); //从P 处起,先与()结合,说明P 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据
- int (*p)(int); //从P 处开始,先与指针结合,说明P 是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以P 是一个指向有一个整型参数且返回类型为整型的函数的指针
- int *(*p(int))[3]; //可以先跳过,不看这个类型,过于复杂从P 开始,先与()结合,说明P 是一个函数,然后进入()里面,与int 结合,说明函数有一个整型变量参数,然后再与外面的*结合,说明函数返回的是一个指针,,然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,然后再与*结合,说明数组里的元素是指针,然后再与int 结合,说明指针指向的内容是整型数据.所以P 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数.
指针类型VS指针所指向的类型
- int*ptr;//指针的类型是int*——>指针所指向的类型是int
- char*ptr;//指针的类型是char*——>指针所指向的的类型是char
- int**ptr;//指针的类型是int**——>指针所指向的的类型是int*
- int(*ptr)[3];//指针的类型是int(*)[3]——>指针所指向的的类型是int()[3]
- int*(*ptr)[4];//指针的类型是int*(*)[4]——>指针所指向的的类型是int*()[4]
函数
函数的声明和定义
函数之间调用的关系,由主函数调用其他函数,其他函数也可以相互调用,同一个函数可以被一个或多个函数调用任意次
无参函数无需返回值,有参函数必须要有返回值
#include <stdio.h>
int add(int x, int y); //函数的声明
int main(){
int a = add(5,6);
int b = add(7,8);
return 0;
}
int add(int x, int y){
return x+y;
}
函数嵌套调用
函数可以嵌套调用不可以嵌套定义
#include <stdio.h>
#include <stdlib.h>
int printstar(int i);
void print_message();
int main(){
int a=10;
a=printstar(a);
print_message();
printstar(a);
return 0;
}
int printstar(int i){
printf("printstar:%d\n",i);
return i+3;
}
void print_message(){
printf("how do you do\n");
printstar(3);
}
递归
递归的核心是找公式
为什么使用递归?
使用递归解决一些问题时,可以让问题变得简单,降低编程难度
#include <stdio.h>
int f(int n)
{
// 一定要有结束条件
if (n == 1)
{
return 1;
}
return n * f(n - 1); // 写公式
}
int main()
{
int n = 0;
scanf("%d", &n);
printf("n的阶层为:%d", f(n));
return 0;
}
递归实现汉诺塔
#include <stdio.h>
// 定义汉诺塔函数,参数为盘子数量n和三个柱子A、B、C
void hanoi(int n, char A, char B, char C) {
// 如果只有一个盘子,直接将其从A柱移动到B柱
if (n == 1) {
printf("Move disk 1 from %c to %c
", A, B);
} else {
// 将n-1个盘子从A柱借助C柱移动到B柱
hanoi(n - 1, A, C, B);
// 将最大的盘子从A柱移动到B柱
printf("Move disk %d from %c to %c
", n, A, B);
// 将n-1个盘子从C柱借助A柱移动到B柱
hanoi(n - 1, C, B, A);
}
}
int main() {
int n = 3; // 盘子数量
hanoi(n, 'A', 'B', 'C'); // 起始柱子为A,目标柱子为B,辅助柱子为C
return 0;
}
结构体
结构体的定义、初始化、结构体数组
#include <stdio.h>
// 结构体定义
struct student
{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
};
int main()
{
struct student s={1001,"lele",'M',20,85.4,"Shenzhen"}; //定义及初始化
struct student sarr[3]; //定义结构体数组
int i;
// 分别打印结构体s里面的成员
printf("%d %s %c %d %f %s\n",s.num,s.name,s.sex,s.age,s.score,s.addr);
// 分别输入结构体数组sarr里面每个结构体的成员
for(i=0;i<3;i++)
{
scanf("%d%s %c%d%f%s",&sarr[i].num,sarr[i].name,&sarr[i].sex,&sarr[i].age,
&sarr[i].score,sarr[i].addr);
}
// 分别输出结构体数组sarr里面每个结构体的成员
for(i=0;i<3;i++)
{
printf("%d %s %c %d %f %s\n",sarr[i].num,sarr[i].name,sarr[i].sex,
sarr[i].age,sarr[i].score,sarr[i].addr);
}
return 0;
}
注意:
- 结构体类型声明要放在main 函数之前,这样main 函数中才可以使用这个结构体,工作中往往把结构体声明放在头文件中。
- 结构体的初始化只能在一开始定义,如果struct students={1001,"lele",'M',20,85.4,"Shenzhen"}已经执行,即struct student s 已经定义,就不能再执行s={1001,"lele",'M',20,85.4,"Shenzhen"}。如果结构体变量已经定义,那么只能对它的每个成员单独赋值
结构体对齐
记:结构体的大小必须是其最大成员的整数倍
#include <stdio.h>
struct student_type1{
double score; // double 8个字节
short age; // short 2个字节
};
struct student_type2{
double score;
int height; // int 在32位系统中,int通常占用4个字节。在64位系统中,int可能占用8个字节。
short age;
};
struct student_type3{
int height;
char sex; // char 1个字节
short age;
};
int main(){
struct student_type1 s1;
struct student_type2 s2;
struct student_type3 s3;
printf("s1 size=%d\n",sizeof(s1));//16
printf("s2 size=%d\n",sizeof(s2));//16
printf("s3 size=%d\n",sizeof(s3));//8
return 0;
}
结构体指针
一个结构体变量的指针就是该变量所占据的内存段的起始地址。可以设置一个指针变量,用它指向一个结构体变量,此时该指针变量的值是结构体变量的起始地址。指针变量也可以用来指向结构体数组中的元素,从而能够通过结构体指针快速访问结构体内的每个成员。
#include <stdio.h>
// 结构体指针
struct student
{
int num;
char name[20];
char sex;
};
int main()
{
struct student s = {1001, "wangle", 'M'};
struct student sarr[3] = {{1001, "lilei", 'M'}, {1005, "zhangsan", 'M'}, {1007, "lili", 'F'}};
struct student *p; // 定义结构体指针
p = &s;
printf("%d %s %c\n", p->num, p->name, p->sex);
p = sarr;
// “.”优先级高于“*”
printf("%d %s %c\n", (*p).num, (*p).name, (*p).sex); // 方式一获取成员
printf("%d %s %c\n", p->num, p->name, p->sex); // 方式二获取成员
printf("------------------------------\n");
p = p + 1;
printf("%d %s %c\n", p->num, p->name, p->sex);
return 0;
}
typedef 在结构体中的使用
#include <stdio.h>
// 结构体指针
typedef struct student
{
int num;
char name[20];
char sex;
} stu, *pstu;
int main()
{
stu s = {1001, "wangle", 'M'};
pstu p;
p = &s;
printf("p->num=%d\n", p->num);
return 0;
}