# 指针与数组
##1.数组指针的概念及定义
- 数组元素指针
+一个变量有地址,一个数组包含若干元素,每个数组元素都有相应的地址指针变量可以指向数组元素(把某一元素的地址放到一个指针变量中) 所谓数组元素的指针就是数组元素的地址
printf(“%p %p”, &(a[0]), a);
输出结果:0x1100, 0x1100
```
>+ 注意: 数组名a不代表整个数组,只代表数组首元素的地址。
>+ “p=a;”的作用是“把a数组的首元素的地址赋给指针变量p”,而不是“把数组a各元素的值赋给 p”
---
##2.使用指针引用数组元素
- 可以用指针去访问数组
```
int main (void)
{
int a[5] = {2, 4, 6, 8, 22};
int *p;
// p = &(a[0]); /* p has address of a[1]*/
p = a;
printf(“%d %d\n”,a[0],*p);
}
输出结果: 2, 2
- 在指针指向数组元素时,允许以下运算:
+加一个整数(用+或+=),如p+1
+减一个整数(用-或-=),如p-1
+自加运算,如p++,++p
+自减运算,如p--,--p
>+ 如果指针变量p已指向数组中的一个元素,则p+1```指向```同一数组中的下一个元素,p-1```指向```同一数组中的上一个元素。
- 结论:
+引用一个数组元素,可用下面两种方法:
* 下标法,如a[i]形式
* 指针法, *(p+i)形式
- 注意:
+数组名虽然是数组的首地址,但是,数组名所所保存的数组的首地址是不可以更改的
```
int x[10];
x++; //illegal
int*p = x;
p++;//legal
```
---
##3.指针变量之间运算
- 两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数。
+实际上是两个指针值(地址)相减之差再除以该数组元素的长度(字节数)。
+(pointer2地址值 -pointer地址值) / sizeof(所指向数据类型)
- 注意:
+指针之间可以相减,但不可以相加(相加无意义)
# 内存管理的概念和内存分区
##1.内存管理的基本概念
- 内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何
高效,快速的分配,并且在适当的时候释放和回收内存资源。
- 1、内存分配方式 内存分配方式有三种:
+(1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
+(2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
+(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多
##2.内存分区
![](http://7xj0kx.com1.z0.glb.clouddn.com/Snip20150519_3.png)
- 栈 (stack):
+栈又称堆栈, ```是用户存放程序临时创建的局部变量```,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外, 在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
- 堆(heap):
+堆是用于存放进程运行中被```动态分配的内存段```,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张); 当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)
- BSS段:
+BSS段(bss segment)通常是指用来存放程序中```未初始化的全局变量和静态变量``` (这里注意一个问题:一般的书上都会说全局变量和静态变量是会自动初始化的,那么哪来的未初始化的变量呢?变量的初始化可以分为显示初始化和隐式初始化,全局变量和静态变量如果程序员自己不初始化的话的确也会被初始化,那就是不管什么类型都初始化为0,这种没有显示初始化的就是我们这里所说的未初始化。既然都是0那么就没必要把每个0都存储起来,从而节省磁盘空间, 这是BSS的主要作用)的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于 静态内存分配。 BSS节不包含任何数据,只是简单的维护开始和结束的地址,即总大小,以便内存区能在运行时分配并被有效地清零。BSS节在应用程序的二进制映象文件中并不存在,即不占用磁盘空间 而只在运行的时候占用内存空间 ,所以如果全局变量和静态变量未初始化那么其可执行文件要小很多。
- 数据段:
+数据段(data segment)通常是指用来存放程序中```已经初始化的全局变量和静态变量```的一块内存区域。数据段属于静态内存分配,可以分为只读数据段和读写数据段。```字符串常量等``` ,但一般都是放在只读数据段中。
- 代码段:
+ 代码段(codesegment/text segment)通常是指用来存放```程序执行代码的一块内存区域```。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等,但一般都是放在只读数据段中。
# 构造类型及结构体
##1.C语言构造类型有哪些?
- 构造数据类型:构造数据类型是根据已定义的一个或多个数据类型用构造的方法来定义的。也就是说,一个构造类型的值可以分解成若干个“成员”或“元素”。每个“成员”都是一个基本数据类型或又是一个构造类型。
- 在C语言中,构造类型有以下几种:
+数组类型
+结构体类型
+共用体(联合)类型
##2.什么是结构体?
- 在实际问题中,一组数据往往具有不同的数据类型。例如,在学生登记表中,姓名应为字符型;学号可为整型或字符型;年龄应为整型;性别应为字符型;成绩可为整型或实型。显然不能用一个数组来存放这一组数据。因为数组中各元素的类型和长度都必须一致,以便于编译系统处理。为了解决这个问题,C语言中给出了另一种构造数据类型——“结构(structure)”或叫“结构体”。 它相当于其它高级语言中的记录。
- “结构”是一种构造类型,它是由若干“成员”组成的。每一个成员可以是一个基本数据类型或
- 结构体是一种“构造”而成的数据类型,那么在说明和使用之前必须先定 义它,也就是构造它。如同在说明和调用函数之前要先定义函数一样。
##3.为什么要有结构类型?
- 结构体可以把功能相同的数据组织起来(封装数据),存在一起,用的时候方便
- 在调用函数时,若 传递参数较多,传一个结构体相对而言简单一些。
- iOS开发中经常需要使用结构体
# 定义结构体
##1.定义结构体的方法
- 定义一个结构的一般形式为:
```
struct 结构体名{
类型名1 成员名1;
类型名2 成员名2;
……
类型名n 成员名n;
};
- 示例
```
struct Student {
char *name; // 姓名
int age; // 年龄
float height; // 身高
};
# 结构体变量的定义
- 格式:
+struct 结构体名结构体变量名;
##1.先定义结构体类型,再定义变量
- 结构体变量名为stu
struct Student {
char *name;
int age;
};
struct Student stu;
##2.定义结构体类型的同时定义变量
- 结构体变量名为stu
struct Student {
char *name;
int age;
} stu;
- 这种形式的说明的一般形式为:
struct 结构名{
成员表列
}变量名表列;
##3.匿名结构体定义结构体变量
- 结构体变量名为stu
struct {
char *name;
int age;
} stu;
- 这种形式的说明的一般形式为:
struct{
成员表列
}变量名表列;
- 第三种方法与第二种方法的区别在于第三种方法中省去了结构名,而直接给出结构变量,这种结构体最大的问题是,不能再次定义新的结构体变量了。
##4.如何访问结构体变量的成员?
- 一般对结构体变量的操作是以成员为单位进行的,引用的一般形式为:结构体变量名.成员名
struct Student {
char *name;
int age;
};
struct Student stu;
// 访问stu的age成员
stu.age = 27;
printf("age = %d", stu.age);
# 结构体变量的初始化
##1.先定义结构体变量,然后再初始化
- 将各成员的初值,按顺序地放在一对大括号{}中,并用逗号分隔,一一对应赋值。
struct Student {
char *name;
int age;
};
struct Student stu = {“NJ", 27};
##2.定义的同时初始化
struct Student {
char *name;
int age;
}stu = {“NJ", 27};
- 注意: 只能在定义变量的同时进行初始化赋值,初始化赋值和变量的定义不能分开,下面的做法是错误的:
struct Student {
char *name;
int age;
};
struct Student stu;
stu = {“NJ", 27}; // 错误
- 可以使用另外一已经存在的结构体初始化新的结构体
```
struct Student stu2 = stu;
- 可以通过强转的方式进行整体赋值
```
stu2 = (structStudent){2020,"cyx"};
# 结构体类型的作用域
##1.作用域概述
- 结构类型定义在函数内部的作用域与局部变量的作用域是相同的
- 函数外部定义的结构体类型类似全局变量
- 全局作用域:从定义的那一行开始直到本文件结束为止
##2.作用域分类
- 结构体根据作用于可以分为全局结构体、局部结构体
//定义一个全局结构体,作用域到文件末尾
struct Person{
int age;
char *name;
};
void test() {
//使用全局的结构体定义结构体变量p
struct Person p = {10,"sb"};
printf("%d,%s\n",p.age,p.name);
//定义局部结构体名为Person,会屏蔽全局结构体
//局部结构体作用域,从定义开始到“}”块结束
struct Person{
int age;
};
struct Person pp;
pp.age = 50;
// pp.name = "zbz";
}
int main(int argc, const char * argv[])
{
test();
return 0;
}
# 结构体数组
##1.结构体数组的概念
- 数组的元素也可以是结构类型的。因此可以构成结构型数组。```结构数组的每一个元素都是具有相同结构类型的下标结构变量。```在实际应用中,经常用结构数组来表示具有相同数据结构的一个群体。如一个班的学生档案,一个车间职工的工资表等。
---
##2.结构体数组定义
- 跟结构体变量一样,结构体数组也有3种定义方式
+先定义结构体类型,再定义结构体数组
```
struct Student {
char *name;
int age;
};
struct Student stu[5]; //定义1
```
+定义结构体类型的同时定义结构体数组
```
struct Student {
char *name;
int age;
} stu[5]; //定义2
```
+匿名结构体定义结构体结构体数组
```
struct {
char *name;
int age;
} stu[5]; //定义3
```
---
##3.结构体数组初始化
- 定义的同时进行初始化
struct {
char *name;
int age;
} stu[2] = { {”NJ", 27},{"JJ", 30} };
```
- 先定义,后初始化,整体赋值
```
// 先定义,后初始化,整体赋值
s[1]=(struct stu){23,"xiaoluo"};
// 先定义,后初始化 分开赋值
s[1].age=12;
strcpy(stu[1].name, "xxoo");
# 结构体指针
##1.指向结构体变量的指针
- 一个指针变量当用来指向一个结构体变量时,称之为结构体指针变量
- 结构指针变量中的值是所指向 的结构变量的首地址
- 通过结构指针即可访问该结构变量,这与数组指针和函数指针的情况是相 同的。
---
##2.结构体指针的定义与初始化
- 结构指针变量说明的一般形式为:
```
struct 结构名 *结构指针变量名
```
- 示例
```
// 定义一个结构体类型
struct Student {
char *name;
int age;
};
// 定义一个结构体变量
struct Student stu = {“NJ", 27};
// 定义一个指向结构体的指针变量
struct Student *p;
// 指向结构体变量stu
p= &stu;
/*
这时候可以用3种方式访问结构体的成员
*/
// 方式1:结构体变量名.成员名
printf("name=%s, age = %d \n", stu.name, stu.age);
// 方式2:(*指针变量名).成员名
printf("name=%s, age = %d \n", (*p).name, (*p).age);
// 方式3:指针变量名->成员名
printf("name=%s, age = %d \n", p->name, p->age);
return 0; }
```
##3.通过结构体指针访问结构体成员
- 结构指针变量,就是用来存储结构体变量地址的,可以通过它访问结构变量的各个成员。
- 可以通过以下两种方式
+(*结构指针变量).成员名
+结构指针变量->成员名(用熟)
- 注意:
+ (*pstu)两侧的括号不可少,因为成员符“.”的优先级高于“*”。 如去掉括号写作*pstu.num则等效于*(pstu.num),这样,意义就完全不对了。
# 结构体和函数
##1.成员值作为函数的参数
- 结构体成员属性作为函数的参数就是值传递(成员变量是数组除外)。
##2.结构体变量名作为函数的参数
- 将结构体变量作为函数参数进行传递时,其实传递的是全部成员的值,也就是将实参中成员的值一一赋值给对应的形参成员。因此,形参的改变```不会```影响到实参。
##3.结构指针作为函数的参数
- 结构体指针作为函数的参数是地址传递时,形参的改变```会```影响到实参
# 结构体嵌套定义
##1.结构体的嵌套方法
- 员也可以又是一个结构,即构成了嵌套的结构
```
Struct Date{
int month;
int day;
int year;
}
struct stu{
int num;
char *name;
char sex;
struct Date birthday;
Float score;
}
```
- 在stu中嵌套存储Date结构体内容
- 注意:
+结构体不可以嵌套自己变量,可以嵌套指向自己这种类型的指针
```
struct Student {
int age;
struct Student stu;
};
```
##2.对嵌套结构体成员的访问
- 如果某个成员也是结构体变量,可以连续使用成员运算符"."访问最低一级成员
```
struct Date {
int year;
int month;
int day;
};
struct Student {
char *name;
struct Date birthday;
};
struct Student stu;
stu.birthday.year = 1986;
stu.birthday.month = 9;
stu.birthday.day = 10;
# 指针与字符串
##1.字符串指针
- 定义字符串的两种方式
+字符数组
```
char string[]=”I love China!”;
printf("%s\n",string);
```
+字符串指针指向字符串
* 格式: ```char *变量名="字符串内容";```
```
char *str="abc"
```
- 字符串指针变量的定义说明与指向字符变量的指针变量说明是相同的。只能按对指针变量的赋值不同来区别
```
char c = 'b';
char *p=&c; // 表示p是一个指向字符变量c的指针变量。
char *s="C Language"; // 则表示s是一个指向字符串的指针变量。把字符串的首地址赋予s。
```
- 注意:
+1、使用字符数组来保存的字符串是保存栈里的,保存栈里面东西是可读可写,所有我们可以改变里面的字符当把一个字符串常量赋值一个字符数组的时候,那么它会把字符串常量中的没有字符都放到字符数组里面
+2、使用字符指针来保存字符串,它保存的是字符串常量地址,常量区是只读的,所以我们不可以修改字符串中的字符
---
##2.字符串指针使用注意事项
- 可以查看字符串的每一个字符
```
char *str = "lnj";
for(int i = 0; i < strlen(str);i++)
{
printf("%c-", *(str+i));
}
输出结果:l-n-j
```
- 不可以修改字符串内容
```
char *str = "lnj";
*(str+2) = 'y'; // 错误
```
- 不能够直接接收键盘输入
```
char *str;
scanf("%s", str);
```
>+ 错误的原因是:str是一个野指针,他并没有指向某一块内 存空间,所以不允许这样写如果给str分配内存空间是可以这样用 的
# 指向函数指针
##1.为什么指针可以指向一个函数?
- 函数作为一段程序,在内存中也要占据部分存储空间,它也有一个起始地址,即函数的入口地址。函数有自己的地址,那就好办了,我们的指针变量就是用来存储地址的。因此,可以利用一个指针指向一个函数。其中,函数名就代表着函数的地址。
---
##2.针函数的定义和使用
- 格式:
```
函数的返回值类型 (*指针变量名)(形参1, 形参2, ...);
```
+其中“类型说明符”表示被指函数的返回值的类型。
+“(* 指针变量名)”表示“*”后面的变量是定义的指针变量。
+最后的空括号表示指针变量所指的是一个函数。
```
int sum(int a,int b)
{
return a + b;
}
int (*p)(int,int);
p= sum;
```
- 技巧
+1、把要指向函数头拷贝过来
+2、把函数名称使用小括号括起来
+3、在函数名称前面加上一个*
+4、修改函数名称
---
- 应用场景
+调用函数
+将函数作为参数在函数间传递
##3.使用注意
- 由于这类指针变量存储的是一个函数的入口地址,所以对它们作加减运算(比如p++)是无意义的
- 函数调用中"(*指针变量名)"的两边的括号不可少,其中的*不应该理解为求值运算,在此处它 只是一种表示符号
# 枚举类型基本概念
##1.什么是枚举类型?
- 在实际问题中,有些变量的取值被限定在一个有限的范围内。例如,一个星期内只有七天,一年只有十二个月,一个班每周有六门课程等等。如果把这些量说明为整型,字符型或其它类型显然是不妥当的。
- C语言提供了一种称为“枚举”的类型。在“枚举”类型的定义中列举出所有可能的取值, 被说明为该“枚举”类型的变量取值不能超过定义的范围。
- 应该说明的是,枚举类型是一种基本数据类型,而不是一种构造类型,因为它不能再分解为任何
基本类型。
---
##2.枚举类型的定义
- 格式
```
enum 枚举名 {
枚举元素1,
枚举元素2,
……
};
```
```
// 表示一年四季
enum Season {
spring,
summer,
autumn,
Winter
};
```
---
##1.枚举变量
- 先定义枚举类型,再定义枚举变量
```
enum Season {
spring,
summer,
autumn,
Winter
};
enum Season s;
```
- 定义枚举类型的同时定义枚举变量
```
enum Season {
spring,
summer,
autumn,
winter
} s;
```
- 省略枚举名称,直接定义枚举变量
```
enum{
spring,
summer,
autumn,
winter
} s;
```
##4.枚举类型变量的赋值和使用
- 可以给枚举变量赋枚举常量或者整型值
```
enum{
spring,
summer,
autumn,
winter
} s;
s = spring; // 等价于 s = 0;
s = 3; // 等价于 s = winter;
printf("%d", s);
```
##5.枚举使用的注意
- C语言编译器会将枚举元素(spring、summer等)作为整型常量处理,称为枚举常量。
- 枚举元素的值取决于定义时各枚举元素排列的先后顺序。默认情况下,第一个枚举元素的值为0,第二个为1,依次顺序加1。
```
enum Season {
spring,
summer,
autumn,
winter
};
// 也就是说spring的值为0,summer的值为1,autumn的值为2,winter的值为3
```
- 没有指定值的枚举元素,其值为前一元素加1。也就说spring的值为0,summer的值为3,autumn的值为4,winter的值为5
+也可以在定义枚举类型时改变枚举元素的值
enum season {spring, summer=3, autumn,winter};
# 返回指针的函数
---
##1.指针函数概念
- 前面我们介绍过,所谓函数类型是指函数返回值的类型。在C语言中允许一个函数的返回值是一
个指针(即地址),这种返回指针值的函数称为指针型函数。
---
##2.指针函数的定义和使用
- 格式:
```
类型说明符 *函数名(形参表) {
/*函数体*/
}
```
>+ 其中函数名之前加了“*”号表明这是一个指针型函数,即返回值是一个指针。类型说明符表示了 返回的指针值所指向的数据类型。