一、函数指针数组
本质:数组,数组的元素是函数指针
示例:
#include <stdio.h>
int add(int x,int b);
int sub(int x,int b);
int mul(int x,int b);
int main()
{
//p是一个函数指针数组,数组中存放3个函数指针 p[0] add p[1] sub p[2] mul
int (*p[3])(int ,int ) = {add, sub, mul};
int i;
//遍历数组
for(i=0; i<3; i++)
{
//p[i]是一个函数指针,通过p[i]调用函数
printf("%d\n", p[i](6,9));
}
return 0;
}
int add(int x,int b)
{
return x+b;
}
int sub(int x,int b)
{
return x-b;
}
int mul(int x,int b)
{
return x*b;
}
二、linux标准main函数
思考:
#include <stdio.h>
int main()
{
char* paras[] = {
"hello",
"hehe",
"haha"
};
printParas(paras, 3);
return 0;
}
void printParas(char** p, int n)
{
}
观察:
//在函数的形参列表中,距离形参最近的*可以使用[]代替,都是表示指针变量的意思。[]里面就算写数字也是无效的。
//形参中的指针变量,比如int* 在语法上既可以指向int类型的变量,又指向int类型的数组。
//在形参列表中距离形参最近的*和[]在语法上是没有区别的,[]在可读性上给人传达的信息是指针要指向数组。
void fun(int* p)
{
}
void fun2(int p[])
{
}
int main()
{
int a[10];
fun2(a);
int b;
fun(&b);
}
//所以上面思考内容中的函数可以定义成
void printParas(char* p[], int n)//在语法上和void printParas(char** p, int n)等价
{
}
linux标准main函数的格式:
//linux标准main函数的形参列表,传的是字符指针数组
//argc argument count 参数个数既字符指针数组的元素个数
//argv argument value 参数的值既字符指针数组的首地址
int main(int argc, const char *argv[])
{
return 0;
}
示例1:
#include <stdio.h>
int main(int argc, const char* argv[])
{
//遍历字符指针数组,打印每个字符指针指向的字符串
int i;
for(i = 0;i < argc;i++)
{
printf("%s\n", argv[i]);
}
return 0;
}
标准main函数编程示例:
在运行程序是通过参数的形式给程序传入一个整数,判断这个整数是不是质数。不可以使用atoi函数。
./a.out 7
是质数
./a.out 123
不是质数
#include <stdio.h>
int myatoi(char* s);
int prime(int num);
int main(int argc, char* argv[])
{
//argv[1]是char*类型变量,指向我们输入的数字字符串
int num = myatoi(argv[1]);//使用argv[1]初始化形参s,所以s也指向输入的字符串*(argv+1)
if(prime(num))
{
printf("%d yes\n", num);
}
else
{
printf("%d no\n", num);
}
return 0;
}
int myatoi(char* s)
{
int sum = 0;
int i = 0;
while(s[i] != '\0')
{
sum = sum*10+s[i]-'0';
i++;
}
return sum;
}
int prime(int num)
{
int i;
for(i = 2;i < num;i++)
{
if(num%i == 0)
{
return 0;
}
}
return 1;
}
三、数据类型:
基本数据类型: char int short long float double
构造数据类型: 指针。 数组。 结构体。 共用体(联合体)。 枚举。
空类型: void
1. 枚举 enum
本质:是类型,枚举里面的成员都是整型常量。
#include <stdio.h>
//使用宏定义给常量起名字,让常量看起来有意义。
#define UP 0
#define DOWN 1
#define LEFT 2
#define RIGHT 3
int main()
{
int dir;
switch(dir)
{
case UP:
break;
case DOWN:
break;
case LEFT:
break;
case RIGHT:
break;
}
return 0;
}
(1)定义:
enum TimeofDay//定义枚举类型
{
//枚举的所有成员都是整型常量
//枚举成员的值默认从0开始,后面的成员依次+1
//枚举的成员后面是逗号
morning, //0
afternoon, //1
evening //2
};
//无论枚举类型定义多少成员,枚举类型都是4字节
//当我们定义枚举类型变量的时候,变量里并不是包含所有的成员,枚举类型的变量很接近int类型变量,枚举类型
//的变量只能被枚举的某个成员赋值。
sizeof(enum TimeofDay);//4
enum TimeofDay
{
morning = 100, //100
afternoon , //101
evening //102
};
(2)注意要点:
1)枚举类型中,声明的第一个枚举成员默认值为0
2)以后每个枚举成员值将是前一个枚举成员的值加1得到的。
3)定义枚举类型时,可以为枚举成员显式赋值。允许多个枚举成员有相同的值。
4)没有显式赋值的枚举成员的值,总是前一个枚举成员的值+1
(3) 使用示例:
枚举的使用,一般不会定义枚举类型变量,因为没有意义,枚举的每个成员都是常量。
enum TimeofToday
{
morning,//0
halfmorninig = 0,//0
afternoon, //1
halfafternoon = 10, //10
evening //11
};
int main()
{
//枚举类型的成员可以直接作为常量使用
printf("%d %d %d %d %d\n", morning, afternoon, evening, halfmorninig, halfafternoon);
}
(4) 为什么使用枚举
#include <stdio.h>
/*#define UP 0
#define DOWN 1
#define LEFT 2
#define RIGHT 3*/
enum Directory
{
up,
down,
left,
right
};
void printDir(enum Directory dir);//在不看源码的情况下,很容易让人看出来实参应该是枚举中的某个成员
//void printDir(int dir);//在不看函数源码的情况下,很难让人看出参数需要什么样的实参。
int main()
{
//printDir(5);
printDir(up);
return 0;
}
//参数的传入的表示方向的值,实参应该是UP DOWN LEFT RIGHT 中的一个值
/*void printDir(int dir)
{
switch(dir)
{
case UP://上
printf("上\n");
break;
case DOWN://下
printf("下\n");
break;
case LEFT://左
printf("左\n");
break;
case RIGHT://右
printf("右\n");
break;
}
}*/
void printDir(enum Directory dir)
{
switch(dir)
{
case up:
printf("上\n");
break;
case down:
printf("下\n");
break;
case left:
printf("左\n");
break;
case right:
printf("右\n");
break;
}
}
2. 共用体 union
也叫联合体
(1)定义
共用体的所有成员共用同一段内存,共用体占内存是最大基本类型成员的整数倍。
union Data
{
//成员a和成员共用同一段内存,b占用a的低位地址字节
int a;
char b;
};
sizeof(union Data);//4
union Data
{
int a;
char b[18];
};
printf("%d\n", sizeof(union Data));//20 最大基本类型是int,所以一定是int的整数倍
#include <stdio.h>
union Data
{
int a;
char b;
};
int main()
{
union Data d;//联合体类型的变量d
printf("%d\n", sizeof(union Data));//4
printf("%p %p %p\n", &d.a, &d.b, &d);//地址的值是一样的
printf("%p %p\n", &d.a+1, &d.b+1);//但是他们的类型是不一样的
return 0;
}
(2)要点:
-> 使用内存的方式,与结构体不同。
-> 共用体的各个成员共用内存,各个成员的起始地址是相同的。(占内存小的成员,是在大成员的低位地址)
-> 整个共用体占用的存储空间以长度最大的成员为准,共用体占内存是最大基本类型成员的整数倍。
-> 一个共用体变量,如果对多个成员赋值,会覆盖掉其他成员的数据。
(3) 代码示例: 测试主机序
字节序:大端对齐(高位地址放低位数据)和小端对齐(高位地址放高位数据)。
#include <stdio.h>
union Data
{
int a;
char c;
};
int main()
{
union Data x;
x.a = 0x12345678;
printf("%x\n", x.c);//78 输出的是低位地址,低位数据,ubuntu是小端对齐
return 0;
}
(4)共用体的作用
1.作为数据泛型,一个共用体变量可以表示多种类型的数据。
2.实现巧妙的数据转换,比如将成员a转换为成员b。
#include <stdio.h>
#include <string.h>
//使用联合体实现类型转换的用法
struct Student
{
char name[20];
int age;
};
union Data
{
struct Student stu;//结构体类型的成员
char buf[sizeof(struct Student)];//和结构体成员一样的大字符数组,逻辑上也是字节数组。
};
int main()
{
union Data d;//定义联合体变量d
strcpy(d.stu.name, "xiaoming");//给d中的结构体成员赋值,与此同时也在给数组元素赋值
d.stu.age = 18;//给d中的结构体成员赋值,与此同时也在给数组元素赋值
//当结构体完成赋值时,数组也完成了赋值,然后可以通过数组将结构体的数据发送。
d.buf;//网络发送数据或者串口发送数据时一般都是以字节为单位发送数据。
return 0;
(5) 共用体对比结构体
结构体每个成员都有独立的内存,而共用体所有成员共用同一段内存。
3. 位域
有时用一个字节去存储信息,还是会很浪费内存,此时就可以使用位域。使用位域的目的是为了节省内存。
1) 定义:
//这个例子只是为了演示位域的语法,这个例子本身并不合理。
struct data
{
unsigned int a : 2; //a占了int类型的前0位和1位
unsigned int b : 4; //b 占了int类型的 2 3 4 5位
unsigned int : 0; /* 空域 *///无用的空间,占了int类型剩下的所有位6~31位
unsigned int c : 3; //c占了另一个int的0 1 2位
};
sizeof(struct data);//8
struct data
{
unsigned int a:2; //int 0 1
unsigned int b:4; //int 2 3 4 5
unsigned int c:3; //int 6 7 8
unsigned int:0; //空域 9~31
};
sizeof(struct data);//4
(2) 注意要点:
1)各位域必须存储在同一个类型长度中,不能跨两个类型长度。
2)位域占用的位数,不能超过类型长度。
3)允许位域无域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。
四、const
作用: 使变量变成只读。
const修饰的变量一定要初始化。
两种情况:
1、常量没有名字,给常量起名字,这个时候如果使用宏,全局有效。如果只在一个小作用域内使用常量,我们可以使用const定义一个变量,就相当于给常量起名字。
2、函数传参数,有一些不想被改变的参数,这个时候会使用const来定义,比如:
字符串操作函数
int strcmp(const char* s1, const char* s2);
在函数内部不可以对这两个参数修改。
示例1:
int main()
{
const int a = 10;//a被修饰成只读
a = 20;//报错 因为a被const修饰,不能修改了
const int b;//尽管编译器不报错,但是也不能这么用,const修饰的变量必须初始化。
return 0;
}
示例2:
#include <stdio.h>
int main()
{
int a = 100, b = 20;
int* const p = &b;//p指向b
p = &a;//const修饰了p,所以p不能改变指向
*p=11;//p指向的变量是可以修改的
printf("%d\n", *p);
}
------------------------------------------------------
#include <stdio.h>
int main()
{
int a = 100, b = 20;
int const* p = &b;//const修饰的是p指向的对象,可以简单记忆成*p
//const int* p = &b;//完全等价于int const* p = &b;
p = &a;//p可以随意改变指向
*p = 11;//不能通过p修改它指向的变量
printf("%d\n", b);
return 0;
}
重点:
const修饰指针变量,const可能出现在*的左边也可能出现在*的右边;出现在*的左边,修饰*p(不能通过指针去修改指针指向的变量);出现在*右边,修饰p(不能修改指针的指向)。
const出现在*左边,叫 常量指针 *p不能改变
const出现在*右边,叫 指针常量 p不能改变
被const修饰的变量必须初始化。
示例3:
#include <stdio.h>
int main()
{
int a = 100;
const int *p = &a;
a = 80;//虽然不能通过p对a赋值,但是可以直接对a赋值
*p = 60;//错误
printf("%d\n", a);
return 0;
}
五、数据存储类型
4种。 自动(auto)、寄存器(register)、静态存储(static)、外部存储(extern)
auto是关键字,但是没有实际用途。
auto就是我们在栈空间定义的局部变量
register针对硬件编程才能用到,就是硬件中的寄存器
1. extern
静态外部链接:使用其他文件中定义的全局变量
示例4:
/// a.c
int a = 200;//在a.c中定义全局变量
/// b.c
#include <stdio.h>
extern int a;//声明使用其他文件中定义的全局变量a
int main()
{
printf("a = %d\n", a);
return 0;
}
执行过程: gcc a.c b.c
2. static
修饰静态。
1)static 修饰全局变量:
如果全局变量被 static 修饰,则该全局变量只能在当前文件中使用,不能被外部使用。
示例5:
/ a.c
static int a = 200;
/// b.c
#include <stdio.h>
extern int a;//报错,因为全局变量a只能在a.c中使用
int main()
{
printf("a = %d\n", a);
return 0;
}
2)static 修饰局部变量
普通的局部变量被定义在内存什么空间?
static修饰局部变量,将局部变量定义在静态空间。
被修饰的局部变量,只初始化一次。生命周期,从进程开始到结束,作用域:就在原来的作用域内。
示例6:
#include <stdio.h>
void fun()
{
static int a = 90;//静态局部变量,被定义在内存的静态空间,a是在程序执行时创建的。
//静态局部变量虽然生命周期变成了,但是作用域没有变化。
printf(" %d \n", a);
a++;
}
int main()
{
fun();
printf(" %d \n", a);//报错
fun();
fun();
return 0;
}
执行结果:90 90 90
静态空间的变量:90 9192
全局变量:不管有没有static修饰,都在静态空间。static对于全部变量的作用是限制全局变量的访问范围。
局部变量:用static修饰的局部变量在静态空间,生命周期和程序一样长,但是作用域不发生变化。
3)static 修饰函数:
被修饰的函数,限定在当前文件内使用。
示例7:
/a.c///
//static修饰的fun函数只能在a.c中使用
static void fun()
{
printf(“hello world\n”);
}
/b.c///
void fun();
int main()
{
fun();//报错,因为b.c中不能使用a.c的static修饰的函数
return 0;
}
编译报错
小总结:
static和const经常在面试中被一起问到,但是他俩又没有任何关系。
static修饰局部变量 生命周期变得和程序一样长,但是作用域不变
static修饰全局变量 缩小了全局变量的访问范围,只能在定义全局变量的文件中使用全局变量,生命周期没有改变
static修饰函数 缩小了函数的访问范围,只能在定义函数的文件中使用
const 将变量修饰成只读的。
常量指针 指向常量的指针,*p不能变。 const在*左边
指针常量 不能改变指向的指针,p不能变。const在*右边