C简介
一、基本概念和特点
1、基本概念:C 是一门 面向过程 强类型 静态 编译型 高级语言
2、C语言的特点如下:
- C语言是一个有结构化程序设计、具有变量作用域以及递归功能的过程式语言;
- C语言传递参数均是以值传递,另外也可以传递指针;
- 不同的变量类型可以用结构体组合在一起;
- 部份的变量类型可以转换,例如整型和字符型变量;
- 通过指针,C语言可以容易的对存储器进行低级控制;
- 预编译处理让C语言的编译更具有弹性;
二、hello world
#include <stdio.h>
int main()
{
/* 我的第一个 C 程序 */
printf("Hello, World! \n");
return 0;
}
*******************tips***********************
• 编译 C 程序请先安装 GCC 编译器;
基础知识
一、基本语法
- C 的令牌(Tokens)是最小词法/语法元素,可以是关键字、标识符、常量、字符串值,或者是一个符号;
- C语句也是以分号结束,且分号不能少;
- 注释和 java 一样(单行和多行);
- 标识符命名规则也和 java 一致;
- C语言对大小写敏感;
二、数据类型
1、整数类型
- char-----unsigned char(1)
- short-----unsigned short(2)
- int-----unsigned int(4)
- long-----unsigned long(8)
- long int------long long
2、浮点类型:float(4)----double(8)----long double(16)
3、void 类型
- 函数的返回值和参数为空都可以用 void;
- 指针指向 void:类型为 void * 的指针代表对象的地址,而不是类型。例如,内存分配函数 void *malloc( size_t size ); 返回指向 void 的指针,可以转换为任何数据类型。
4、类型转换
- 自动转换(隐式转换),基本的方向如下:
- 有时候需要进行强制转换,下面的例子是否进行强制转换得到的结果是不同的
#include <stdio.h>
int main()
{
int sum = 17, count = 5;
double mean;
mean = (double)sum / count;
printf("Value of mean : %f\n", mean ); // result is 3.4
mean = sum / count;
printf("Value of mean : %f\n", mean ); //result is 3.0
}
三、变量、常量、存储类和运算符
1、变量
(1)变量的申明和定义
extern int i; //声明,不是定义
int i; //声明,也是定义
- 定义是需要分配存储空间的,而仅仅申明不需要;
(2)关于左值和右值
- 左值:指向内存位置的表达式被称为左值(lvalue)表达式;
- 右值:指的是存储在内存中某些地址的数值;
- 总结:变量是左值,可以出现在等号的左右两边;数字是右值,不能出现在赋值号的左边;
2、常量
(1)四种常量
- 整数常量和浮点常量:
- 字符常量和字符串常量(区分单括号和双括号):转义字符的用法和其他的语言差不多;
(2)常量定义
#define LENGTH 10
const int WIDTH = 5;
3、存储类(类似于 java 中的修饰符)
- auto:局部变量默认的存储类,只能修饰局部变量;
- register:修饰的还是局部变量,但是这个局部变量比较特殊,一般是存在寄存器(CPU)中的,不能使用一元的 & 运算符(不在内存中);被 register 修饰不一定就是存在寄存器,而是可能存在寄存器(受硬件条件影响)
- static:修饰局部变量,在程序的整个生命周期这个局部变量都不会销毁;修饰全局变量,会使变量的作用域限制在声明它的文件内(在另外的文件中即使用 extern 也无法访问),只要在同一个文件中怎么调用都行;
- extern:是用来在另一个文件中声明一个全局变量或函数(引用其他文件的非 static 的全局变量和函数);
4、运算符
- 算数运算符也是支持 ++ 和 -- 的,用法和 java 一样;
- 位移运算比 java 少了无符号的右移,其余一致;
- 其他运算符:sizeof &取地址 *a 指向一个变量;
四、语句和函数
1、语句
- 判断:if---else 和 switch(break),用法和 java 基本是一样;
- 循环:while、for 和 do--while,用法也是和 java 一致的,也支持continue 和 break,还有goto(跳转到标记的行,不建议使用);
2、函数
- 函数声明:int max(int num1, int num2);使用在前面,定义在后面的话,需要在使用之前进行函数的声明;
- 函数传参:值传递(不会改变实参的值)和引用传递(通过指针传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作);
3、作用域
- 全局变量:全局变量保存在内存的全局存储区中,占用静态的存储单元;自动初始化;
- 局部变量:局部变量保存在栈中,只有在所在函数被调用时才动态地为变量分配存储单元;需要手动初始化;
- 函数形参:被当作该函数的局部变量;
数据类型(扩展)
一、数组
1、基本使用
- 数组的声明和初始化如下,访问还是通过下标访问;
- 数组容量的获取可以使用:double haha[4];int len = sizeof(haha)/sizeof(double);
- 多维数组的使用和 java 没有太大的差异;
- 数组名 balance 和 &balance 的值是一样的,但意义不同,前者是数组的首元素地址,后者是整个数组的地址;一般情况等同来用不会有太大的问题;参考资料
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};
2、数组作函数参数和返回值
- 返回数组一定要将函数内的数组声明为 static;不允许返回一个完整的数组,只能返回一个指针(数组名),函数的定义的时候返回值也要写指针;
- 类似指针作形参 voidmyFunction(intparam),可以传递一个数组(这里的变量名 param 和数组名实际代表的都是地址,可以等同看待);
************************tips*****************************
• C语言随机数使用:srand((unsigned)time(NULL));rand();
- 计算机内存的基本单位是 byte,而不是 bit;
3、指向数组的指针和指针数组
(1)指向数组的指针
- 在如下定义的前提下,*(balance + 4) 是一种访问 balance[4] 数据的合法方式
double *p;
double balance[10];
p = balance;
(2)指针数组
- 首先这是一个数组;
- 其次这个数组的元素都是指针,存的是指向其他变量的地址;
- 可以用一个指向字符的指针数组来存储一个字符串列表,使用如下:
char *names[] = {
"Zara Ali",
"Hina Ali",
"Nuha Ali",
"Sara Ali",
};
(3)指向指针的指针
- 多级的间接寻址的方式,会形成指针链
int var;
int *ptr;
int **pptr;
var = 3000;
ptr = &var;
pptr = &ptr;
printf("Value of var = %d\n", var );
printf("Value available at *ptr = %d\n", *ptr );
printf("Value available at **pptr = %d\n", **pptr);
二、枚举
1、枚举的定义和使用
- 枚举的本质就是一些离散的整数值;
- 枚举的定义和使用如下所示:
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;
2、其他说明
- 枚举的遍历:值连续的枚举可以循环遍历;
- 整数转换为枚举类型:(enum DAY)1;
- 枚举在 switch 语句中的应用(java 中也可以);
三、指针
1、指针是什么:指针是一个特殊的变量,这个变量的值是另一个变量的地址
2、指针怎么用
#include <stdio.h>
int main ()
{
int var = 20; /* 实际变量的声明 */
int *ip; /* 指针变量的声明 */
ip = &var; /* 在指针变量中存储 var 的地址 */
printf("Address of var variable: %p\n", &var );
/* 在指针变量中存储的地址 */
printf("Address stored in ip variable: %p\n", ip );
/* 使用指针访问值 */
printf("Value of *ip variable: %d\n", *ip );
return 0;
}
3、指针使用细节
- 指针的递增递减和比较:指针支持 ++ -- 等算数操作和 <> 等比较运算;
- 函数指针:指向函数的指针,给函数指针赋值时,可以用&fun或直接用函数名fun,二者基本是等效的,都能取到函数的地址;
- 回调函数:(示例参考)
- 函数指针变量可以作为某个函数的参数来使用的
- 回调函数就是一个通过函数指针调用的函数
- 回调函数是由别人的函数执行时调用你实现的函数
四、字符串
1、字符串的本质和定义
- 字符串的本质就是字符数组,其定义的形式是这样的:char greeting[] = "Hello"
2、字符串常用函数(如下的函数都是在 c 的标准库<string.h>中的)
五、结构体(有点类似于 java 中的类的概念)
1、结构体的定义、初始化和访问
- 数组是用来存储相同类型的数据的,而结构体是用来组织相关联的不同类型的数据的,其定义和初始化如下:
- 结构体成员的访问方式和 java 中类成员的访问方式是类似的(book.title),需要注意的是c语言中字符串是不能直接赋值的,要使用strcpy函数来赋值(定义初始化的时候例外);
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book = {"C 语言", "test", "编程语言", 123456};
2、结构体做函数参数和指向结构的指针
- 结构体做函数的参数的使用和其他类型做函数参数没啥不同;
- 指向结构体的指针使用:定义和通过指针访问结构体的成员
struct Books *book; //define
book->title //access member
3、位域的概念
- 位域的个人的理解就是对字节(byte)进行更加精确的位(bit)划分,利用划分出来的位段来存储数据,这样可以节省存储空间,并且便于处理;
- 位域的定义如下,访问按照结构体成员的一般方式即可访问
struct bs{
int a:8;
int b:2;
int c:6;
}data;
- 关于位域的几点说明:
- 位域在本质上就是一种结构类型,不过其成员是按二进位分配的;
- 位域可以是无名位域,只用来作填充或调整位置,是不能使用的;
- 关于位域的跨字节问题:很多教程说位域不能跨字节,但是实测是可以的,限制是不能超过原有类型的字节数(int 位域划分就不能超过 32 位);
- 位域的赋值超过位数限定范围不会报错但是数值会出现重置的现象(4位的位域赋值 17,打印出来的值就是1);
4、用结构体实现类似于面向对象的编程方式
int max(int,int);
struct ren{
int (*mymax)(int,int);
char name[50];
} ren1 = {max,"haha"};
int main(void)
{
struct ren ren2;
strcpy(ren2.name,"ren2");
ren2.mymax = max;
printf("ren2 name = %s\n",ren2.name);
printf("ren2 max = %d\n",ren2.mymax(34,1000));
return 0;
}
int max(int a,int b){
return a>b?a:b;
}
六、共用体
- 共用体 是一种特殊的数据类型,允许在相同的内存位置存储不同的数据类型;
- 可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值,且占用的空间是按成员中占用空间最大的那个来算的;
- 共用体变量同时使用会导致后面的覆盖前面的,使得前面的赋值出现损失,所以不要同时使用;
- 共用体的定义和使用和结构体是差不多的:
union Data
{
int i;
float f;
char str[20];
};
union Data haha;
haha.i = 10;
*******************************tips***********************************
- typedef 可以用来为内置的数据类型和自定义的数据类型取别名,定义之后使用该类型可以直接用别名替代;
- typedef 和 #define:
- typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名;
- typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的;
输入&输出
一、基本输入输出(需要标准库的支持<stdio.h>)
1、printf 和 scanf(格式化输出)
- scanf("%s %d", str, i) 期待你的输入的类型和需要的类型相同,否则有可能报错;
- scanf 遇到空格就会停止读取,所以 "this is test" 对 scanf 来说是三个字符串;
2、getchar 和 putchar(一个字符的输入和输出)
3、gets 和 puts(一行一行地读写,需要提供一个缓冲区)
char str[100];
gets( str );
puts( str );
二、文件读写
1、文件打开(fopen)
2、文件关闭(fclose)
3、文件写入(写字符:fputc 写字符串:fputs 和 fprintf)
4、文件读取(读字符:fgetc 读字符串:fgets 和 fscanf ----fgets 按行读取,而 fscanf 是以空格作为结束的)
5、二进制输入输出函数(fread 和 fwrite)
FILE * fp;
fp = fopen ("file.txt", "w+");
fprintf(fp, "%s %s %s %d", "We", "are", "in", 2014);
fclose(fp);
预处理器和头文件
一、预处理器和宏
1、二者的概念
- 预处理器就是一个文本替换的工具;
- C 语言中的宏就是实现文本替换功能的C代码;
2、预处理器指令和运算符
- 常见的预处理器指令如下:
- 运算符:续行符(\)、字符串常量化运算符(#)、标记粘贴运算符(##)、defined() 运算符
3、预定义宏和参数化宏
- 预定义宏常用的如下,不能直接修改:
- __DATE__: 当前日期,一个以 "MMM DD YYYY" 格式表示的字符常量
- __TIME__ :当前时间,一个以 "HH:MM:SS" 格式表示的字符常量
- __FILE__ :这会包含当前文件名,一个字符串常量
- 可以使用参数化的宏来模拟函数,必须要注意的是宏饿本质就是用来文本替换的,所以下面的实例中括号绝对不能少,少了可能出错:
int square(int x) {
return x * x;
}
可以用如下的来替换:
#define square(x) ((x) * (x))
二、头文件(这里的用法类似于java中的import)
1、基本语法
- #include <file> 引用系统头文件
- #include "file" 引用用户头文件
2、只引用一次
#ifndef HEADER_FILE
#define HEADER_FILE
int max(int x,int y){return x;}
#endif
3、条件引用
#if SYSTEM_1
# include "system_1.h"
#elif SYSTEM_2
# include "system_2.h"
#elif SYSTEM_3
...
#endif
错误-递归-可变参-内存管理-命令行参数-排序算法
一、错误处理
1、C 语言没有提供类似 java 的错误处理的机制,只能在错误发生的时候输出一些错误的信息
2、错误信息的输出
- 在发生错误时,大多数的 C 或 UNIX 函数调用返回 1 或 NULL,同时会设置一个错误代码 errno,该错误代码是全局变量,表示在函数调用期间发生了错误;
- C 语言提供了 perror() 和 strerror() 函数来显示与 errno 相关的文本消息
#include <stdio.h>
#include <errno.h>
#include <string.h>
extern int errno ;
int main ()
{
FILE * pf;
int errnum;
pf = fopen ("unexist.txt", "rb");
if (pf == NULL)
{
errnum = errno;
fprintf(stderr, "错误号: %d\n", errno);
perror("通过 perror 输出错误");
fprintf(stderr, "打开文件错误: %s\n", strerror( errnum ));
}
else
{
fclose (pf);
}
return 0;
}
3、程序的退出状态:程序正常退出exit(0);程序发生异常exit(-1);
二、递归
- 1、所谓的递归就是函数自己调用自己,最经典的例子就是斐波那契数列的实现;
- 2、递归一定要有退出条件,不然会形成死循环,导致程序出错;
- 3、递归的次数不可过多,次数过多的话效率十分低下,还容易导致栈的溢出;
#include <stdio.h>
int fibonaci(int i)
{
if(i == 0)
{
return 0;
}
if(i == 1)
{
return 1;
}
return fibonaci(i-1) + fibonaci(i-2);
}
int main()
{
int i;
for (i = 0; i < 10; i++)
{
printf("%d\t\n", fibonaci(i));
}
return 0;
}
三、可变参(参数的数量不定)
1、可变参的使用要依赖于 C 的标准库:#include<stdarg.h>
- 库变量:va_list(参数列表)
- 库函数:va_arg(访问参数的某个项)、va_start(初始化valist)、va_end(清理valist内存)
2、详细使用示例
#include <stdio.h>
#include <stdarg.h>
double average(int num,...)
{
va_list valist;
double sum = 0.0;
int i;
/* 为 num 个参数初始化 valist */
va_start(valist, num);
/* 访问所有赋给 valist 的参数 */
for (i = 0; i < num; i++)
{
sum += va_arg(valist, int);
}
/* 清理为 valist 保留的内存 */
va_end(valist);
return sum/num;
}
int main()
{
printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}
四、内存管理
1、可变参的使用要依赖于 C 的标准库:#include<stdlib.h>
- 动态分配内存:calloc(int num,int size)、malloc(int num)
- 释放内存:free(void *address)
- 重新分配内存:realloc(void *address, int newsize)
2、详细使用示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char name[100];
char *description;
strcpy(name, "Zara Ali");
/* 动态分配内存 */
description = malloc( 30 * sizeof(char) );
//description = calloc(30,sizeof(char));
if( description == NULL )
{
fprintf(stderr, "Error - unable to allocate required memory\n");
}
else
{
strcpy( description, "Zara ali a DPS student.");
}
/* 假设您想要存储更大的描述信息 */
description = realloc( description, 100 * sizeof(char) );
if( description == NULL )
{
fprintf(stderr, "Error - unable to allocate required memory\n");
}
else
{
strcat( description, "She is in class 10th");
}
printf("Name = %s\n", name );
printf("Description: %s\n", description );
/* 使用 free() 函数释放内存 */
free(description);
}
五、命令行参数
- int(int,char*argv[]):argc 是指传入参数的个数,argv[] 是一个指针数组,指向传递给程序的每个参数;
- argv[0] 存储程序的名称,argv[1] 是一个指向第一个命令行参数的指针;
- 多个命令行参数之间用空格分隔,如果参数本身带有空格,应把参数放置在双引号或单引号内部;
六、排序(几种常见排序的 C 代码练习)
七、标准库参考:https://www.runoob.com/cprogramming/c-standard-library.html