C学习笔记

--------------------------------------------------------------------------------
201501023
--------------------------------------------------------------------------------
学习目标
1.【掌握】指针与指针之间的运算
2.【掌握】指针与字符串
3.【掌握】指针与中括号
4.【掌握】字符串数组
5.【掌握】字符串函数补充
6.【掌握】const关键字
7.【掌握】内存管理

一、指针与指针之间的运算
减法运算:
如果有两个指针都分别指向同一个数组中的某个元素,我们可以通过指针减法得到这两个元素之间相差多少个元素。注意指针与指针的减法运算,
只有运用在数组中才有意义,并且指针之间只有减法没有加法。
#include <stdio.h>

int main() {
int arr[5] = {10,20,30,40,50};
int *p1 = arr;
int *p2 = &arr[4];
long num = p2 - p1;//返回long类型
// long num1 = p2 + p1; //这样是错误的。。。
printf("num = %ld\n",num);//结果为4,代表这两个指针指向的元素之间相差4个元素
return 0;
}

关系运算:
可以判断两个指针谁在高字节,谁在低字节,或者是否指向同一个变量等等。
#include <stdio.h>

int main() {
int arr[5] = {10,20,30,40,50};
int *p1 = arr;
int *p2 = &arr[4];
int result = p1 > p2;
int result1 = p1 < p2;
int result2 = p1 == p2;
int result3 = p1 != p2;
//.......
//打印结果,1为真,0为假
printf("result = %d\nresult1 = %d\nresult2 = %d\nresult3 = %d\n",result,result1,result2,result3);
return 0;
}

二、指针与字符串
内存分区
为了便于系统管理,内存之中主要分为5个区域,存储在不同区域的数据我们系统的管理方式是不一样的。

1.栈空间:所有的局部变量存储在栈空间之中,分配是从高地址向低地址分配空间。
2.堆空间:可以由程序员手动申请字节空间来使用,分配是从低地址向高地址分配空间。
3.BSS段:存储未被初始化的全局变量、静态变量。全局变量虽然有默认值,但是在初始化之前,这个变量是存储在BSS段的。
4.数据段(常量区):存储已被初始化的全局变量、静态变量、常量数据、字符串常量数据。
5.代码段(代码区):执行一个程序,会将这个程序加载到内存,一个程序中其实就是代码,这个程序的代码存储在代码段区域。

存储方式
char chs[] = "jack";//字符串数据是以字符数组的形式存储在字符数组之中,将字符串的每一个字符存储在字符数组之中,并以'\0'作为结束符。
char *str = "jack";//字符串数据以字符指针的形式存储,直接给一个字符指针初始化一个字符串数据。

当他们都是局部变量的时候
char chs[] = "jack";//会在栈空间之中创建一个长度为5的字符数组,将字符串的每一个数据存储到该元素中,并自动追加'\0'。
char *str = "jack";//str指针是一个局部变量,所以这个指针声明在栈空间中,"jack"这个字符串是以字符数组的形式存储在数据段之中。
str指针存储的是数据段之中字符数组的地址。

当他们都是全局变量的时候
char chs[] = "jack";//这个时候chs字符数组是创建在数据段之中的
char *str = "java";//str指针存储在数据段,"jack"字符串也是存储在数据段

注意:
1.以字符数组存储的字符串,可以通过下标或者指针去修改存储在字符数组之中的每一个元素的值。
2.以字符指针的形式存储的字符串,是存储在数据段的,存储在数据段的数据具有恒定性,一旦创建就无法修改。

#include <stdio.h>

int main(){
char ch[] = "jack";
char *pp = &ch;//声明一个指针指向字符数组
*pp = 'a';//这样是允许的
// ch = "zhou";//这样是不允许的,ch是数组地址,是常量无法修改的

char *p = "jack";
// *p = 'a';//这样是不允许的
printf("p = %p\n",p);//第一个地址、第二个地址相同

p = "jack";//这里他不会重新创建,因为常量区已经有了,直接返回他的地址给p。地址不会改变
printf("p = %p\n",p);//第二个地址、第一个地址相同

p = "zhou";//这里是在数据段重新创建一个新的"zhou"字符串,让指针p指向它。这里只是修改指针指向,不是修改字符串数据
printf("p = %p\n",p);//第三个地址和第一、第二个地址不同

return 0;
}

三、指针与中括号
所有指针名后面都可以写一个中括号,其中写一个整数。
指针名[n] == *(指针名 + n)

#include <stdio.h>
#include <string.h>

int main(){

char *str = "fafafdfyurfadfaafafgdg";
int count = 0;
unsigned long length = strlen(str);

//第一种写法,*(str + i)
for (int i = 0; i <= length; i++) {
if (*(str + i) == 'a') {
count++;
}
}
printf("a一个出现了%d次\n",count);

count = 0;//重置次数
//第二种写法,str[i]
for (int i = 0; i <= length; i++) {
if (str[i] == 'a') {
count++;
}
}
printf("a一个出现了%d次\n",count);

return 0;
}

四、字符串数组
使用二维字符数组来存储多个字符串数据,每个字符串的长度必须限制并且不能超过限制长度,灵活性很差。
#include <stdio.h>

int main(){
char name[3][20] = {"zhou","jian","feng"};//灵活性差
return 0;
}

使用字符指针数组来保存多个字符串数据,每一个字符串数据是以字符数组的形式存储在数据段的,字符指针数组的元素存储的是字符串在数据段的字符数组的地址。
所以不限制字符串数据的长度,灵活性很好。
#include <stdio.h>

int main(){
char *name[] = {"zhou","jian","feng","fjalfjlajflajfladjflafj"};//灵活性好
return 0;
}

案例:对字字符串进行各种排序

#include <stdio.h>
#include <string.h>

int main(){

//随便整一堆字符串
char *names[] = {
"hexiaoping",
"zhoujinafeng",
"jack",
"rose",
"canglaoshi",
"laowang",
"tongzhuo"
};
//计算字符指针数组长度
int length = sizeof(names) / sizeof(char *);

//用冒泡排序来按照字符串长度来排序
for (int i = 0; i < length - 1; i++) {
for (int j = 0; j < length - 1 - i; j++) {
//分别计算字符串的长度
int len = sizeof(names[i]);
unsigned long len1 = strlen(names[j]);
unsigned long len2 = strlen(names[j+1]);
if (len1 > len2) {
char *temp = names[j+1];
names[j+1] = names[j];
names[j] = temp;
}
printf("%d ",len);
}
}

//用选择排序来按照字符顺序来排序
for (int i = 0; i < length - 1; i++) {
for (int j = i+1; j < length; j++) {
//比较字符串的大小
int res = strcmp(names[i], names[j]);
if (res > 0) {
char *temp = names[i];
names[i] = names[j];
names[j] = temp;
}
}
}

//遍历字符串,输出结果
for (int i = 0; i < length; i++) {
printf("%s\n",names[i]);
}

return 0;
}

五、字符串函数补充
fputs()函数
作用:可以将字符串输出到指定的流之中。可以输出到标准输出流(控制台),也可以输出到文件中。
语法:fputs(字符串,输出流);
关于输出流:stdout 代表标准输出流,也就是我们的控制台。
fopen()函数可以返回指定的文件的指针。
"w" write 写入数据到这个文件,如果文件不存在则创建再写入,如果已经存在则替换文件。
"r" read 从这个文件之中读取数据
"a" apped 追加,在文件末尾追加数据

fgets()函数
作用:从指定的流之中读取字符串数据。这个流可以是标准输入流(控制台),也可以是文件流。
语法:fgets(用于存储读取出来的字符串的字符数组名,最多读取多少长度的字符串,指定流);
当指定流是标准输入流stdin的时候,第二个参数要和数组的长度保持一致。假如第二个参数为n,如果用户输入的字符串长度大于等于了n,只会存储n-1个字符。
如果用户输入的字符串长度等于了n-1,刚好完美。如果用户输入的字符串长度小于了n-1,那就会多接收个'\n'。

案例:从控制台输入字符串,并存储到指定文件
#include <stdio.h>
#include <string.h>

int main(){

//打开文件
FILE *fp = fopen("fputs.txt", "w+");

char str[100];

if (fp != NULL) {
printf("请输入字符串存储到文件中\n");

//从控制台读取99个以内的字符数据并存储到字符数组str中
fgets(str, 100, stdin);
unsigned long len = strlen(str);
if (str[len-1] == '\n') {
str[len-1] = '\0';
}

//从字符数组str从输出字符存储到文件中
fputs(str, fp);
}
fclose(fp);//关闭文件

return 0;
}

六、const关键字
在声明变量的同时,用const关键字来修饰这个变量,被const修饰的变量,在某种程度上,这个变量具备不可更改性。像这样的具备不可更改性,叫常量。
作用:让变量具备不可更改性。

//const修饰基本数据类型(int double float char)这些基本数据类型具备不可更改性。
int const num = 10;//让num变量不能被重新赋值。
const int num = 10;//效果同上

//const修饰数组
const int arr[3] = {10,20,30};//arr数组中元素的值不能更改。
int const arr[3] = {10,20,30};//效果同上

//const修饰指针一
int num = 13;
const int *p = &num;//无法通过p指针修改指向num变量的值,但p指针变量的值可以改
int num1 = 10;
p = &num1;//可以
//*p = 10;//不可以

//const修饰指针二
int num = 13;
int const *p = &num;//和一效果相同
int num1 = 10;
p = &num1;//可以
//*p = 10;//不可以

//const修饰指针三
int num = 13;
int * const p = &num;//无法修改p指针变量的值,但可以通过p指针修改指向num变量的值
*p = 100;//可以
//int num1 = 10;
//p = &num1;//不可以

//const修饰指针四
int num = 10;
int const * const p = &num;//都不能改
//*p = 100;//不可以
//int num1 = 10;
//p = &num1;//不可以

常见应用场景
1.如果在程序运行期间,有1个数据是不会发生变化的。但在很多地方都有使用,就可以用const修饰。
2.当指针作为函数参数的时候,可以在指针参数前加const修饰。就说明这个函数不能修改调用者变量的值。

七、内存管理
堆空间的特点:
1.允许程序员在堆空间中申请任意个字节数的空间来使用
2.申请在堆空间之中的字节空间,除非程序员主动释放或者程序结束,否则申请的空间是不会释放的。

申请字节空间的相关函数都在 stdlib.h 系统头文件之中

malloc()函数
使用malloc函数在堆空间申请任意个字节数的空间,是从低地址向高地址分配一块连续的字节空间。返回值是申请的那块连续空间的低地址的第一个字节的地址。
语法上任意指针类型的变量都能接收这个函数的返回值。为了避免浪费内存空间,我们要根据自己的实际情况声明指针接收。malloc申请是有可能失败的,
如果申请失败返回的值是NULL。
#include <stdio.h>
#include <stdlib.h>

int main(){
int *num = malloc(4);
if(num != NULL){
*num = 200;//先判断是否申请成功,然后再操作
}

free(num);//当空间不在使用的时候,要手动释放空间
return 0;
}

calloc()函数
在堆中申请指定字节的空间来使用,其用法和malloc使用一样。唯一不同的是,alloc申请的空间有可能有垃圾值,calloc申请空间时并清空申请到的空间,
我们一般也是用calloc来申请空间。
语法:
calloc(块数,每块的字节空间);
#include <stdio.h>
#include <stdlib.h>

int main(){

int *num = calloc(2, sizeof(int));
//先判断是否申请成功,然后再操作
if(num != NULL){
*num = 200;
*(num + 1) = 300;
}

free(num);//当空间不在使用的时候,要手动释放空间
return 0;
}

realloc()函数
realloc是对已经申请的字节空间增大容量,如果后面空闲空间足够,则直接扩容。如果不够,则找一块新的连续空间,并把原来空间的值赋值过来,
原来的空间将自动释放,并返回新的空间的低字节地址。
#include <stdio.h>
#include <stdlib.h>

int main(){

int *num = calloc(2, sizeof(int));
//先判断是否申请成功,然后再操作
if(num != NULL){
num[0] = 200;
num[1] = 300;
}
int *num1 = realloc(num, 1600);//将num扩容至1600字节,并返回第一个字节的地址
printf("num = %p num1 = %p\n",num,num1);//两个地址不同,则表示已经换到新空间

free(num1);//当空间不在使用的时候,要手动释放空间
return 0;
}

free()函数
释放不再使用的手动申请的堆空间,所谓释放是这块空间可以分配给其他人,但里面的值并没有清空。
语法:
free(需要释放空间的指针名);
--------------------------------------------------------------------------------
20150710
--------------------------------------------------------------------------------
学习目录
1.【掌握】返回指针的函数
2.【掌握】指向函数的指针
3.【掌握】结构体的声明
4.【掌握】结构体与数组
5.【掌握】结构体与指针
6.【掌握】结构体的嵌套
7.【掌握】结构体与函数

一、返回指针的函数
指针作为函数的返回值
指针是可以作为函数的返回值的,不能返回局部变量的指针,因为当函数执行结束后指针变量就释放了。如果我们真的需要返回一个指针变量,那就要保证我们的函数执行完毕之后,
指针指向的变量仍然存储在内存之中。那我们可以将变量创建在堆空间中,使用malloc或者calloc申请空间。或者直接声明为全局变量或者用static修饰的局部变量。

如果函数需要返回一个字符串,我们可以返回(char *)类型字符串,不要使用字符数组。字符串是存储在数据段的,直到程序执行结束才会释放。
#include <stdio.h>
#include <stdlib.h>

char *getWeekDay(int day){
return "星期N";
}
int main(){
char *weekDay = getWeekDay(2);
return 0;
}

二、指向函数的指针
程序在运行的时候,函数是存储在内存(代码段)之中的,存储函数的空间也是有地址的,所以我们可以用指针指向函数。
语法:返回值类型(*指针名)([参数列表]);
注意:
1.函数的地址就是函数名
2.指向函数的指针,本质上还是一个变量,那么我们就可以声明、赋值给另外一个同类型的指针变量使用。

无参无返回值
#include <stdio.h>

void testFunction(){
printf("这是一个无参无返回值的函数\n");
}
int main(int argc, const char * argv[]) {

//指针指向函数
void (*pFunction)() = testFunction;//存储没有返回值没有参数的地址,函数名就是函数的地址

//指针调用函数
// (*pFunction)();
pFunction();//简写

return 0;
}

有参有返回值
#include <stdio.h>

int getSum(int num1,int num2){
return num1 + num2;
}
int main(int argc, const char * argv[]) {

//指针指向函数,参数列表可以省略
int (*pFunction)() = getSum;

//指针调用函数
int sum = pFunction(10,12);

printf("sum = %d\n",sum);
return 0;
}

应用场景:多种方式对字符串数组进行排序
#include <stdio.h>
#include <string.h>

/**
* 对字符串进行各种排序
*
* @param countries 需要被排序的字符串数组
* @param length 字符串数组的长度
* @param pCompare 指向控制排序方式的函数指针
*/
void sort(char *countries[],int length,int (*pCompare)()){
for (int i = 0; i < length; i++) {
for (int j = 0; j < length - 1 -i; j++) {
//调用比较字符串的函数进行比较
int res = pCompare(countries[j],countries[j+1]);
if (res > 0) {
//交换位置
char *temp = countries[j];
countries[j] = countries[j+1];
countries[j+1] = temp;
}
}
}
}

/**
* 比较字符串大小
*
* @param str1 第一个字符串
* @param str2 第二个字符串
*
* @return 第一个大字符串大返回正数,第二个字符串大返回负数,相同返回0
*/
int compare(char *str1,char *str2){
return (int)strcmp(str1, str2);
}

/**
* 比较字符串的长度
*
* @param str1 第一个字符串
* @param str2 第二个字符串
*
* @return 第一个字符串大返回1,第二个字符串大返回-1,相同返回0
*/
int compare1(char *str1,char *str2){
unsigned long len1= strlen(str1);
unsigned long len2 = strlen(str2);
if (len1 > len2) {
return 1;
}else if(len1 < len2){
return -1;
}
return 0;
}

int main(int argc, const char * argv[]) {
char *countries[] = {
"fldjfljfaf",
"fdahfiyyyyfh",
"aadjflui",
"jabfaf"
};

//计算字符串数组长度
int length = sizeof(countries)/sizeof(char *);

//传入字符串数组,数组长度,指向函数的指针。按字符串大小排序
sort(countries, length, compare);

//传入字符串数组,数组长度,指向函数的指针。按字符串长度排序
sort(countries, length, compare1);

//遍历打印字符串
for (int i = 0; i < length; i++) {
printf("%s\n",countries[i]);
}

return 0;
}

三、结构体的声明

在实际应用中,我们通常需要由不同类型的数据来构成一个整体,比如学生信息这个整体可以由姓名、年龄、身高等数据构成,这些数据都具有不同的数据类型,
姓名可以是字符串类型,年龄可以是整型,身高可以是浮点型。C语言提供了一种构造类型来解决这个问题,可以由程序员自己声明多个数据类型组成一个整体类型,
这个玩意就是结构体。
声明结构体语法:
struct 类型名 {
数据类型1 成员名1;
数据类型2 成员名2;
..........
};
声明结构体变量语法:struct 类型名 变量名;

//声明类型的同时声明变量
struct Student {
char *name;
int age;
float heigth;
}stu,stu1;//可以同时声明多个变量

//匿名结构体。声明类型的同时声明变量,可以省略类型名,但是这种方法不能再次声明新的变量了
struct {
char *name;
int age;
float heigth;
}stu,stu1;//也可以同时声明多个变量

//声明类型,再声明变量
struct Student {
char *name;
int age;
float heigth;
};
struct Student stu1,stu2;//变量名是stu1,stu2.可以同时声明多个变量
struct Student stu2;//变量名是stu3

为结构体变量中的成员赋值。
#include <stdio.h>

int main(int argc, const char * argv[]) {
//声明一个结构体Student1
struct Student1 {
char *name;
int age;
float heigth;
};
struct Student1 stu1;
stu1.name = "zhoujianfeng";//单独为变量的每个成员赋值
stu1.age = 18;
stu1.heigth = 1.9f;

//声明一个结构体Student2
struct Student2 {
char *name;
int age;
float heigth;
};
struct Student2 stu2 = {"zhoujianfeng",18,1.9f};//声明变量的同时初始化
struct Student2 stu3 = {"zhoujianfeng",18};//初始化部分
struct Student2 stu4 = {.age = 18};//指定成员初始化
struct Student2 stu5 = stu2;//将stu2赋值给stu5

return 0;
}

结构体注意:
1.这个新声明的结构体也是一个数据类型,由程序员自己声明的新类型,所以可以声明这个结构体类型的变量。
2.定义在大括号之中的变量,叫新类型的成员。必须要先声明结构体类型,再声明结构体变量(可以在声明结构体类型的同时声明结构体变量)。
3.结构体类型名首字母大写,并且结构体末尾需要加分号。
4.声明一个结构体变量没有初始化,成员的值是垃圾值,如果声明的同时初始化了部分,其他成员初始化为0。
5.相同类型的结构体变量是可以相互赋值的,赋值是将原结构体中的每一个成员的值挨个拷贝给目标结构体变量。
6.结构体变量名,直接代表整个结构体变量。在外面学习到的所有数据类型中,只有数组名、函数名才代表他们的地址,其他都是代表变量本身。

四、结构体与数组

语法:struct 类型名 数组名[长度];
#include <stdio.h>

int main(int argc, const char * argv[]) {
struct Student {
char *name;
int age;
int score;
};

//stuArray数组的每一个元素的类型是一个struct Student结构体类型
struct Student stuArray[] = {
{"小明",19,99},
{"小芳",19,99},
{"小东",18,20}
};

//单独初始化一个元素,需要做类型强转,不然编译器不知道赋值给数组还是结构体
stuArray[0] = (struct Student){"小明",19,100};
stuArray[0].age = 20;

//数组长度 = 数组占的总字节数 / 每个元素占的字节数
int length = sizeof(stuArray)/sizeof(struct Student);

//遍历数组,打印3个学生的信息
for(int i = 0; i < length; i++){
printf("%s %d %d\n",stuArray[i].name,stuArray[i].age,stuArray[i].score);
}

return 0;
}

五、结构体与指针

结构体变量也是一个变量,那么这个结构体变量一定是有内存地址的,所以我们就可以搞个指针指向这个结构体变量。然后我们就可以通过指针间接操作结构体变量。

语法:
struct 类型名 *指针名;

#include <stdio.h>

struct Student {
char *name;
int age;
int score;
};
int main(int argc, const char * argv[]) {

//初始化变量
struct Student stu = {"静静",19,100};

//p指针指向stu变量
struct Student *p = &stu;

//第一种方式
(*p).name = "芳芳";
(*p).age = 18;
(*p).score = 99;

//第二种方式
p->name = "东东";
p->age = 18;
p->score = 99;

printf("%s %d %d\n",p->name,p->age,p->score);
return 0;
}

六、结构体的嵌套

我们在为结构体写成员的时候,发现某个成员也是一个需要多个数据组成一个整体的数据,这个时候我们就可以使用结构体嵌套。在结构体内声明另一个结构体类型的变量作为
成员来使用。
#include <stdio.h>

//日期结构体类型
struct Date{
int year;
int month;
int day;
};

//人结构体类型
struct Person {
char *name;
int age;
struct Date birthday;
};

int main(int argc, const char * argv[]) {
//创建一个人并赋值
struct Person zhouJianFeng = {"周剑峰",23,{2015,9,18}};
// struct Person zhouJianFeng = {"周剑峰",23,2015,9,18};//可以成员的省略大括号

//打印出这个人的信息
printf("姓名:%s\n年龄:%d\n生日:%d年%d月%d日\n",zhouJianFeng.name,zhouJianFeng.age,zhouJianFeng.birthday.year,zhouJianFeng.birthday.month,zhouJianFeng.birthday.day);
return 0;
}

七、结构体与函数

结构体作为函数的参数
#include <stdio.h>

//人结构体类型
struct Person {
char *name;
int age;
};

void test(struct Person *stu){
stu->name = "周剑峰";
stu->age = 18;
}
int main() {

struct Person zhou = {"静静",19};
test(&zhou);//调用函数,传入地址

printf("%s %d\n",zhou.name,zhou.age);
return 0;
}

结构体作为函数的返回值
#include <stdio.h>

//人结构体类型
struct Person {
char *name;
int age;
};

struct Person getStu(){
struct Person stu = {"静静",19};
return stu;//是返回一个变量的值
}
int main() {

struct Person stu = getStu();
printf("%s %d\n",stu.name,stu.age);
return 0;
}
--------------------------------------------------------------------------------
20150711
--------------------------------------------------------------------------------
学习目标
1.【掌握】枚举
2.【掌握】typedef关键字
3.【理解】预处理指令
4.【掌握】#define宏定义
5.【掌握】条件编译
6.【掌握】static与extern关键字

一、枚举

当我们要描述方向、四季、性别、学历、婚配情况等等事物的时候,我们知道这些事物的取值范围是非常有限的。比如,性别取值就男、女,四季取值就春、夏、秋、冬。
类似这样的需求,C语言提供了一种构造类型枚举专门针对此类需求,由程序员自己声明一种新的数据类型,并给这个新的数据类型声明几个固定枚举值。
同时,声明这个新的数据类型的变量时,给变量赋值的取值范围就只能赋值我们类型里声明的某个固定枚举值。

枚举类型的声明
语法:enum 枚举名 {枚举值1,枚举值2,...};
enum Gender {Genderwomen, Genderman};//声明一个枚举类型,他的取值是women和man

枚举变量的声明
语法:enum 枚举名 变量名;
enum Gender {Genderwomen, Genderman};//声明一个枚举类型,他的取值是women和man
enum Gender gender;//声明一个enum Gender类型的变量gender

变量的初始化
语法:enum 枚举名 变量名 = 枚举值;
enum Gender {Genderwomen, Genderman};//声明一个枚举类型,他的取值是women和man
enum Gender gender = Genderwomen;//声明一个enum Gender类型的变量gender并初始化为women

枚举值对应的整型数值
enum Gender {Genderwomen, Genderman};//声明一个枚举类型,他的取值是women和man
enum Gender gender = Genderwomen;//声明一个enum Gender类型的变量gender并初始化为women
printf("gender = %d\n",gender);//打印出gender = 0
gender = Genderman;//重新把枚举变量赋值man
printf("gender = %d\n",gender);//打印出gender = 1
gender = 0;//这样赋值也是可以的,相当于赋值为women,但是不直观不好看.不推荐这种赋值

注意:
1.枚举也是一种数据类型,类型名是(enum 枚举名),必须要加上enum关键字啊。
2.给枚举变量初始化或者赋值的时候,取值只能取枚举类型的枚举值中的某个值,不能随意赋值。
3.每一个枚举值对应都有一个整型数值的,从第一个枚举值开始从0依次递增。
4.声明一个枚举变量的时候,这个变量里面存的实际上是这个枚举值对应的整型,而不是枚举值本身。
5.枚举值命名最好能加上区分这个枚举值属于哪个枚举类型的标示,比如在枚举值前面加上枚举类型名。

实际应用:
#include <stdio.h>

//性别枚举类型
enum Gender {Genderwomen,Genderman};

struct Person {
char *name;//姓名
int age;//年龄
enum Gender gender;//性别
};
int main(int argc, const char * argv[]) {

struct Person laoWang = {"老王",18,Genderman};
printf("name = %s\nage = %d\ngender = %s\n",laoWang.name,laoWang.age,laoWang.gender == 0 ? "男" : "女");
/*打印出
name = 老王
age = 18
gender = 女
*/
return 0;
}

二、typedef关键字

如果你感觉有些数据类型太长,难以记忆难以书写,我们可以使用typedef关键字为各种数据类型定义一个新名字(别名)。
语法:typedef 数据类型 别名;

typedef与普通数据类型

typedef int Integer;
Integer num = 10;//等同于int num = 10;

typedef与指针

typedef char * String;
String str = "字符串";//等同于char *str = "字符串";
printf("str = %s\n", str);//打印 str = 字符串

typedef与结构体

//1.先声明结构体,再给结构体定义一个别名
struct Person {
char *name;
int age;
};
typedef struct Person Person;//给struct Person定义一个别名
Person person = {"老王",18};//等同于 struct Person person = {"老王",18};
printf("姓名:%s\t年龄:%d\n",person.name,person.age);

//2.声明的结构体的同时给结构体定义一个别名
typedef struct Person {
char *name;
int age;
}Person;
Person person = {"老王",18};//等同于 struct Person person = {"老王",18};
printf("姓名:%s\t年龄:%d\n",person.name,person.age);

//3.声明的结构体的同时给结构体定义一个别名,可以省略结构体名
typedef struct {
char *name;
int age;
}Person;
Person person = {"老王",18};//等同于 struct Person person = {"老王",18};
printf("姓名:%s\t年龄:%d\n",person.name,person.age);

typedef与指向结构体的指针

//声明一个结构体并定义别名
typedef struct {
char *name;
int age;
}Person;

typedef Person * PP;//给指向结构体的指针取别名
Person laoWang = {"老王", 18};//声明结构体变量
PP p = &laoWang;// 声明指针变量指向结构体变量

//利用指针变量访问结构体成员
printf("name = %s age = %d\n", p->name, p->age);

typedef与枚举

//1.先声明枚举类型,再给枚举类型定义别名
enum Gender {Genderman,Genderwomen};
typedef enum Gender Gender;//给枚举类型起别名
Gender gender = Genderman;//声明枚举变量

//2.声明枚举类型的同时定义别名
typedef enum Gender {Genderman,Genderwomen}Gender;
Gender gender = Genderman;//使用别名声明枚举变量

//3.声明枚举类型的同时定义别名,枚举名也是可以省略的
typedef enum {Genderman,Genderwomen}Gender;
Gender gender = Genderman;//使用别名声明枚举变量

typedef与指向函数的指针

#include <stdio.h>

// 定义一个sum函数,计算a跟b的和
int sum(int a, int b) {
int c = a + b;
return c;
}
typedef int (*MySum)(int, int);//这里别名是MySum

int main(int argc, const char * argv[]) {

MySum p = sum;//声明一个指向sum函数的指针变量p
int result = p(4,5);// 利用指针变量p调用sum函数

printf("result = %d\n",result);//打印 result = 9
return 0;
}

三、预处理指令

让我们来回顾一下C程序从编写源代码到执行需要的步骤。先编写符合C语言语法规范的源代码文件,然后编译成二进制代码的目标文件,再然后会进行链接,最终生成可执行文件。其实在编译之前,还有一个很重要的步骤,系统会自动执行,那就是执行预处理指令。预处理指令是在编译之前执行的,我们已经学过#include文件包含指令,今天我们再来整几发指令。

四、#define宏定义
在程序编译之前,会把使用宏名的地方替换成宏值的内容。注意这里的替换是纯洁的替换,无论宏值是表达式还是数值,甚至是错误代码,都会原模原样的替换。
语法:#define 宏名 宏值

无参数的宏:使用无参数宏的时候,只是纯粹的文本替换
#include <stdio.h>
#define LOG printf("这里是LOG\n")
#define SUM a+b
#define AREA PI*r*r //宏值可以使用宏名,在使用地方的后面也可以
#define PI 3.14

int main(int argc, const char * argv[]) {

double num = PI * 3 * 3;//等同于double num = 3.14 * 3 * 3;
printf("num = %.2lf\n",num);

LOG;//等同于printf("这里是LOG\n");

int a= 1,b = 1;//如果宏值中有变量,必须先声明再使用
int result = SUM * 2;//等同于int result = a + b * 2; 注意替换后的运算符优先级
printf("result = %d\n",result);

int r = 2;//半径为2
float area = AREA;
printf("area = %.2f\n",area);

#undef PI //取消宏 PI ,在之后就不能替换宏值了

return 0;
}

有参数的宏:使用有参数宏的时候,需要调用宏的人传入一个值作为宏值的一部分
#include <stdio.h>
#define Log(str) printf(str)
#define SUM(a,b) a+b //这里的参数就是宏名和宏值里相同的部分

int main(int argc, const char * argv[]) {

Log("有参数宏\n");//等同于printf("有参数宏");

int num1 = 10,num2 = 20;

//这里传入的参数是num1和num2,并不是10和20。代码执行的时候才会为num1,num2赋值10,20
int sum = SUM(num1, num2);//等同于int sum = a + b;
printf("sum = %d\n",sum);

return 0;
}

注意:
1.当宏值是一个表达式,宏值的语法错误不会报错,因为检查语法是在编译的时候干的。
2.当宏值是一个表达式,替换宏名也是替换源代码中使用宏名的地方,所以特别注意替换后的运算符优先级问题。
3.宏值当中如果有变量,使用宏值之前必须要先声明这个变量。
4.如果双引号中出现了宏名,其实这个不是宏名,只是和宏名很像的字符串。
5.宏可以定义在任何地方,能被使用的作用域是从定义开始到文件结束。

五、条件编译

在很多情况下,我们希望程序的其中一部分代码只有在满足一定条件时才进行编译,否则不参与编译,这就是条件编译。
语法:
#if 条件1
代码1
#elif 条件2
代码2
#else
代码3
#endif
执行顺序:如果条件1满足,则代码1参与编译。如果条件1不满足,条件2满足,则代码2参与编译。如果条件1和条件2都不满足,则执行代码3。

第一种情况,判断宏值是否满足条件
#include <stdio.h>
#define Flag 1

//这里Score只能使用宏,不能使用变量
#if Flag == 1
#define SUM(a,b) a+b
#else
#define SUM(a,b) a-b
#endif

int main(){

int result = SUM(20, 10);//如果Flag为1则相加,否则相减
printf("result = %d\n",result);

return 0;
}

第二种情况,判断一个宏有被定义
#include <stdio.h>
#define Score 5

int main(int argc, const char * argv[]) {
#ifdef Score //等同于 #if defined(Score)
printf("已经定义了\n");
#else
printf("没有定义\n");
#endif
return 0;
}

第三种情况,判断一个宏没有被定义
#ifndef ___AAAA____ //等同于 #if !defined(Score)
#define ___AAAA____
//#include <stdio.h>
//#include "xxxxx.h"
//#include "aaaaa.h"
//#include "zzzzz.h"
//#include "vvvvv.h"
//这里是一百多个头文件。。
/*
这里表示如果没有定义宏名为___AAAA____的宏,就定义一个,然后包含进来若干头文件。
然并卵?

当其他文件包含本文件多次的时候,每次都会判断是否定义了___AAAA____。如果没有定义才包含,定义过了就算了
*/
#endif

注意:
1.条件编译一定要使用宏进行条件判断,不能使用变量,因为变量的值是在程序执行的时候才赋值的。
2.#endif表示这个条件编译结束,一定不能少,不然会发生一些不可预料的事情。
3.条件编译只会编译符合条件的那一个分支编译成二进制代码。

六、static与extern关键字

static与函数
如果一个函数被static关键字修饰,只能在当前模块中使用,就相当于内部函数咯。

extern与函数
如果一个函数被extern关键字修饰,可以在当前模块中使用,也能被其他模块共享,不过默认函数就是被extern修饰的,可以省略不写。

static与变量
被static修饰的局部变量会声明在常量区中,函数执行完毕后不会被释放,只有程序执行结束才会释放。
#include <stdio.h>

void test(){
static int num = 10;
num++;
printf("num = %d\n",num);
}
int main(){

for (int i = 0; i < 5; i++) {
test();//执行5次test函数
}
/*打印出
num = 11
num = 12
num = 13
num = 14
num = 15
*/
return 0;
}

被static关键字修饰的全局变量只能在当前模块中使用,不能被其他模块共享,相当于私有全局变量。
main.c文件
#include <stdio.h>
#include "test.h"
void test(){
num++;
printf("num = %d\n",num);
}
int main(){
for (int i = 0; i < 5; i++) {
test();
}
return 0;
}

test.h文件
static int num;

test.c文件
#include "test.h"
static int num = 100;

extern与变量
extern不能修饰局部变量,被extern修饰的全局变量可以在当前模块中使用,也能被其他模块共享。不过默认全局变量就是extern修饰的,所以我们可以省略(Xcode6.3前是默认加extern的,Xcode6.3后必须自己在声明里加上extern,但定义的地方可以不写)。
main.c文件
#include <stdio.h>
#include "test.h"
void test(){
num++;
printf("num = %d\n",num);
}
int main(){
for (int i = 0; i < 5; i++) {
test();
}
return 0;
}

test.h文件
extern int num;

test.c文件
#include "test.h"
extern int num = 100;

转载于:https://www.cnblogs.com/heartless/p/4911919.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值