【无标题】指针

一、函数指针数组

本质:数组,数组的元素是函数指针

示例:

#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在*右边

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值