C语言基础 - AruiLR/MyNote GitHub Wiki

指针

1.理解复杂类型的原则:从变量名起,根据运算符优先级结合,一步一步分析

  • 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 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数.

2.指针的类型

从语法角度,只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针本身所具有的类型

  • int *ptr:指针的类型是int*
  • int **ptr:指针的类型是int**
  • int (*ptr)[3]:指针的类型是int (*)[3]

3.指针所指向类型

从语法角度,只要把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型

  • int *ptr:指针所指向的类型是int
  • int **ptr:指针所指向的类型是int *

4.指针的值——指针所指向的内存区域或地址

  • 32位程序里,所有类型的指针的值都是一个32位的数
  • 64位程序里,所有类型的指针的值都是一个64位的数

5.指针的算术运算

  • 例一
char a[20];
int *ptr=(int *)a; //**强制类型转换不会改变a的类型**
ptr++; //编译器这样处理:把指针ptr的值加上sizeof(int)

ptr加1后指向a[4]

  • 例二
char array[20]={0};
int *ptr=array;
for(i=0;i<20;i++)
{
    (*ptr)++;
    ptr++;
}
//输出:10001000100010001000
  • 例三
char a[20]="You_are_a_girl";
int *ptr=(int *)a;
ptr+=5;
//指针ptr的值加上了5*sizeof(int)=20 字节

6.运算符&和*

  • &a的运算结果是一个指针,指针的类型是a的类型加个*,指针指向的类型是a的类型
  • *p的运算结果是p所指向的东西,它的类型是p指向的类型,它所占用的地址是p所指向的地址

7.数组和指针的关系

  • 例一
char *str[3]={
    "Hello, thisisasample",
    "Hi,good morning",
    "Helloworld"
};
char s[80];

str是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串,把指针数组名str当作一个指针的话,它指向数组的第0号单元,类型是char **,它指向的类型是char*。

  • 例二
int (*ptr)[10]

上面ptr是一个数组指针,sizeof(ptr) == 4, sizeof(*ptr) == 40

8.指针和结构类型的关系

  • 例一
struct MyStruct  
    {  
        int a;  
        int b;  
        int c;  
    };  
    struct MyStruct ss={20,30,40}; //声明了结构对象ss,并把ss的成员初始化为20,30 和40。  
    struct MyStruct *ptr=&ss;//声明了一个指向结构对象ss 的指针。它的类型是MyStruct *,它指向的类型是MyStruct。  
    int *pstr=(int*)&ss;声明了一个指向结构对象ss 的指针。但是pstr和它被指向的类型ptr 是不同的。

//可通过ptr按如下方式访问ss的三个成员变量
ptr -> a; //指向运算符,或者可以这样(*ptr).a
ptr -> b;
ptr -> c;

//不可以这样访问,结构体成员之间可能会有填充字节
*pstr; //访问ss的成员a
*(pstr+1); //访问ss的成员b
*(pstr+2); //访问ss的成员c

8.指针和函数的关系

可以把一个指针声明成为一个指向函数的指针

9.指针类型转换

当我们初始化一个指针或给一个指针赋值时,赋值号的左边是一个指针,赋值号的右边是一个指针表达式,指针所指向的类型和指针表达式所指向的类型是一样的。

  • 例一
float f=12.3;
float *fptr=&f;
int *p;

//如何让指针p指向实数f
p=&f; //错误,因为指针p的类型是int *,指向的类型是int,表达式&f的结果是一个指针,它的类型是float *,指向的类型是float,赋值号两边类型不一样。
//强制类型转换
p=(int*)&f; //如果有一个指针p,我们需要把它的类型和所指向的类型改为TYPE *TYPE,那么语法格式是:(TYPE *)p,这样强制类型转换的结果是一个新的指针,它指向的地址就是原指针指向的地址,而原来指针p的一切属性都没有被修改

2.指针变量赋值

  • 初始化赋值:int *p = &var
  • 赋值语句赋值:int var = 20; int *p; p = &var;
int a=12; int b; int *p; int **ptr;  
p=&a; //&a的结果是一个指针,类型是int*,指向的类型是int,指向的地址是a的地址。  
*p=24; //*p的结果,在这里它的类型是int,它所占用的地址是p所指向的地址,显然,*p就是变量a。  
ptr=&p; //&p的结果是个指针,该指针的类型是p的类型加个*,在这里是int **。该指针所指向的类型是p的类型,这里是int*。该指针所指向的地址就是指针p自己的地址。  
*ptr=&b; //*ptr是个指针,&b的结果也是个指针,且这两个指针的类型和所指向的类型是一样的,所以用&b来给*ptr赋值就是毫无问题的了。  
**ptr=34; //*ptr的结果是ptr 所指向的东西,在这里是一个指针,对这个指针再做一次*运算,结果是一个int 类型的变量。

数组、字符串

。。。。

大小端字节序

。。。。

变量类型

声明一个变量不会分配内存,而定义一个变量会分配内存;一般会将声明放在头文件,而定义放在源文件。

1.auto 自动变量/局部变量

  • 函数内部定义使用的变量,只允许本函数使用
  • 变量没有链接性,不允许其它文件访问,因此可以在这个函数外的其它地方或其它函数内部定义同名的变量,不会发生冲突
  • 保存在堆栈,生存周期从函数执行到执行完毕,系统自动回收

2.static静态变量

  • 分配固定内存,生存周期为程序运行的整个周期
  • 函数内部静态变量的作用域为本函数

3.extern 外部变量

  • 定义在程序外部,所有函数或程序段都可以使用
  • int data -> 声明+定义;extern int data -> 只声明,未定义。
  • 外部变量可能会在某一程序段被重新定义,以段内变量为参考值。

4.static extern变量

静态外部变量和外部变量差别在于,外部变量可以同时给多个文件使用,而静态外部变量则只能给声明此变量的文件使用。

5.全局变量

全局变量不能定义在被多个.c文件引用的.h文件中。编译器发现不了错误,但是链接器会在解析交叉引用时报告“符号被多重定义”错误。

6.结构体(struct)

  • 占据空间大小计算
  1. 原则一:结构体中元素是按照定义顺序一个一个放到内存中去的,元素放置的位置一定会在自己宽度的整数倍上开始
  2. 原则二:补齐为多有元素中最宽元素的长度的整数倍
  3. 包含指针类型:只记住指针本身占4个字节的存储空间,不必看它是指向什么类型的指针

7.共用体、联合体(union)

结构体和共用体的区别:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员

其它

1.新增函数注意事项

  • 避免函数过长,新增函数不超过50行(非空非注释行)
  • 避免函数的代码块嵌套过深,新增函数的代码块嵌套不超过4层