C语言对于Android开发来说还是非常必要的,不管你是要阅读源码,还是想要学习NDK,音视频,性能优化等,都不可避免需要接触到C,而且C语言属于系统级的语言,操作系统内核都有C的身影,所以我今天学习一下C语言,本篇博客作为笔记,以防以后忘记
C语言最初适用于系统开发工作的,特别是组成操作系统的程序,由于C语言产生的代码运行速度与汇编编写的代码运行速度几乎相同,所以采用C语言作为系统开发语言,下面列举几个使用C的实例
写在源文件的中代码,使我们人类可以看懂的,他需要编译转换为机器语言,这样CPU可以按照指令执行程序,而C语言可以通过GUN编译器,把源码编译为机器语言
Mac上直接下载Xcode就可以使用GUN编译器,开发工具可以使用CLion
或者直接用文本编译器
#include
int main()
{
/* 我的第一个 C 程序 */
printf("第一个c程序Hello, World! \n");
return 0;
}
一个C程序包括
#include
是预处理指令,告诉C编译器实际编译之前需要包括stdio.h
文件int main()
是主函数,程序从这里开始运行,printf()
是程序中另一个可用的函数作用是在屏幕上显示Hello, World!
return 0;
标识此函数结束,并返回0Hello, World!
Last login: Mon Feb 17 14:53:00 on ttys002
L-96FCG8WP-1504:~ renxiaohui$ cd Desktop/
L-96FCG8WP-1504:Desktop renxiaohui$ cd c/
L-96FCG8WP-1504:c renxiaohui$ gcc hello.c
L-96FCG8WP-1504:c renxiaohui$ a.out
第一个c程序 Hello, World!
L-96FCG8WP-1504:c renxiaohui$
标记符是用来标记变量,函数或者任何用户自定的变量名称,一个标识符以字母A—Z,a-z,或者_下划线开始,后面跟0个或多个字母,数字,下划线
标识符中不允许出现标点字符,比如@%,标识符是区分大小写的
这些关键字不能作为常量名或者变量名,其他标识符的名称
关键字 | 说明 |
---|---|
continue | 结束当前循环,开始下一轮循环 |
switch | 用于开关语句 |
case | 开关语句分支 |
default | 开关语句中的其他分支 |
break | 跳出当前循环 |
do | 循环语句的循环体 |
while | 循环语句的循环条件 |
if | 条件语句 |
else | 条件语句否定分支与if一起使用 |
for | 一种循环语句 |
goto | 无条件跳转语句 |
return | 子程序返回语句,可带参数可不带参数 |
char | 声明字符变量或者返回值类型 |
double | 声明双精度浮点类型对象或函数返回值类型 |
float | 声明浮点型变量或返回值类型 |
short | 声明短整形变量或者返回值类型 |
int | 声明整形变量或者返回值类型 |
long | 声明长整形变量或返回值类型 |
unsigned | 声明无符号类型变量或返回值类型 |
void | 声明函数无返回值或无参数,声明无类型指针 |
enum | 声明枚举类型 |
static | 声明静态变量 |
auto | 自动声明变量 |
const | 定义常量,如果一个变量被const修饰,则它的值不能被改变 |
extern | 声明变量或函数在其他文件或本文件的其他位置定义 |
register | 声明寄存器变量 |
signed | 声明有符号类型的变量 |
sizeof | 计算数据类型或变量的长度 |
struct | 声明结构体类型 |
union | 声明共用体 |
typedef | 给数据类型取别名 |
volatile | 说明变量在执行过程中可以隐含的改变 |
可以看到70%都是java中有的,学习起来并不是很难
C中的数据类型可以分为以下几种
类型 | 描述 |
---|---|
基本数据类型 | 他们是算术类型,包含俩种类型,整数类型和浮点数类型 |
枚举类型 | 他们也是算术类型,被用来定义在程序中只能赋予其一定的离散整数值得变量 |
void | 标识没有可用的值 |
派生类型 | 他包括指针类型,数组类型,结构类型,共用体类型和函数类型 |
我们先介绍基本数据类型
类型 | 存储大小 | 值范围 |
---|---|---|
char | 1字节 | -128-127或0-255 |
unsigned char | 1字节 | 0-255 |
signed char | 1字节 | -128-127 |
int | 2或4字节 | -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647 |
unsigned int | 2或4字节 | 0 到 65,535 或 0 到 4,294,967,295 |
short | 2字节 | -32,768 到 32,767 |
unsigned short | 2字节 | 0 到 65,535 |
long | 4字节 | -2,147,483,648 到 2,147,483,647 |
unsigned long | 4字节 | 0 到 4,294,967,295 |
各类型的储存大小,与系统位数有关,为了得到准确大小可以用sizeof
来计算
#include
#include
int main(){
printf("int 存储大小 : %lu \n", sizeof(int));
return 0 ;
}
int 存储大小 : 4
类型 | 存储大小 | 值范围 | 精度 |
---|---|---|---|
float | 4字节 | 1.2E-38 到 3.4E+38 | 6 位小数 |
double | 8字节 | 2.3E-308 到 1.7E+308 | 15位小数 |
long double | 16字节 | 3.4E-4932 到 1.1E+4932 | 19位小数 |
类型 | 描述 |
---|---|
函数返回为空 | C 中有各种函数都不返回值,或者您可以说它们返回空。不返回值的函数的返回类型为空。例如 void exit (int status); |
函数参数为空 | C 中有各种函数不接受任何参数。不带参数的函数可以接受一个 void。例如 int rand(void); |
指针指向void | 类型为void*指针代表对象的地址,而不是类型,例如,内存分配函数 void *malloc( size_t size ); 返回指向 void 的指针,可以转换为任何数据类型。 |
变量是程序可操作的储存区名称,C中每个变量都有特定的类型,类型决定了变量的大小和布局,该范围内的值都可以储存在内存中
C语言中有基本数据类型的变量,也可以有其他类型的变量,比如数组,指针,枚举,结构体,共用体等
变量的定义就是告诉储存器何处创建变量的储存,以及如何创建变量的储存,变量定义指定数据类型,并包含该类型的一个或多个变量列表
type variable_list;
type必须是一个有效的C数据类型,variable_list为变量的名字,可以有多个
int i, j, k;
char c, ch;
float f, salary;
double d;
上方i,j,k等声明并定义了变量i,j,k
变量可以在声明的时候初始化
type variable_name = value;
例如
extern int d = 3, f = 5; // d 和 f 的声明与初始化
int d = 3, f = 5; // 定义并初始化 d 和 f
byte z = 22; // 定义并初始化 z
char x = 'x';
变量的声明,像编辑器保证变量以指定的类型和名称存在,这样编辑器就可以在不知道变量完整细节的情况下,也能进一步编译
变量声明有俩种情况
extern int i; //声明,不是定义
int i; //声明,也是定义
常量是固定的值,在程序期间不会改变,这些固定的值又叫做字面量
常量可以是任何基本数据类型,常量在值定义之后不会修改
在C中有俩种定义常量的方式
#define
预处理器#define identifier value
#include
#include
#define FFF 10
#define DDD 20
#define HHH '\n'
int main(){
int a ;
a =FFF*DDD;
printf("值,%d",a);
printf("\n字符串,%c",HHH);
return 0 ;
}
值,200
字符串,
const
关键字const type variable = value;
int main() {
const int FFF =10;
const int DDD=20;
const char HHH='\n';
int a;
a = FFF * DDD;
printf("值,%d", a);
printf("\n字符串,%c", HHH);
return 0;
}
值,200
字符串,
储存类定义C中变量,函数的范围和生命周期,这些说明符放在他们所修饰的类型之前,下面列出可用的储存类
auto储存类是所有局部变量默认的储存类
{
int mount;
auto int month;
}
上面的两种写法都是一样的,auto只能用在函数内,即只能修饰局部变量
用于定义储存在寄存器中而不是RAM的局部变量,这意味着变量的最大大小是寄存器的大小
{
register int miles;
}
寄存器只用于需要快速访问的变量,比如计数器
编译器在声明周期内保持保持局部变量的值,而不需要每次进入和离开作用域是创建和销毁,因此使用static修饰局部变量,可以函数调用间保持局部变量的值
static也可以用于全局变量,当static修饰全局变量时,变量的作用域会限制在他的本文件中,也就是只有本文件才可以访问(普通的全局变量,使用extern外部声明后任何文件都可以访问)
static函数:和上方的全局变量一样(非静态函数可以在另一个文件中直接引用,甚至不必使用extern声明)
用于提供一个全局变量的引用,全局变量对所有的程序文件都可见
当在A文件定义一个全局变量a,B文件想要使用A文件的变量a,可以在B文件使用extern关键字拿到a的引用
第一个文件
#include
int count =5;
extern void add();
int main() {
add();
return 0;
}
第二个文件
#include
extern int count;
void add(){
printf("count=%d\n",count);
}
运行后
bogon:untitled renxiaohui$ gcc text.c tex1.c
bogon:untitled renxiaohui$ a.out
count=5
同java,不在记录
同java
同java
return_type function_name( parameter list )
{
body of the function
}
一个函数的组成部分
return_type
是一个函数返回值的数据类型,有些函数不返回数据,这种情况下return_type
的关键字是void
function_name
int Max(int num1,int num2){
int result;
if(num1>num2){
result=num1;
}else{
result=num2;
}
return result;
}
这就是一个函数的简单实例
函数的声明会告诉编译器,函数的名称和如何调用函数,函数的实际主体可以单独定义
函数声明包括以下几个部分
return_type function_name( parameter list );
针对上方的函数我们可以声明
int Max(int num1,int num2);
在函数声明中,参数名称并不是很重要,可以省略掉
int Max(int ,int);
当你在一个源文件定义一个函数,在另一个文件调用这个函数时,函数声明是必要的,这种情况下,你需要在调用函数文件的顶部声明函数
#include
int Max(int,int);
int main() {
int num1 = 100;
int num2 = 120;
int rec;
rec = Max(num1,num2);
printf("比较结果为%d\n",rec);
return 0;
}
int Max(int num1, int num2) {
int result;
if (num1 > num2) {
result = num1;
} else {
result = num2;
}
return result;
}
结果为
比较结果为120
在某个函数块内声明的变量为局部变量,他只能被该函数或者该代码块内的函数语句使用,局部变量外部是不可知的
#include
int main ()
{
/* 局部变量声明 */
int a, b;
int c;
/* 实际初始化 */
a = 10;
b = 20;
c = a + b;
printf ("value of a = %d, b = %d and c = %d\n", a, b, c);
return 0;
}
这里的a,b,c 都是局部变量
全局变量定义在函数的外部,通常是程序的顶部,全局变量在整个程序的生命周期内都是有效的,在任意的函数内部可以访问全局变量。
全局变量可以被任意函数访问,也就是说全局变量在声明后在整个程序中都是可用的
局部变量和全局变量名称可以相同,但是在函数内,如果俩个名字相,会使用局部变量,不会使用全局变量
#include
//全局变量
int a = 10;
int c = 40;
int main(){
//局部变量
int a = 20;
int b =30;
printf("a=%d\n",a);
printf("b=%d\n",b);
printf("c=%d\n",c);
return 0;
}
输出
a=20
b=30
c=40
type arrayName [ arraySize ];
这是一个一维数组,arraySize必须是大于0的常量,type是任意有效的c数据类型,声明一个含有10个double数据的nums数组
double nums [10];
初始化固定数量的数组
int nums [3] = {10,2,3}
初始化数量不固定的数组
int nums []={1,2,3,4,5}
为某个数组赋值
nums[3] = 6;
访问数组元素
int a = num[3];
实例
#include
int main(){
int n[10];
int i,j;
for(i=0;i<10;i++){
n[i]=i*10;
}
for (int j = 0; j < 10; ++j) {
printf("%d = %d\n",j,n[j]);
}
}
结果
0 = 0
1 = 10
2 = 20
3 = 30
4 = 40
5 = 50
6 = 60
7 = 70
8 = 80
9 = 90
枚举是C语言中的一种基本数据类型
enum 枚举名 {枚举元素1,枚举元素2,……};
假如我们定义一周七天,假如不用枚举,用常量
#define MON 1
#define TUE 2
#define WED 3
#define THU 4
#define FRI 5
#define SAT 6
#define SUN 7
假如用枚举
enum Day{
MON=1, TUE, WED, THU, FRI, SAT, SUN
}
这样看起来会很简洁
注意:默认第一个成员变量为0,后面的顺序加1,如果第一个是1,后面是2,以此类推
1 先定义枚举类型在定义变量
enum DAY{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;
2 定义枚举类型的同时定义枚举变量
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
3 省去枚举名称直接定义枚举变量
enum
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
实例
#include
enum DAY {
MON = 1, TUE, WED, THU, FRI, SAT, SUN
};
int main() {
enum DAY day;
day=THU;
printf("enun= %d\n",day);
return 0;
}
结果
enun= 4
在C语言中,枚举是被当做int
或者 unsigned int
来处理的,所以按照C语言规范是没有办法遍历枚举的
#include
#include
enum Color {
red=1, green, blue
};
int main() {
enum Color enumColor;
printf("请选择你喜欢的颜色(1 红色 2 绿色 3 蓝色)");
scanf("%d",&enumColor);
switch (enumColor){
case red:
printf("你喜欢红色\n");
break;
case green:
printf("你喜欢绿色\n");
break;
case blue:
printf("你喜欢蓝色\n");
break;
}
return 0;
}
结果
请选择你喜欢的颜色(1 红色 2 绿色 3 蓝色)1
你喜欢红色
每一个变量都有一个内存位置,每一个内存位置都定义了可使用&符号
访问地址,他表示在内存中的一个地址
#include
int main(){
int a ;
int b[10];
printf("a 的内存地址=%p\n",&a);
printf("b 的内存地址=%p\n",&b);
return 0;
}
结果
a 的内存地址=0x7ffee5e086c8
b 的内存地址=0x7ffee5e086d0
指针是一个变量,其值为另一个变量的内存地址,使用指针之前需要对其进行声明
type *var-name;
type
是指针的类型,他必须是一个有效的C数据类型,var-name
是指针的名称,*
用来表示这个变量是指针。
int *aa;//一个整形指针
double *bb;//一个double类型的指针
使用指针会频繁的进行如下几个操作,定义一个指针变量,把变量地址赋值给指针,访问指针变量中的可用地址
#include
int main(){
int a =20;//定义int值
int *b;//定义指针
b=&a;//为指针赋值
printf("变量的地址值=%p\n",&a);
printf("指针的值=%p\n",b);
printf("指针的地址值=%d\n",*b);
return 0;
}
结果
变量的地址值=0x7ffeef4356f8
指针的值=0x7ffeef4356f8
指针的地址值=20
变量声明的时候,如果没有明确的地址可以赋值,为指针赋值一个NULL是一个良好的习惯,赋值为NULL被称为空指针
int main(){
int *var = NULL;
printf("var的地址为=%p\n",var);
return 0;
}
var的地址为=0x0
#include
int main(){
int a;
int *b;
int **c;
a=40;
b=&a;
c=&b;
printf("a的值为=%d\n",a);
printf("b的值为=%d\n",*b);
printf("c的值为=%d\n",**c);
return 0;
}
a的值为=40
b的值为=40
c的值为=40
#include
int sum(int *arr,int size);
int* getarr();
int main() {
int a[4] = {1, 2, 3, 4};
int v = sum(a,4);
printf("sum=%d\n",v);
int *p = getarr();
for (int i = 0; i < 4; ++i) {
printf("数组=%d\n",*(p+i));
printf("数组=%d\n",p[i]);
}
return 0;
}
int sum(int *arr, int size) {
int sum = 0;
for (int i = 0; i < size; ++i) {
printf("i=%d\n", arr[i]);
printf("i=%d\n", *(arr+i));
sum+=arr[i];
}
return sum;
}
int * getarr(){
static int arr[4]={2,4,5,7};
return arr;
}
i=1
i=1
i=2
i=2
i=3
i=3
i=4
i=4
sum=10
数组=2
数组=2
数组=4
数组=4
数组=5
数组=5
数组=7
数组=7
C指针用数字表示地址,因此可以进行算术运算,++,–,+,-等,假如prt是一个int类型的地址1000,那么执行prt++,prt将指向1004,即当前位置移动4个字节,假如prt是一个char类型的地址1000,那么执行prt++,prt将指向1001,这个跟类型也是相关的
递增一个指针
#include
const int Max = 3;
int main() {
int arr[3] = {1, 2, 3};
int i ,*p;
//给p指针赋值数组中第一个元素的地址
p = arr;
for (int i = 0; i < Max; ++i) {
printf("元素的地址=%p\n", p);
printf("元素的地址=%d\n", *p);
//移动到下个位置
p++;
}
return 0;
}
元素的地址=0x7ffee165b6ac
元素的地址=1
元素的地址=0x7ffee165b6b0
元素的地址=2
元素的地址=0x7ffee165b6b4
元素的地址=3
有一种情况,我们数组内可以存储内存地址值
#include
const int Max= 3;
int main(){
int arr[3]={1,2,3};
int *p[Max];
for (int i = 0; i <Max ; ++i) {
p[i]=&arr[i];
}
for (int j = 0; j < Max; ++j) {
printf("指针数组数据=%p\n",p[j]);
printf("指针数组数据值=%d\n",*p[j]);
}
return 0;
}
指针数组数据=0x7ffee7cda6ac
指针数组数据值=1
指针数组数据=0x7ffee7cda6b0
指针数组数据值=2
指针数组数据=0x7ffee7cda6b4
指针数组数据值=3
指针也可以指向指针
#include
int main(){
int a = 10;
int *b;
int **c;
b=&a;
c=&b;
printf("a的值为=%d\n",a);
printf("b的值为=%d\n",*b);
printf("c的值为=%d\n",**c);
}
a的值为=10
b的值为=10
c的值为=10
#include
#include
void getlong(unsigned long *a);
int main() {
unsigned long b;
getlong(&b);
printf("b的值为=%ld\n",b);
}
void getlong(unsigned long *a) {
*a = time(NULL);
return;
}
b的值为=1585048748
函数指针是指向函数的指针变量,函数指针可以向普通函数一样,传递参数,调用函数
函数返回值类型 (* 指针变量名) (函数参数列表);
#include
int max(int, int);
int main() {
//p是函数指针
int (*p)(int, int) = &max;//&可以省略
int a, b, c, d;
printf("请输入三个数字:");
scanf("%d,%d,%d", &a, &b, &c);
d = p(p(a, b), c);//相当于调用max(max(a,b),c);
printf("d的值为=%d\n", d);
return 0;
}
int max(int a, int b) {
return a > b ? a : b;
}
输出
请输入三个数字:1,2,3
d的值为=3
#include
#include
void setvalue(int *arr, int b, int(*p)(void)) {
for (int i = 0; i < b; ++i) {
arr[i] = p();
}
}
int stett(int(*p)(void)){
return p();
}
int getvalue(void) {
return rand();
}
int main() {
int arr[10];
setvalue(arr, 10, getvalue);
for (int i = 0; i < 10; ++i) {
printf("i=%d\n", arr[i]);
}
int b;
b= stett(getvalue);
printf("b=%d\n", b);
return 0;
}
结果
i=16807
i=282475249
i=1622650073
i=984943658
i=1144108930
i=470211272
i=101027544
i=1457850878
i=1458777923
i=2007237709
b=823564440
C语言定义字符串Hello,两种形式,字符串和字符数组的区别:最后一位是否是空字符
#include
int main(){
char hello[6]= {'H','e','l','l','o','\0'};
char hello1[]= "hello";
char *hello2="hello";
printf("测试=%s\n",hello);
printf("测试=%s\n",hello1);
printf("测试=%s\n",hello2);
return 0;
}
测试=Hello
测试=hello
测试=hello
结构体可以存储不同类型的数据项
定义一个结构体
struct tag {
member-list
member-list
member-list
...
} variable-list ;
struct Book {
int book_id ;
char title[50];
char author[50];
char subject[50];
} book;
在一般情况下,tag、member-list、variable-list 这 3 部分至少要出现 2 个。以下为实例:
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct
{
int a;
char b;
double c;
} s1;
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE
{
int a;
char b;
double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
//也可以用typedef创建新类型
typedef struct
{
int a;
char b;
double c;
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;
//结构体包含其他结构体
struct AA{
int a;
struct Book b;
};
//结构体包含自己的指针
struct BB{
int b;
struct BB *nextbb
};
struct Book {
int book_id ;
char title[50];
char author[50];
char subject[50];
} book= {1,"c语言","小明","bb"};
title : c语言
author: 小明
subject: bb
book_id: 1
访问结构体的成员可以用.
符号,比如上方的book.title;
int main(){
struct Book book1;
strcpy(book1.title,"学习书");
strcpy(book1.author,"小红");
strcpy(book1.subject,"111");
book1.book_id=222;
printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book1.title, book1.author, book1.subject, book1.book_id);
return 0;
}
title : 学习书
author: 小红
subject: 111
book_id: 222
void printstuct(struct Book book);
int main(){
struct Book book1;
strcpy(book1.title,"学习书");
strcpy(book1.author,"小红");
strcpy(book1.subject,"111");
book1.book_id=222;
printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book1.title, book1.author, book1.subject, book1.book_id);
printstuct(book1);
return 0;
}
void printstuct(struct Book book){
printf( "Book title : %s\n", book.title);
printf( "Book author : %s\n", book.author);
printf( "Book subject : %s\n", book.subject);
printf( "Book book_id : %d\n", book.book_id);
}
title : 学习书
author: 小红
subject: 111
book_id: 222
Book title : 学习书
Book author : 小红
Book subject : 111
Book book_id : 222
定义,赋值,调用
struct Books *struct_pointer;
struct_pointer = &Book1;
struct_pointer->title;
int main() {
struct Book book1;
strcpy(book1.title, "学习书");
strcpy(book1.author, "小红");
strcpy(book1.subject, "111");
book1.book_id = 222;
printstuct1(&book1);
return 0;
}
void printstuct1(struct Book *book) {
printf("Book title : %s\n", book->title);
printf("Book author : %s\n", book->author);
printf("Book subject : %s\n", book->subject);
printf("Book book_id : %d\n", book->book_id);
}
Book title : 学习书
Book author : 小红
Book subject : 111
Book book_id : 222
共用体是一个特殊的数据类型,允许在相同的储存位置,储存不同的数据类型,可以定义一个带有多个成员的共用体,但是任何时候只能一个成员带值
用union
定义共用体,
union [union tag]
{
member definition;
member definition;
...
member definition;
} [one or more union variables];
union tag
是可选的,每个member definition;
都是标准的变量定义,如int i char b等,在分号之前可以定义一个或多个共用体变量是可选的
定义一个成员有int,float,char[]的共用体
#include
#include
union Data {
int a;
char b[100];
float c;
};
int main() {
union Data data;
printf("数据长度=%lu\n", sizeof(data));
data.a = 1;
data.c = 10.00;
strcpy(data.b, "测试数据");
printf("数据%d\n",data.a);
printf("数据%f\n",data.c);
printf("数据%s\n",data.b);
return 0;
}
数据长度=100
数据-393497114
数据-5278115000342806695772160.000000
数据测试数据
我们看到数据a,c成员的值有损坏,是因为最后赋值的变量占用了的内存位置,这也是b变量可以正确输出的原因
我们同一时间只能使用一个变量
#include
#include
union Data {
int a;
char b[100];
float c;
};
int main() {
union Data data;
printf("数据长度=%lu\n", sizeof(data));
data.a = 1;
printf("数据%d\n",data.a);
data.c = 10.00;
printf("数据%f\n",data.c);
strcpy(data.b, "测试数据");
printf("数据%s\n",data.b);
return 0;
}
数据长度=100
数据1
数据10.000000
数据测试数据
在这里所有的成员都可以正确输出,是因为同一时间只用到了一个变量
C语言提供typedef
关键字,可以使用它为类型起一个新的名字
#include
typedef unsigned int TEXT;
int main(){
TEXT a = 11;
printf("参数为=%d\n",a);
return 0;
}
参数为=11
也可以为用户自定义的数据类型去一个新的名字,比如结构体
#include
#include
typedef unsigned int TEXT;
typedef struct BOOKS {
char a[50];
char b[50];
} Book;
int main() {
TEXT a = 11;
printf("参数为=%d\n", a);
Book book;
strcpy(book.a, "测试1");
strcpy(book.b, "测试2");
printf("a=%s\n", book.a);
printf("b=%s\n", book.b);
return 0;
}
参数为=11
a=测试1
b=测试2
#define
是一个C指令,用于为各种数据类型定义别名,与typedef
类似,但是他有以下几种不同
typede
f仅限于为类型定义符号名称,#define
不仅为类型定义别名,也可以为数值定义别名typedef
为编译器解释执行,#define
为预编译器进行处理#define TRUE 0
#define FALSE 1
int main() {
printf("数值为=%d\n", TRUE);
printf("数值为=%d\n", FALSE);
return 0;
}
数值为=0
数值为=1
int getchar(void)
函数从屏幕读取下一个可用的字符,并把它返回为一个整数。这个函数在同一个时间内只会读取一个单一的字符。您可以在循环内使用这个方法,以便从屏幕上读取多个字符。
int putchar(int c)
函数把字符输出到屏幕上,并返回相同的字符。这个函数在同一个时间内只会输出一个单一的字符。您可以在循环内使用这个方法,以便在屏幕上输出多个字符。
#include
int main(){
printf("请输入一个字符\n");
int a = getchar();
printf("你的输入为\n");
putchar(a);
printf("\n");
}
请输入一个字符
text
你的输入为
t
char *gets(char *s)
函数从stdin读取一行到s指向的缓存区,直到一个终止符或一个EOF
int puts(const char *s)
函数吧一个字符串s和一个尾随的换行符写入stdout
#include
int main(){
char a[100];
printf("输入你的字符\n");
gets(a);
printf("你输入的字符为\n");
puts(a);
return 0;
}
输入你的字符
text
你输入的字符为
text
int scanf(const char *format, ...)
函数从标准输入流 stdin 读取输入,并根据提供的 format 来浏览输入。
int printf(const char *format, ...)
函数把输出写入到标准输出流 stdout ,并根据提供的格式产生输出。
format 是一个简单的常量字符串,但是你可以定义%s,%d,%c,%f来读取字符串,数字,字符,浮点数
#include
int main() {
char a[100];
int b;
printf("输入文字\n");
scanf("%s%d", a, &b);
printf("你输入的文字=%s,%d\n",a,b);
return 0;
}
输入文字
text 123
你输入的文字=text,123
#include
int main() {
FILE *file;
//打开文件允许读写,如果不存在则创建该文件
file = fopen("/Users/renxiaohui/Desktop/test.txt", "w+");
//写入一行
fprintf(file, "this is a text\n");
//再写入一行
fputs("this is a text aaaa\n", file);
fclose(file);
}
会在创建文件test.txt,并写入俩行文字
#include
int main() {
FILE *file1;
char a[255];
//打开一个文件,只读
file1 = fopen("/Users/renxiaohui/Desktop/test.txt", "r");
//读取文件,当遇到第一个空格或者换行符会停止读取
fscanf(file1, "%s", a);
printf("1= %s\n", a);
//读取字符串到缓冲区,遇到换行符或文件的末尾 EOF返回
fgets(a, 255, file1);
printf("2= %s\n", a);
fgets(a, 255, file1);
printf("3= %s\n", a);
//关闭文件
fclose(file1);
}
读取刚才创建的文件
1= this
2= is a text
3= this is a text aaaa
C预处理器不是编译器的组成部分,他是编译过程中的一个单独步骤,他们会在编译器实际编译之前完成所需的预处理
所有的预处理器都是以(#)开头的,他必须是第一个非空字符,为了增强可读性,预处理器应该从第一列开始,下面列出比较重要的预处理器
指令 | 描述 |
---|---|
#define | 定义宏 |
#undef | 取消定义的宏 |
#include | 包含一个源文件代码 |
#ifdef | 如果宏已经定义则返回真 |
#ifndef | 如果宏没有定义则返回真 |
#if | 如果给定条件为真则编译一下代码 |
#else | #if的替代方案 |
#elseif | 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码 |
#endif | 结束一个#if。。#else 语句 |
#error | 遇到标准错误是,输出错误消息 |
#pragma | 使用标准化方法,向编译器发布特殊指令到编译器中去 |
#define MAX 20
这个指令表示,把所有的MAX定义为20,使用#define定义常量可以增加可读性
#include
#include "myheader.h"
第一个表示从系统库获取stdio.h
库,并添加到源文件中,一个是从本地目录获取myheader.h
,并添加到源文件中
#undef FILE
#define FILE 99
取消已经定义的FILE,并把它重新定义为99
#ifdef MESSAGE
#define MESSAGE "you wish"
#endif
这个表示只有MESSAGE未定义时,才定义MESSAGE为you wish
ANSI C中预定义宏,我们可以在编程中使用这些宏,但不可以改变他的值
宏 | 描述 |
---|---|
1__DATE__ | 当前日期,一个以 “MMM DD YYYY” 格式表示的字符常量。 |
1__TIME__ | 当前时间,一个以 “HH:MM:SS” 格式表示的字符常量。 |
1__FILE__ | 这会包含当前文件名,一个字符串常量。 |
1__LINE__ | 这会包含当前行号,一个十进制常量。 |
1__STDC__ | 当编译器以 ANSI 标准编译时,则定义为 1。 |
看下使用
#include
int main() {
printf("data=%s\n",__DATE__);
printf("file=%s\n",__FILE__);
printf("time=%s\n",__TIME__);
printf("line=%d\n",__LINE__);
printf("stdic=%d\n",__STDC__);
return 0;
}
data=Mar 31 2020
file=text27.c
time=15:31:49
line=19
stdic=1
参数化宏可以模拟函数,例如下面是一个计算数字平方的方法
int square(int x) {
return x * x;
}
可以用参数化宏来表示
#define square(x) ((x)*(x))
在使用参数化宏之前必须使用#define来定义,参数列表必须包含在圆括号内的,并且必须紧跟在宏名称之后,不允许有空格
#define square(x) ((x)*(x))
int main() {
printf("平方为=%d\n",square(5));
return 0;
}
平方为=25
宏延续运算符(\)
一个宏通常写在一个单行上,但是如果宏太长,一个单行容不下,则使用宏延续运算符(\)例如
#define message_for(a,b) \
printf(#a"and "#b"we are friend\n")
字符串常量化运算符(#)
在宏定义中,需要把一个宏的参数转化为字符串常量时,则使用字符串常量化运算符(#)例如:
#define message_for(a,b) \
printf(#a"and "#b"we are friend\n")
int main() {
message_for("xiaoming","xiaohong");
return 0;
}
"xiaoming"and "xiaohong"we are friend
** 标记粘贴运算符(##)**
直接看代码比较
#define add(n) printf("token" #n " = %d\n",token##n)
int main() {
int token34 =40;
add(34);
return 0;
}
token34 = 40
这个是怎么发生的,因为这个实际会从编译器中产出以下实际输出
printf ("token34 = %d", token34);
defined()运算符
预处理器defined运算符是在常量表达式中的,用来确定一个标识符是否已经#define定义过,如果定义过为真,如果没有定义过值为假
#if !defined(TEXT)
#define TEXT "text\n"
#endif
int main() {
printf(TEXT);
return 0;
}
text
头文件是后缀名为.h
的文件,包含了函数声明和宏定义,有俩种头文件系统头文件和c程序员自己编写的头文件
在C程序中建议把所有的常量,宏,系统全局变量,和函数原型写入头文件中
使用预处理命令#include
引入头文件,有俩种类型
#include
这种形式用于引用系统头文件,他在系统目录搜索头文件
#include "file"
这种形式用于引用用户头文件,他在当前的文件目录搜索头文件
如果一个头文件引用被引用俩次,编译器会处理俩次头文件的内容,这将产生错误,为了防止这种情况,标准的做法
#ifndef HEADER_FILE
#define HEADER_FILE
the entire header file file
#endif
如果没有定义HEADER_FILE
则定义引入头文件,如果下次在引用因为HEADER_FILE
已经定义则不会再次引入
关于.h和.c的区别请看这篇文章
https://www.cnblogs.com/laojie4321/archive/2012/03/30/2425015.html
当C语言发生错误时,大多数的C会返回1或NULL,同时会设置一个错误代码errno
,改错误代码是全局变量,表示执行函数期间发生了错误,可以在 errno.h
头文件中找到各种错误代码
所以程序员可以通过返回值来判断是否有错误,开发人员在初始化时吧errno
初始化为0,是一个良好的编程习惯,0表示没有错误
C语言提供了perror() 和 strerror()来显示errno相关的文本信息
perror()
函数:他将显示你传入的文字后面跟一个冒号和来errno
相关的文本信息strerror()
函数:返回一个指针,指向当前 errno 值的文本表示形式。#include
#include
#include
int main() {
FILE *pf;
int errnum;
pf = fopen("aaa.txt", "rb");
if (pf==NULL){
//stderr,输出到屏幕
printf("错误号为%d\n",errno);
fprintf(stderr,"错误为%s\n",strerror(errno));
perror("perror显示错误");
}
return 0;
}
错误号为2
错误为No such file or directory
perror显示错误: No such file or directory
C语言为内存的分配提供了几个函数,在
函数 | 描述 |
---|---|
void *calloc(int num, int size); | 在内存中动态的分配num个长度为size的连续空间,并将每一个字节初始化为0 |
void free(void *address); | 该函数释放address所指向的内存块,释放的是动态分配的内存空间 |
void *malloc(int num); | 在堆区分配一块指定大小的内存空间,用来储存数据,该内存空间在函数执行完之后不会初始化,他们的值是位置的 |
void *realloc(void *address, int newsize); | 该函数重新分配内存,新内存分配到newsize大小 |
注意:void* 表示未确定类型的指针,void*类型指针可以通过强制转换转换为任意其他类型的指针
编程时如果你预先知道数组的大小,那么定义数组时会比较容易,比如一个可以容纳100个字符的数组
char name[100];
但是如果不知道需要储存文本长度的,那就需要动态分配内存了
#include
#include
#include
int main() {
char a[100];
char *b;
strcpy(a, "固定数组100");
//动态分配内存
b = (char *) malloc(200 * sizeof(char));
// b= calloc(200, sizeof(char));
if (b == NULL) {
printf("分配内存失败");
} else {
strcpy(b, "动态分配内存成功");
}
printf("值为=%s\n",b);
//重新分配内存
b = realloc(b, 500 * sizeof(char));
if (b == NULL) {
printf("分配内存失败");
} else {
strcpy(b, "重新分配内存成功");
}
printf("值为=%s\n",b);
//释放内存
free(b);
return 0;
}
值为=动态分配内存成功
值为=重新分配内存成功
上面分配内存的代码也可以替换为
b= calloc(200, sizeof(char));
执行程序时可以从命令行传递参数给程序,这些值为命令行参数
,命令行参数是通过main函数参数来处理的,其中,argc 是指传入参数的个数,argv[] 是一个指针数组,指向传递给程序的每个参数
#include
int main(int argc, char *argv[]) {
if (argc == 2) {
printf("参数为 %s\n", argv[1]);
} else if (argc > 2) {
printf("参数超过俩个\n");
} else {
printf("只有一个参数\n");
}
return 0;
}
L-96FCG8WP-1504:untitled renxiaohui$ gcc text30.c
L-96FCG8WP-1504:untitled renxiaohui$ a.out text
参数为 text
https://www.runoob.com/cprogramming/c-tutorial.html