指针

  • 基本用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int a=10;
int *p; //定义指针
p=&a; //指针指向a的地址
printf("%d",*p); //使用,解引用p得到a,*为还原指针指向的变量

//用指针定义后,两者意义相同(p等价于&a)
printf("%p",&a);
printf("%p",p);

//用指针定义后,两者意义相同(*p等价于a)再次用*即解引用
printf("%d",a);
printf("%d",*p);

//可以通过指针直接访问指向对象的地址
  • 意义:存储变量的地址
    指针地址为十六进制的值,如果以%d输出地址将转化为十进制输出
    找到地址->依靠指针类型得到数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void swap(int a,int b)
{
int temp=b;
b=a;
a=temp;
}

swap(c,d)
/*函数中的参数传递方式是值传递,即将实参的值复制一份传递给形参,而不是传递实参的地址,因此在 swap 函数中修改的是形参 a 和 b 的值,不会影响到原始调用函数时传入的实参
(形参不能改变实参,a,b改变不会改变c,d的值)*/
void swap(int *a, int *b)
{
int temp = *b;
*b = *a;
*a = temp;
}

swap(&c,&d);
//使用指针,使形参可以更改实参的值(因为是直接将地址传递给形参)
  • 指针做加减法:指针加减某个整数:指向前或向后偏移几个位置,使其指向前/后几个位置
    ***指针之间相减:得到两个指针之间间隔的元素数量,即偏移量(地址上相差几个位置)
  • 打印指针的地址:***%p***(打印指针指向的对象是用对象的占位符,如整数就是%d)
  • 多重指针:一个指针指向零一个指针
  • 指针数组:一个数组里储存的全是指针
  • 函数指针:写法int/void (*p)() = Func;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void Func() { 
printf("Hello World!\n"); //定义函数
}
int main() {
void (*p)() = Func; // 定义函数指针(括号先解引用,再用于定义函数)
p(); //可以通过函数指针直接调用函数
return 0;
}
//函数指针需要与函数同一类型
int Func() {
printf("Hello World!");
return 1;
}
int main() {
int a;FunC
int (*p)() = FunC; //有返回值的也可以
a=p();
printf("%d",a);
return 0;
}
//定义函数指针时括号内形参要与函数保持一致
//int (*p)(int a) = FunC; 等价于 int FunC(int a);
/*函数指针能够实现动态调用,通过改变指针的指向在不同阶段调用不同的函数(指针能够提高程序的效率,如函数名较长写清楚该段函数意义,而用一个简单的指针如p调用该函数,可以有效减少代码量)
//函数指针可以作为参数传递运用于复杂的函数嵌套

const int *p1 = a; //指针指向的a是常量,不能通过指针修改a的值,只能访问a的值
int *const p2 = a; //指针是常量,指向的地址不可变,a可以改变(即不可以重定义指针的指向)

动态内存分配

  • malloc在堆上分配内存,通过free手动回收内存
1
2
3
4
5
6
7
8
9
//malloc语法写法(只能定义指针
#include <malloc.h> //malloc头文件

int *ptr=(int*)malloc(4); //括号内为分配的内存数,也可以写sizeof(int)就是4
free(ptr); //要与malloc成组搭配使用,防止内存泄漏
*ptr = 10; *ptr = a; //可以正常指向变量

void *p = //void是空,即可以指向任何类型,但无法被解引用
int *p = &a //野指针:指向已经被销毁的变量
  • int a在栈上分配内存,生命周期结束后自动回收内存
  • 生命周期:所在函数结束,大括号代表生命周期结束(全局变量会始终存在)
    堆和栈都是内存中的区域
  • 内存泄漏:用malloc分配后未释放,形成无用内存(影响程序运行)
    malloc记得和free成组搭配使用

字符串

  • 内存布局:环境,栈,堆,未初始化数据段(BSS)和初始化数据段(DS)
  • 字符串声明方式
1
2
3
char t[] = "Hello World";  //字符数组方式
char t* = "Hello World"; //指针方式
//区别:指针方式是声明一个指向字符串(常量数组)的指针,不能通过指针改变字符串的内容
  • 字符串输入方式
1
2
3
4
scanf("%s",str);
scanf("%[^\n]",str);
gets(str); //已弃用,不安全(超过100会覆盖原有字符)
fgets(str,100,stdin) //会把回车也读进去,最多储存100个字符,超出会截断(不再覆盖)
  • 字符串输出方式
1
2
3
printf("%s",str);
puts(str);
fputs(str,stdout); //stdin位标准输入,stdout为标准输入(std表示标准)

结构体

  • 定义:一种数据结构,它由不同类型的数据组合成一个整体,以便引用,这些组合在一个整体中的数据是互相联系的,这样的数据结构称为结构体
1
2
3
4
5
6
//声明
struct student{ //student为结构体名
char *name; //结构体里也能定义指针
int age; //大括号内为成员列表
double score;
}
  • 定义类型typedef:为已有的数据类型定义一个新名字(类型别名) def意味define-定义
1
2
3
4
5
6
7
8
9
10
11
typedef int myint;  //把int定义成myint(typedef为定义一个新类型:用已有类型定义)
myint a = 1; //刺身就等价于int a = 1;
//也可以用typedef定义结构体
typedef struct {
char *name;
int age;
double score;
} student; //这样每次声明变量前不用再写struct(把这一堆定义成student)

student a; //定义类型student后这样写就行
student b; //声名第2个同样的结构体
  • 结构体初始化:
1
2
3
4
5
6
7
8
struct student{  
char name[20];
int age;
double score;
}a={"ytm666",114514,1919.810},b={"hades",222222,52.886};
//可以用逗号隔开,初始化2个结构体变量
struct student b={"hades",222222,52.886};
是student,因此可省略struct
  • 访问与修改结构体的值:
1
2
3
4
5
a.age=20;  //修改结构体的值
printf("%d",a.age); //访问结构体的值

struct student *p = &a; //先定义指针
printf("%d",(*p).age); //加括号,先解引用,再加后缀)

printf(“%d”,p->age); //指针第二两种写法(代表指针指向的值

  • 结构体只能存放变量,不可以定义函数,但我们可以在结构体中定义函数指针来调用不同函数
1
2
3
4
5
6
7
8
9
typedef struct {  
void (*p)(); //可以先定义,再指向函数
int age;
double score;
} student;

student a={"ytm666",114514,1919.810};
a.p() = Func1; //指向函数Func1(p经过指针定义*后,a.p意为指针指向函数Func1)
a.p() = Func2; //重新指向函数Func2
  • 32位系统中4字节一组(存储一个地址),指针占4个字节; eg:00 00 00 01
  • 64位(当今)系统中8字节一组,指针占8个字节;eg:00 00 04 01 00 00 04 02
    (现在都是64位计算机,因此指针默认占8个字节
  • 结构体内存对齐规则:
    1.第一个成员在与结构体变量偏移量为0的地址处 2.从第二个成员开始,以后每个成员都要对齐到某个对齐数的整数倍位置,这个对齐数是自身成员大小和默认成员对齐数的较小值 3.当成员全部放入后,结构体的总大小必须是所有成员对齐数(每个成员变量都有一个对齐数)中最大对齐数的整数倍
typedef struct { 
    char b;
    int *a;
    char c;
    int e;
} student;

- 对齐的意义:便于计算机访问,适配多种平台,提升运行速度