C语言学习第十天

英语:
平方:square
file:文件
line:行号
function:函数
date:日期
time:时间
end:结束
architecture:架构

回顾:
1.常量,常量指针(用的最多),指针常量,常量指针常量:const(保护作用,不可随意)
const int a = 250;
const int p = &a;
int * const p = &a;
cont int
const p = &a;

2.无类型指针:void *
语法:void 指针变量名
特点:1.可以保存任意类型的地址
不能获取指向的存储的数据的数据类型
2.不能用
,必须强转
3.可以做指针的计算,加减几实际地址就加减几

3.字符串
概念:用""括起来的一组字符,结束符’\0’(=0)
特点:
1.分配的内存是连续的
2.字符串的首地址就是第一个字符的首地址
3.并列的字符最终要合并成一个字符串
形式:
1.字符串和指针
char *p = “hello,world”;
A(“hello,world”); //实际给A函数传递的是字符串的首地址,只占4字节
只要看到一个字符串,实际看到的是字符的首地址!!!!!
切记:通过p是不能修改其中的字符数据
2.字符串和数组
char a[] = {‘a’ …, ‘\0’};
cahr a[] = “hello,world”;
切记:可以修改字符数组中的字符数据
字符串的操作函数:都是标准C库函数(大神写好,咱直接用),理论上肯定自己也能写一个
strlen/strcat/strcmp/strcpy/sprintf
4.函数和指针的关系
形参是指针:函数通过形参能够可以修改实参
返回值是指针:指针函数,切记:不要反悔局部非静态变量的地址(野指针)


4.字符指针数组
4.1.明确:[]运算符的计算步骤:
1.先求地址
2.后取值
char a[] = {‘a’, ‘b’};
char p = &a;
p[1]:先求地址:p+1,然后取值:
(p+1)

4.2.指针数组(实际开发很常用)
a)指针数组概念:数组中每个元素都是一个指针(地址)
元素只能是地址,不能是普通的数据

b)指针数组定义的语法格式:
数据类型 *数组名[元素个数/数组长度] = {地址列表};
例如:
int a = 10, b = 20, c = 30;
int p[3] = {&a, &b, &c};
结果:p[0] = &a, p[1] = &b, p[2] = &c;
等价于:
(p+0) = &a, *(p+1) = &b, *(p+2) = &c;
取值:*p[0] = 10, *p[1]=20, *p[2] = 30;
等价于:**(p+0) = 10, **(p+1) = 20, **(p+2) = 30;
元素个数 = sizeof(数组名)/sizeof(数组名[0])

c)应用场景:如果将来需要定义大量的一堆堆的指针变量,反而让代码看起来极其繁琐
采用指针数组来进行统一,类似数组来替换大量的变量
现在用指针数组替换大量的指针变量
例如:
int a = 10, b = 20, c = 30, d = 40, e = 50; …
int *pa = &a, *pb = &b, *pc = &c, *pd = &d, *pe = &e; …
相当累啊,我好难啊,烦死了,想起指针数组来优化:
int *p = {&a, &b, &c, &d, &e …};

4.3.字符指针数组
a)概念:字符指针数组是特殊的指针数组,每个元素是一个字符串的首地址
例如:
char *p[] = {“abc”, “efg”}; //第0个元素是字符串"abc"的首地址
第1个元素是字符串’efg’的首地址
等价于
char *p1 = “abc”;
char *p2 = “efg”;
char *p[] = {p1, p2};

结果:
p[0] = p1 = “abc”
p[1] = p2 = “efg”;

b)应用场景:需要用到大量字符串指针变量此种情形!

--------------------------------------------------------------------------------------------------- 5.详解预处理
5.1.回顾C程序编译三步骤
a)预处理:gcc -E -o xxx.i xxx.c
b)只编译不链接:gcc -c -o xxx.o xxx.i/xxx.c
c)链接:gcc -o xxx xxx.o

5.2.预处理指令(以#开头,不分分号;)分类:
a)头文件包含指令:#include,又分两类:
#include <stdio.h> //告诉gcc直接到/usr/include目录下找stdio.h并且
把stdio.h的内容全部拷贝过来
#include “stdio.h” //告诉gcc先到当前目录下找stdio.h,如果没有找到再去/usr/include
目录下找stdio.h
问:如果包含的头文件不在当前目录下,也不在/usr/include目录下,怎么找到包含
的头文件呢?
答:可以通过-I选项指定头文件所在的路径(核心,开发常用)
编译命令格式:gcc -E -o xxx.i xxx.c -I /home/tarena/stdc/day10/

b)宏定义指令:#define
1.建议:宏的名称用大写,续行用
2.作用:提高代码的可移植性,让代码将来改起来方便
3.宏定义指令又分两类:常量宏和参数宏(宏函数)
3.1.常量宏
a)语法格式:#define 宏名 (值)
例如:#define PI (3.14)
效果:将来gcc会将程序中所有的宏PI替换成3.14
b)案例:利用宏实现计算圆的周长和面积
参考代码:circle.c
编译命令:gcc -E -o circle.i circle.c //此时gcc将PI替换成3.14
vim circle.i //跑到文件的最后看你的代码是否发生了替换
gcc -o circle circle.i //直接编译
./circle
3.2.参数宏(又称宏函数)
a)语法格式:#define 宏名(宏参数) (宏值)
注意:宏值里面的宏参数不要少圆括号
如果宏参数有多个,用逗号来分区
例如:#define SQUARE(x) ((x)*(x))
#define SUB(x, y) ((x) - (y))
优点:代码的执行效率比函数要高
b)gcc替换过程:gcc编译器先将宏参数替换成实际值, 然后将代码中宏名最终全部替换成宏值
c)案例:利用宏函数实现数的平方
参考代码:define.c
编译好习惯:gcc -E -o define.i define.c
vim define.i //好习惯看看替换的结果
gcc -o define define.i

c)#(转字符串指令)和##(粘贴指令)
1.#作用:将后面跟的宏参数转换为字符串
例如:#N替换成了"N"
参考代码:define.c
2.##作用:将其后面的宏参数进行替换然后与前面的部分粘连在一起,最终作为宏值替换
参考代码:define.c

#include <stdio.h>
//定义宏函数SQUARE:求平方
#define SQUARE(x)  ((x) * (x)) //圆括号不要少

#define PI  (3.14) //常量宏

//定义求圆周长的宏函数:CALP
#define CALP(r) (2*PI*(r))

//定义求圆面积的宏函数:CALA
#define CALA(r) (PI*(r)*(r))

//定义求两个数相减的宏函数:SUB
#define SUB(x, y) ((x) - (y))

//定义将某个数的某个位清0:CLEAR_BIT
#define CLEAR_BIT(data, n) ((data) &= ~(1 << (n)))

//定义将某个数的某个位置1:SET_BIT
#define SET_BIT(data , n) ((data) |= (1 << (n)))

//玩玩#:定义PRINT打印宏函数
#define  PRINT(N)   (printf(#N"=%d\n", N))

//玩玩##
#define  ID(N)  id##N
int main(void)
{
    printf("%d\n", SQUARE(10)); //(10*10)
    printf("%d\n", SQUARE(3+7)); //((3+7)*(3+7))
    printf("%lf\n", CALP(10));
    printf("%lf\n", CALA(10));
    printf("%d\n", SUB(300, 200)); //(300)-(200)
    
    int a = 0x55, n = 0;
    CLEAR_BIT(a, n);
    printf("%#x\n", a); //01010101->01010100=0x54
    SET_BIT(a, n);
    printf("%#x\n", a); //01010100->01010101=0x55
    
    int b = 10, c = 20;
    PRINT(b); //替换:printf("b""=%d\n", b) = printf("b = %d\n", b);
    PRINT(c);
    PRINT(b+c);
    
    int ID(1) = 100, ID(2) = 200; //替换: int id1 = 100, id2=200; 
    printf("%d  %d\n", ID(1), ID(2)); //替换:id1, id2
    
    printf("这里出错了:%s, %s, %s, %s, %d,"
            "出现了一个野指针的访问错误.\n",
                __DATE__, __TIME__, __FILE__, 
                __FUNCTION__, __LINE__);
   
    //gcc -DSIZE=250 -DWELCOME=\"达内\" -o define define.c
    printf("SIZE=%d, WELCOME=%s\n", SIZE, WELCOME);
    

d)编译器已经定义好的宏(直接使用,无需#define),实际开发非常常用!
注意:都是两个下划线
FILE:表示当前文件名,%s
LINE:表示当前行号,%d
FUNCTION:表示当前所在的函数名,%s,等价于__func__
DATE:文件创建的日期,%s
TIME:文件创建的时间,%s
实际开发使用公式:
printf(“这里出错了:%s, %s, %s, %s, %d,出现了一个野指针的访问错误.\n”,
DATE, TIME, FILE, FUNCTION, LINE);

e)用户预定义宏:通过gcc的-D选项来预定义一个宏
作用:程序在编译的时候可以给程序传递一个值
注意:如果宏是一个字符串,那么-D后面的宏值需要用"说明
例如:gcc -DSIZE=250 -DWELCOME=“达内”
结果代码中可以直接使用SIZE和WELCOME宏,并且他们的值分别是250和"达内"
printf("%d %s\n", SIZE, WELCOME);

f)条件编译命令 (实际开发用的非常多)
#if //如果,例如:
#if A1
#ifdef //如果定义了…
#ifndef //如果没有定义 …
#elif //否则如果
#else //否则
#endif //和#if,#ifdef,#ifndef配对使用
#undef //取消定义,和#define死对头,例如:#define PI (3.14) #undef PI
参考代码:if.c
实际开发的演示代码:
利用条件编译让一套代码支持不同的CPU(X86,ARM,POWERPC,DSP)
vim A(void)
{
#if ARCH
X86
只编译X86相关代码
#elif ARCH==ARM
只编译ARM相关代码

}


6.大型程序实现
6.1.掌握:头文件卫士
a)结论:以后编写任何头文件编码框架必须如下:
vim A.h 添加
#ifndef __A_H //头文件卫士,宏名一般跟头文件名同名
#define __A_H

  头文件的代码区域

  #endif //和#ifndef配对
  头文件卫士作用:防止头文件内容重复定义

b)问:头文件卫士如何保护呢?
答:分析原理(了解即可)
例如:
//没有头文件卫士的情形:
vim a.h 添加
int a = 250; //定义变量
保存退出

vim b.h 添加
#include “a.h”
保存退出

vim main.c 添加
#include <stdio.h>
#include “a.h”
#include “b.h”

int main(void)
{
printf("%d\n", a);
return 0;
}
保存退出
gcc -E -o main.i main.c
vim main.i
结果:
int a = 250; //重复定义了
int a = 250; //重复定义了
int main(void)
{
printf("%d\n", a);
return 0;
}
问:如何解决?
答:添加头文件卫士
//添加头文件卫士情形:
vim a.h 添加
#ifndef __A_H
#define __A_H
int a = 250; //定义变量
#endif
保存退出

vim b.h 添加
#ifndef __B_H
#define __B_H

#include “a.h”

#endif
保存退出

vim main.c 添加
#include <stdio.h>
#include “a.h”
#include “b.h”

int main(void)
{
printf("%d\n", a);
return 0;
}
保存退出
gcc -E -o main.i main.c
vim main.i
结果:
int a = 250;
int main(void)
{
printf("%d\n", a);
return 0;
}

预处理时展开:main.c
//#include “a.h”
#ifndef __A_H //一开始没有定义__A_H
#define __A_H //没有定义__A_H,那么就定义__A_H
int a = 250; //定义变量
#endif

//#include “b.h”
#ifndef __B_H //一开始没有定义__B_H
#define __B_H //没有定义__B_H,那么就定义__B_H

//#include “a.h”
#ifndef __A_H //前面已经定义了__A_H.所以后面两条语句消失
#define __A_H
int a = 250; //定义变量
#endif

#endif

3.2.实际开发产品代码组成部分:三部分
a)代码开发原则:
鄙视目前所学的代码结构:一个源文件搞定,实际产品代码不可能一个源文件
否则造成代码的维护极其的郁闷
实际产品代码应该按照功能将一个源文件分拆成多个源文件来实现
提高了代码的可维护性
b)实际产品代码分拆三部分:
1.头文件部分:负责变量的声明,自定义数据类型的声明和函数的声明
作用:将来别的源文件只需包含头文件即可访问变量和函数
例如:dog.h,duck.h,chick.h
2.源文件部分:负责变量的定义,自定义数据类型变量的定义,函数的定义
例如:dog.c描述狗,duck.c描述鸭子,chick.c描述鸡
3.主文件部分:负责统领头文件和源文件,它里面只有main函数,它负责调用其他源文件的
变量和函数
例如:main.c
4.建议:一个源文件对应一个头文件,并且源文件包含自己对应的头文件
5.建议:如果头文件中有相同的内容,建议摘出来之后放到一个公共的头文件中
其他头文件只需包含功能的头文件即可
vim common.h
两只眼
vim dog.h
#include “common.h”
vim duck.h
#include “common.h”
6.注意:static

案例:实现加,减,清0,置1
分析:
头文件:add.h sub.h set.h clear.h
源文件:add.c sub.c set.c clear.c
主文件:main.c
分步编译:
gcc -E -o add.i add.c
gcc -E -o sub.i sub.c
gcc -E -o set.i set.c
gcc -E -o clear.i clear.c
gcc -E -o main.i main.c

gcc -c -o add.o add.i
gcc -c -o sub.o sub.i
gcc -c -o set.o set.i
gcc -c -o clear.o clear.i
gcc -E -o main.o main.o

gcc -o main main.o set.o clear.o sub.o add.o
./main

一步到位:
gcc -o main main.c sub.c add.c set.c clear.c

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值