C语言 笔记 1

OrzMiku / 2023-08-20 / 原文

指针有什么用?

场景A

通过函数交换两个变量的值

eg. 交换变量a,b的值

int swap(int *a, int *b){
	int temp = 0;
	temp = *a;
	*a = *b;
	*b = temp;
}

场景B

返回结果有多个,或return返回状态,指针返回结果

int divide(int a,int b,float *res){
	int ret = 1;
	if(b != 0){
		*res = (float)a/b;
	}else{
		ret = 0;
	}
	return ret;
}

指针最常见的错误

定义了指针变量,但是没有指向任何变量,就开始使用。

int main(){
	int *p;
	*p = 12;
	printf("%d",*p);
	return 0;
}

指针与数组

数组变量是特殊的指针

  • 数组变量本身表达地址。所以
    • int a[10]; int* p = a; //无需用&取地址
    • 但是数组的单元表达的是变量,需要用&取地址
    • a == &a[0]
  • []运算符可以对数组做,也可以对指针做
    • p[0] <==> a[0]
  • 运算符可以对指针做,也可以对数组做
    • *a 操作的是数组的第一个单元
  • 数组变量是const的指针,不能被赋值
    • int b[] --> int * const b;

指针与const

如果指针被const修饰

  • 表示一旦得到了某个变量的地址就不能再指向其他变量
    • int * const q = &i; // 指针 q 是 const
    • *q = 26; // OK
    • q++; // Error

如果所指是const

  • 表示不能通过指针修改那个变量(并不能使那个变量成为const)
    • const int *p = &i;
    • *p = 26; // Error
    • i = 26; // OK
    • p = &j; //OK

这些都是啥意思?

int i;
const int* p1 = &i;
int const* p2 = &i;
int *const p3 = &i;
  • 判断哪个被const了标志是const在*的前面还是后面
    • const * :所指被const了
    • * const :指针被const了

转换

  • 总是可以把一个非const的值转换成const的
void f(const int * x);
// const int * x; 所指被const了,表示传入的变量在这个函数中绝对不会被修改
int a = 15;
f(&a); // ok,传入一个非const值,转换为const,不能修改
const int b = a;
f(&b); // ok,传入const值,不能修改

f(const int * x){
	x++; // error,const不能修改
}
  • 当要传递的参数的类型比地址大的时候,这是常用的手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量修改

const数组

  • const int a[] =
  • 数组变量本身就是const指针,这里的const表明数组的每个单元都是const int
  • 所以必须通过初始化进行赋值

保护数组值

  • 因为把数组传入函数时传入的是地址,所以函数可以修改数组值
  • 传入时用const修饰,可以保护数组值不被函数修改
  • int f(const int arr[]);

指针运算

指针+1的含义

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

指针可用的运算符

  • 给指针加减一个整数 (+ , += , - , -=)
  • 递增递减 (++ , --)
  • 两个指针相减(返回两个指针之间的距离)
int a[] = {5,4,3,2,1};
int *p1 = &a[1];
int *p2 = &a[4];
printf("%d\n",p2-p1); // 输出结果:3

指针的大小比较

  • 两个指针可以比较大小
  • 只有在两个指针指向同一数组时比较才有意义
  • 比较结果依赖于指针在数组中的相对位置
int a[] = {1,0};
int *p = &a[0];
int *q = &a[1];
printf("%d",p>q); //输出0
printf("%d",*p>*q); //输出1

指向符合字面量的指针

int *p = (int []){0,1,2,3,5};

C99特性,可以减少一些麻烦。不用声明一个数组,再用指针指向这个数组的第一位。

指针用于数据处理

*和++运算符的组合

++ 优先级大于 *

表达式 含义
*p++ 或 *(p++) 自增前表达式的值是*p,然后自增p
(*p)** 自增前表达式的值时*p,然后自增*p
*++p或者*(++p) 先自增p,自增后表达式的值仍是*p
++*p或者++(*p) 先自增*p,自增后表达式的值仍是*p

动态内存分配

在C99中,可以用变量做数组定义的大小,C99之前呢?

C99之前可以给指针分配一定大小的内存,来存放数组,这样就能实现定义数组的大小。

int *arr = (int*)malloc(n*sizeof(int));

malloc分配的是void*,需要强制转换成int*,然后用数组大小n乘以单个int占用的字节就可以把能存放n个int型的空间分配给了*arr。

malloc

  • 使用malloc函数需要引入头文件stdlib.h

  • malloc返回一个指针,类型为void*

  • 如果malloc请求失败,返回NULL

  • malloc的参数为size,以字节为单位

  • 使用free()释放内存

小记

对于以下代码段,正确的说法是:

char *p; 
while(1){
	p = malloc(1);
	*p = 0; 
}
  • A. 最终程序会因为没有没有空间了而退出
  • B. 最终程序会因为向0地址写入而退出
  • C. 程序会一直运行下去
  • D. 程序不能被编译

正确为B选项。一直给p分配内存,但不释放,最终malloc会因内存不足而请求失败,返回NULL。此时指针p的地址指向了NULL(0地址),此时对0地址写入,系统崩溃退出。

单字符的输入输出

putchar

  • int putchar(int c);
  • 向标准输出写一个字符
  • 返回写了几个字符,EOF(-1)表示写失败

getchar

  • int getchar(void);
  • 从标准输入读取一个字符
  • 返回类型为int,是为了返回EOF(-1)
    • Windows -> Ctrl+Z
    • Unix -> Ctrl+D

字符串数组

  • char **a
    • a是一个指针,指向另一个指针,那个指针指向一个字符(串)
  • char a[][n]
    • 一个数组a,每个单元都能放下长度为10的字符(串)
    • 缺点,字符串长度固定,不能超出
  • char *a[]
    • 一个数组a,每个单元都是一个char*,可以存放字符串
    • 相比a[][],字符串的长度不受限制

程序参数

  • int main(int argc,char const *argv[])
  • argc是argv字符串数组的数量
  • argv[0] 是启动程序的命令本身
    • 当使用符号链接时,反应符号链接的名字
  • 当然可以不叫argc和argv,名字可以自己取
#include<stdio.h>

int main(int n,char const *a[]){
    for(int i = 0; i < n; i++){
        printf("%d => %s\n",i,a[i]);
    }
    return 0;
}

将该文件编译为叫做test的程序,使用 ./test 运行

0 => ./test

使用 ./test abc def 114 514 运行

0 => ./test
1 => abc
2 => def
3 => 114
4 => 514

使用 ln -s test test2 创建符号链接

使用 ./test2 abc def 114 514 运行

0 => ./test2
1 => abc
2 => def
3 => 114
4 => 514