C语言自学笔记-进阶

前言:

学完了入门课程,正巧上一个页面在编辑的时候已经会发生卡顿的情况了,所以开了新的页面。链接最后一段想不好该叫什么,暂时写作https://lingyun67.cn/cplus/

指针的使用

交换两个变量的值:

void swap(int *pa, int *pb)
{
    int t = *pa;
    *pa = *pb;
    *pb = t;
}

使用的时候注意传进去的是地址

函数返回多个值:

函数返回多个值,某些值就只能通过指针返回
传入的参数实际上是需要保存带回的结果的变量

如传入数组,数组长度,即将接受最大最小值的两变量的地址,函数最终运行结束最大最小值将得到赋值。

函数返回状态,结果用指针返回:

函数返回运算的状态,结果通过指针返回
常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错
-1或0(在文件操作会看到大量的例子)
当任何数值都可能是有效的结果时,就得分开返回:即状态return,实际值用指针。
因为运算可能会出错,所以需要返回状态。在后续的语言(C++,Java)采用了异常机制来解决这个问题

初学者的常见错误:

可以看作是入门课程48.1 常见错误的补充:
定义指针后不初始化,它会随机指向一个地址,这个时候对它进行写入,很可能会导致错误。
所以任何一个地址变量,在没有得到赋值之前、没有得到任何有实际意义的地址之前,不能够用*去访问任何的数据,那都是没有任何意义的。

向函数传入数组传入的是什么:

入门篇42.5出现的课程,完全一样。个人感觉在入门篇中出现的少数“突然出现的”课程就是从进阶篇中直接转过去的。

指针与const(C99)

指针是const:

表示一旦得到了某个变量的地址,不能再指向其他变量

int *const q = &i; //q是const
*q = 26; //OK
q++; /ERROR

所指是const:

表示不能通过这个指针去修改那个变量(注意:并不会使那个变量成为const)

const int *q = &i;
*q = 26; //ERROR (*p)是const
i = 26; //OK
p = &j; //OK

多个写法:

int i;
int *const p1 = &i;
const int *p2 = &i;
int const *p3 = &i;

判断哪个被const了的标志是const在*前面还是后面
所以p2与p3相等

转换:

总是可以把一个非const的值转换成const的

void f(const int *x);
int a = 15;
f(&a); //OK
const int b = a;
f(&b); //OK

事实上void f(const int *x)的意思是这个函数保证不会修改传进来的这个地址里面的值。

当要传递的参数的类型比地址大的时候,这是常用的手段:技能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改。

const数组:

const int a[] = {1,2,3,4,5,6,};
数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int
所以必须通过初始化进行赋值。

所以可以对数组值进行保护:
因为把数组传入函数时传递的时地址,所以那个函数内部可以修改数组的值
为了保护数组不被函数破坏,可以设置参数为const
int sum(const int a[], int length);

指针运算

指针+1:

有int *p = ai;对比p+1与p
p+1比p大4 因为int的长度是4字节

而char *q = ac;对比q+1与q
q+1比q大1 因为char的长度是1字节

0号位第一个字节的地址是2c,那么q+1就会移动到30也就是一号位
为什么移动的是变量类型的长度,因为如果直接让地址本身加1,那么得到的地址毫无意义(会取前面单元里的三个和后面的单元里一个一共四个字节)

星号是一个单目运算符,他的优先级比加号高,所以要这样*(p+1)

给一个指针加1表示要让指针指向下一个变量
int a[10];
int *p = a;
*(p+1) –> a[1]
如果指针不是指向一片连续分配的空间如数组,则这种运算没有实际意义。

除了加,减一个整数 递增递减以外,指针还可以:

两个指针相减:

两个指针相减,得到的不是两个指针的地址差,得到的是地址差除以变量类型的长度。

*p++:

*的优先级没有++高,所以
取出p所指的那个数据来,完事之后顺便把p移到下一个位置去
(个人感觉其实是先移动指针p的位置,然后输出的是移动之前的数据)

常用于数组类的连续空间操作
在某些cpu上,这可以直接被翻译成一条汇编指令。

指针比较:

<,<=,==,>,>=,!= 都可以对指针做
比较它们在内存中的地址
数组中的单元的地址肯定是线性递增的

0地址:

多进程的操作系统会给进程分配一个虚拟的地址空间,所有的程序在运行的时候都以为自己拥有一片从“0”开始的连续的空间

当然内存中有0地址,但是0地址通常是不能随便碰的地址
所以指针不应该具有0值
因此可以用0地址来表示特殊的事情:
返回的指针是无效的
指针没有被真正初始化(先初始化位0)
NULL(注意大写)是一个预定定义的符号,表示0地址
有的编译器不愿意你用0来表示0地址

指针的类型:

有一个指向char类型的p 一个指向int的q
如果这件事真的做成了,代表指向int的指针q会在表示char的区域取四个字节

无论指向什么类型,所有的指针的大小都是一样的,因为都是指针。
但是指向不同类型的指针是不能直接互相赋值的,这是为了避免用错指针。

指针的类型转换:

void* 表示不知道只想什么东西的指针
计算时与char*相同(但不相通)
往往会用在底层程序,系统程序中,希望直接去访问某个内存地址,直接去访问内存地址所代表的一些外部设备等。
指针可以转换类型
int *p = &i; void *q = (void*)p;
这并没有改变p所指的变量的类型,而是让后人用不同的眼光通过p看它所指的变量。通过p看i,就是int;通过q看i那就是void。

总结,用指针来做什么:

需要传入较大的数据时用作参数
传入数组后用指针对数组做操作
函数返回不止一个结果
需要用函数不止一个变量
动态申请的内存..

动态内存分配

输入数据:

如果输入数据时,先告诉个数,然后再输入,要记录每个数据
C99可以用变量做数组定义的大小,那么C99之前:

int *a (int*)malloc(n*sizeof(int))

比如要三十个int 那就是告诉malloc我需要120个字节。得到的是void*所以需要转换成int*

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int number;
	int* a = NULL;
	int i;
	printf("输入数量:");
	scanf_s("%d", &number);
	//int a[number]; C99only
	a = (int*)malloc(number * sizeof(int)); //给a分配number个int的空间
	for (i = 0; i < number; i++) {
		scanf_s("%d", &a[i]);
	}
	for (i = number - 1; i >= 0; i--) {
		printf("%d", a[i]);
	}
	free(a); //释放内存

	return 0;
}

需要调用标准库stdlib.h
void* malloc(size_t size);
向malloc申请的空间的大小是以字节为单位的
返回的结果是void*,需要类型转换为自己需要的类型
(int*)malloc(n*sizeof(int));

没空间了:

如果申请失败则返回0,或者叫做NULL
你的系统能给你多大的空间?

free():

把申请的来的空间还给“系统”
申请过的空间,最终都应该还回去
只能归还申请来的空间的首地址

free一个地址0(NULL)是可以的,free函数判断如果输入了一个NULL那就不做任何事情,为的是配合p = 0的好习惯,当p在malloc中没有得到数据,这时它为0,如果是1或类似的则不行。

常见问题:
申请了没free->长时间运行内存逐渐下降
新手:忘了
老手:找不到合适的free时机
free过了再free
地址变过了直接去free

没有free对小程序来说不是问题,因为程序结束之后操作系统的保护机制会将程序曾经使用过的所有的内存都会清除干净,但是并不是个好习惯。

建议:
1:牢牢记住malloc之后需要free
2:对程序的整体架构有一个良好的设计,保证有合适的地方去放free
3:经验,多阅读别人的代码,多在失败中总结

字符串操作

单字符输入输出:

int putchar(int c);
向标准输出写一个字符
它接受的是int 但要输入一个字符的char
返回写了几个字符,是一个int,EOF(-1)表示写失败

int getchar(void);
从标准输入读入一个字符
它返回从标准输入中读到的字符
返回类型是int是为了返回EOF(-1)

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注