基础
第一个函数
argc代表参数个数argument count。argv代表参数value,第一个为放的是文件名,后面是传入的参数。
编译过程
- 预处 :
gcc -E hello.c -o hello.i
- 编译 :
gcc -S hello.c(.i) -o hello.s
- 汇编 :
gcc -c hello.c -o hello.o
- 连接 :
gcc hello.o -o hello
- 一步到位:
gcc hello.c -o hello
生成文件
存储类
auto:用完即丢。
static:本文件的全局变量。
extern:只声明,不定义,引用外部变量。
register:放在寄存器而不是内存。
c
//auto
{
auto int month; // 等于int mount;
}
//register
{
register int miles;
}
//static
static int count=10; /* 全局变量 - static 是默认的 */
int main(){
}
extern实例
c
//文件add.cpp
extern int x;
extern int y;
int add(){
return x+y;
}
//文件test.cpp
#include <stdio.h>
int x = 2;
int y = 5;
extern int add(); //不用extern也可以
int main(){
int z = add();
printf("%d\n",z);
}
//cmd下
$ gcc add.cpp test.cpp -o sum
//返回结果
$ sum
//区别extern
extern int a; // 声明一个全局变量 a
extern int a =0; // 定义一个全局变量 a 并给初值。一旦给予赋值,一定是定义,定义才会分配存储空间
全局变量static保存在内存的全局存储区堆中,占用静态的存储单元;
局部变量auto保存在栈中,只有在所在函数被调用时才动态地为变量分配存储单元。
常量
整数、浮点数、字符、字符串(字符串常量以'\0'或者null结尾)。
定义常量define或者const修饰:define只是进行文本替换,const用于只读变量,会为其分配内存(不会为常量分配内存),还可以捕获一些潜在的错误。所以建议使用const
c
3.14159 /* 合法的 */
314159E-5L /* 合法的 */
//浮点数==整数+小数。科学计数法==小数+指数
510E /* 非法的:不完整的指数 */
210f /* 非法的:没有小数或指数 */
.e55 /* 非法的:缺少整数或分数 */
//s1和s2相同
char *s1 = "hello, dear";
char *s2 = "hello, " "d" "ear";
数据类型
基本,指针,构造(数组、结构、枚举、共用),空。
整形数据根据编译环境的不同,所取范围不同。
浮点数
24位:符号+底数。8位:指数(偏移值为+127)。
c
##include<cfloat>头文件,知道原理
printf("%e\n",FLT_MIN) ; //1.175494e-038
printf("%e\n",FLT_MAX) ; //3.402823e+038
//二者均为常量9
int a = '\11';
int b = 011;
运算符
算数、逻辑、位、关系、赋值。杂运算符:sizeof()、&(取地址)、*(指向一个变量)、?:。
优先级:后缀((),->,++/--),单目(!/sizeof/++/--),算数,移位,大小关系,相等关系。位运算,逻辑运算。三元,赋值,逗号。
格式化输出
格式字符,转义字符,普通字符。格式修饰符。g和G?l和L?h?%.ns表示?%-m.nf表示什么意思?
枚举
枚举类型是被当做 int 类型来处理的,所以按照 C 语言规范是没有办法遍历枚举类型的。不过在一些特殊的情况下,枚举类型必须连续是可以实现有条件的遍历。
c
//不可以遍历
enum
{
ENUM_0, ENUM_10 = 10, ENUM_11
};
//可以遍历。默认MON是0,但是这里赋值为1
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
}day;
int main()
{
// 遍历枚举元素
for (day = MON; day <= SUN; day++) {
printf("枚举元素:%d \n", day);
}
}
指针
指针的含义。&
和*
函数指针
函数指针:一个指向函数的指针int (*p)(int,int) = fun
c
//函数指针
int max(int a,int b){ return a>b?a:b; }
int main(){
int (*p)(int,int) = &max;//&可以不写
p(1,2); //等价max(1,2)
return 0;
}
回调函数
populate_array() 将调用 10 次回调函数,并将回调函数的返回值赋值给数组。
c
void populate_array(int *array,size_t length,int (*getValue)()){
for(int i=0 ; i<length ; i++){
*(array+i) = getValue(); //这里
}
}
int getRandom(){ return rand(); } //回调函数
int main(){
int array[10];
//getRandom不能加括号否则无法编译,因为加上括号之后相当于传入此参数时传入了 int,而不是函数指针
populate_array(array,10,getRandom);
//此时for输出array
return 0;
}
//size_t 是一种数据类型,近似于无符号整型,但容量范围一般大于 int 和 unsigned。这里使用 size_t 是为了保证 arraysize 变量能够有足够大的容量来储存可能大的数组。
字符串
\0
是用于标记字符串的结束
c
#include <string.h>
char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};
char site[] = "RUNOOB";
char site[6] = {'R', 'U', 'N', 'O', 'O', 'B'};//如果当作字符串输出会乱
strcpy(s1, s2); //复制字符串 s2 到字符串 s1。
strcat(s1, s2); //连接字符串 s2 到字符串 s1 的末尾。catenate
strlen(s1); //返回字符串 s1 的长度。
strcmp(s1, s2); //如果 s1 和 s2 是相同的则返回0如果 s1<s2 则返回小于0;如果 s1>s2 则返回大于 0。
strchr(s1, ch); //返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
strstr(s1, s2); //返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。
strupr(s1);
strlwr(s1);
结构体
定义结构体:
c
#1
//声明了结构体变量s1
struct{
int a;
char b;
double c;
} s1;
#2
struct SIMPLE{
int a;
char b;
double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
#3
//也可以用typedef创建新类型
typedef struct Simple2{
int a;
char b;
double c;
};
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;
struct Books{
char title[50];
char author[50];
char subject[100];
int book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};
实例:
c
struct Person {
char name[20];
int age;
float height;
};
int main() {
struct Person person;
printf("结构体 Person 大小为: %zu 字节\n", sizeof(person));
return 0;
}
使用 sizeof 运算符来获取 person 结构体的大小,结构体的大小可能会受到编译器的优化和对齐规则的影响。了解结构体的内存布局和对齐方式,可以使用 offsetof
宏和 _attribute_((packed))
属性等进一步控制和查询结构体的大小和对齐方式。
共用体
定义
c
union Data
{
int i;
float f;
char str[20];
} data1;
union Data data2; //sizeof(data2) = 20,会采用共用体中最大的元素作为对象的大小
如果其中对象的一个元素被赋值,其他的元素都会受到损害。共用体用于多个元素只会用其中的一种情况,比如传递信息的包(只会传递一种包)。
位域
位域bit-field是结构体中特殊的成员,指定成员占用多少bit,位域的宽度不能超过其数据类型的大小。
c
/* 定义简单的结构 */
struct
{
unsigned int a;
unsigned int b;
} status1; //sizeof(status1)=8
/* 定义位域结构 */
struct
{
unsigned int a : 1; //指定a占用1bit,也就是a只能为0或者1
unsigned int b : 1; //指定b占用1bit
} status2; //sizeof(status2)=4
//一个位域存储在同一个字节中,如一个字节所剩空间不够存放另一位域时,则会从下一单元起存放该位域。也可以有意使某位域从下一单元开始。
struct
{
char a : 2;
char b : 3;
char c : 3;
} status3; //sizeof(status3)=1
//位域可以是无名位域,这时它只用来作填充或调整位置。无名的位域是不能使用的。
struct k{
int a:1;
int :2; /* 该 2 位不能使用 */
int b:3;
int c:2;
};
结构体内存分配原则
原则一:元素根据自己的整数倍进行存储。
原则二:struct的大小为元素(最占空间)的倍数。
typedef
自定义类型。
c
//C语言中没有byte的数据类型
typedef unsigned char byte;
byte a = 1;
typedef struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} Book;
Book book;
typedef区别#define:(typedef定义类型,define文本替换)
- typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
- typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。
c
//#define可以使用其他类型说明符对宏类型名进行扩展,但对 typedef 所定义的类型名却不能这样做。
#define INTERGE int;
unsigned INTERGE n; //没问题
typedef int INTERGE;
unsigned INTERGE n; //错误,不能在 INTERGE 前面添加 unsigned
#define PTR_INT int *
PTR_INT p1, p2; //p1、p2 类型不相同,宏展开后变为int *p1, p2;
typedef int * PTR_INT
PTR_INT p1, p2; //p1、p2 类型相同,它们都是指向 int 类型的指针。
输入 & 输出
int scanf(const char *format, ...)与int printf(const char *format, ...)
int getchar(void)与int putchar(int c)
**char *gets(char s)与 int puts(const char s)
C 语言把所有的设备都当作文件。所以设备(比如显示器)被处理的方式与文件相同。以下三个文件会在程序执行时自动打开,以便访问键盘和屏幕。
标准文件 | 文件指针 | 设备 |
---|---|---|
标准输入 | stdin | 键盘 |
标准输出 | stdout | 屏幕 |
标准错误 | stderr | 您的屏幕 |
c
#1:
int getchar(void); //从屏幕读取下一个可用的字符,并把它返回为一个整数。这个函数在同一个时间内只会读取一个单一的字符。您可以在循环内使用这个方法,以便从屏幕上读取多个字符。
int putchar(int c); // 函数把字符输出到屏幕上,并返回相同的字符。这个函数在同一个时间内只会输出一个单一的字符。您可以在循环内使用这个方法,以便在屏幕上输出多个字符。
int c = getchar();
putchar(c);
#2:
char *gets(char *s); //函数从 stdin 读取一行到 s 所指向的缓冲区,直到一个终止符或 EOF。读取的换行符被转换为null值,做为字符数组的最后一个字符,来结束字符串。gets函数由于没有指定输入字符大小,如果很大会越界。
int puts(const char *s); //函数把字符串 s 和一个尾随的换行符写入到 stdout。
char str[100];
gets(str);
puts(str);
#3:
scanf() 和 printf() 函数
#4:
fgets函数原型:char *fgets(char *s, int n, FILE *stream);我们平时可以这么使用:fgets(str, sizeof(str), stdin);取代gets(),原因gets()易越界
char str[5];
fgets(str, sizeof(str), stdin);
fputs(str,stdout);
文件读写
FILE *fopen( const char *filename, const char *mode );
r | 打开一个已有的文本文件,允许读取文件。 |
---|---|
w | 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。 |
a | 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。 |
r+ | 打开一个文本文件,允许读写文件。 |
w+ | 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。 |
a+ | 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。 |
如果是二进制"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"。
函数介绍:(错误返回EOF)
c
int fputc( int c, FILE *fp );
int fputs( const char *s, FILE *fp );
int fprintf(FILE *fp,const char *format, ...)
int fgetc( FILE * fp );
char *fgets( char *buf, int n, FILE *fp ); //读取n-1,因为最后要有'\0'。遇到换行即停
int fscanf(FILE *fp, const char *format, ...) //遇到空格换行即停。
实验
c
#include <stdio.h>
int main()
{
FILE *fp = NULL;
fp = fopen("C:\\tmp\\test.txt", "w+");
fprintf(fp, "This is testing for fprintf...\n");
fputs("This is testing for fputs...\n", fp);
fclose(fp);
}
#include <stdio.h>
int main()
{
FILE *fp = NULL;
char buff[255];
fp = fopen("C:\\tmp\\test.txt", "r");
fscanf(fp, "%s", buff);
printf("1: %s\n", buff );
fgets(buff, 255, (FILE*)fp);
printf("2: %s\n", buff );
fgets(buff, 255, (FILE*)fp);
printf("3: %s\n", buff );
fclose(fp);
}
//输出
1: This
2: is testing for fprintf...
3: This is testing for fputs...
实验
int fseek(FILE *stream, long offset, int whence);
文件中光标偏移
whence有
c
#include <stdio.h>
int main(){
FILE *fp = NULL;
fp = fopen("test.txt", "r+"); // 确保 test.txt 文件已创建
fprintf(fp, "This is testing for fprintf...\n");
fseek(fp, 10, SEEK_SET);
if (fputc(65,fp) == EOF) {
printf("fputc fail");
}
fclose(fp);
}
#打开test.txt
This is teAting for fprintf...
预处理器
C Preprocessor简写为CPP
指令 | 描述 |
---|---|
#define | 定义宏 |
#include | 包含一个源代码文件 |
#undef | 取消已定义的宏 |
#ifdef | 如果宏已经定义,则返回真 |
#ifndef | 如果宏没有定义,则返回真 |
#if | 如果给定条件为真,则编译下面代码 |
#else | #if 的替代方案 |
#elif | 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码 |
#endif | 结束一个 #if......#else 条件编译块 |
#error | 当遇到标准错误时,输出错误消息 |
#pragma | 使用标准化方法,向编译器发布特殊的命令到编译器中。pragma"注释" |
实例
c
//取消定义,并重新定义
#undef FILE_SIZE
#define FILE_SIZE 42
//ifdef---endif
#ifndef MESSAGE
#define MESSAGE "You wish!"
#endif
内置的宏定义
c
#include <stdio.h>
main()
{
printf("File :%s\n", __FILE__ );
printf("Date :%s\n", __DATE__ );
printf("Time :%s\n", __TIME__ );
printf("Line :%d\n", __LINE__ ); //__LINE__当前行号
printf("ANSI :%d\n", __STDC__ ); //__STDC__,当编译器以ANSI编译器时,为1
}
实例
c
//'\'表示宏延续运算符,'#'字符串常量化运算符。宏定义中printf如果被大括号括,则需要在末尾有分号
#define message_for(a, b) \
printf(#a " and " #b ": We love you!\n")
int main(void){
message_for(Carole, Debra);
return 0;
}
//输出
Carole and Debra: We love you!
//'##'标记粘贴运算符
#define tokenpaster(n) printf ("token" #n " = %d", token##n)
int main(void){
int token34 = 40;
tokenpaster(34);
return 0;
}
头文件
include <> 和 include ""区别。
有条件引用
c
#if SYSTEM_1
# include "system_1.h"
#elif SYSTEM_2
# include "system_2.h"
#elif SYSTEM_3
...
#endif
//需要指定在不同的操作系统上使用的配置参数。
#if SYSTEM_1
# include "system_1.h"
#elif SYSTEM_2
# include "system_2.h"
#elif SYSTEM_3
...
#endif
//有条件引用
#define SYSTEM_H "system_1.h"
...
#include SYSTEM_H
//global.h包含所有头文件,以后直接引用global.h即可
#ifndef _GLOBAL_H
#define _GLOBAL_H
#endif
#include <fstream>
#include <iostream>
#include <math.h>
强制类型转换
char/short → int → unsigned → long → double ← float
错误处理
errno、perror()、strerror()、可以直接使用 stderr 文件流
c
#include <stdio.h>
#include <errno.h>
#include <string.h>
extern int errno;
int main(){
FILE *fp = fopen("unexist.ext","rb");
if(fp == NULL){
perror("打开错误\n");
fprintf(stderr,"---%s",strerror(errno));
}else{
fclose(fp);
}
return 0;
}
//输出
打开错误
: No such file or directory
---No such file or directory
实例
c
//退出程序时返回状态。EXIT_FAILURE和EXIT_SUCCESS是宏定义,分别是-1和0
#include <stdio.h>
#include <stdlib.h>
main()
{
int dividend = 20;
int divisor = 5;
int quotient;
if( divisor == 0){
fprintf(stderr, "除数为 0 退出运行...\n");
exit(EXIT_FAILURE);
}
quotient = dividend / divisor;
fprintf(stderr, "quotient 变量的值为: %d\n", quotient );
exit(EXIT_SUCCESS);
}
可变参数
实例
c
va_list ; //stdarg.h中的类型
va_start(ap, last_arg) //ap为va_list,last_arg表示最后一个可变参数。ap指向可变参数的第一个元素
va_arg(ap, type) //可变参数的下一个参数
va_end(ap) //结束,ap置空
#include <stdio.h>
#include <stdarg.h>
float fun_average(int num,...){
va_list valist;
va_start(valist,num);
float sum = 0;
for(int i=0; i<num ; i++){
sum += va_arg(valist,int);
}
va_end(valist);
return sum/num;
}
int main(){
printf("%f\n",fun_average(3,5,6,7));
printf("%f\n",fun_average(4,5,6,7,8));
return 0;
}
内存管理
函数均在stdlib.h文件中
1 | void *calloc(int num, int size); 在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是 0。c表示clear |
---|---|
2 | void free(void *address); 该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。 |
3 | void *malloc(int num); 在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。 |
4 | void *realloc(void *address, int newsize); 该函数重新分配内存,把内存扩展到 newsize。 |
实例
c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(){
char *p;
p = (char*)malloc(5*sizeof(char));
if(p == NULL){
fprintf(stderr,"---1stderr");
}else{
strcpy(p,"1234");
}
printf("%s\n",p);
p = (char*)realloc(p,10*sizeof(char));
if(p==NULL){
fprintf(stderr,"---2stderr");
}else{
strcat(p,"12345");
}
printf("%s\n",p);
free(p);
return 0;
}
命令行参数
c
#include <stdio.h>
int main( int argc, char *argv[] )
{
if( argc == 2 )
{
printf("The argument supplied is %s\n", argv[1]);
}
else if( argc > 2 )
{
printf("Too many arguments supplied.\n");
}
else
{
printf("One argument expected.\n");
}
}
//执行。argv[0]存储程序的名字,argv[1]存储第一个输入的参数
$./a.out testing
The argument supplied is testing
$./a.out testing1 testing2
Too many arguments supplied.
$./a.out
One argument expected
$./a.out "testing1 testing2"
Progranm name ./a.out
The argument supplied is testing1 testing2
排序
- 冒泡排序:略
- 选择排序:略
- 插入排序:略
- 希尔排序:
有待解决
例子
基础
C
#两整数相除任然是整数。
printf("%f",(float)(10/3)); //3.000000
int *ptr; //ptr:整型指针变量
//后缀可以是大写,也可以是小写,U 和 L 的顺序任意。
0xFeeL /* 合法的 */
0x4b /* 十六进制 */
30u /* 无符号整数 */
30l /* 长整数 */
30ul /* 无符号长整数 */
#'\101'等价于0101
'\101', '\x41', 'A' //三者等价
变量修饰符
c
//用循环函数检测使用时间
time_t start,end;
time(&start);
fori;
time(&end);
printf("%ld",start-end);
函数
c
#add.c
#include <stdio.h>
/*外部变量声明*/
extern int x ;
extern int y ;
int add()
{
return x+y;
}
#test.c
#include <stdio.h>
/*定义两个全局变量*/
int x=1;
int y=2;
int add();
int main()
{
printf(" %d\n",add());
return 0;
}
拓展
编码
- ascii编码规则?常用字符?
- ANSI编码即扩展ascii编码。GB2312(94*94)三种编码方式、GBK用剩余的空间再进行编码、Big-5、JIS
- utf-8编码规则?
格式化输出
- 整形输出:%ld %o %x %u %5d %05d %-5d
- 小数输出:%20.15f %-20.15f %e %g
- 字符输出:%5c
- 指针输出:%p
- 格式修饰符:l、 L、h、m.n、-
visual stdio(vs)和vs code
两者都是microsoft下
vs:通俗的说是一个编译器。不可跨平台,是IDE,可以编译很多高级语言。
vs code:通俗的说是一个编辑器。可跨平台,超级文本编辑器。
vc(visual C++):编译器,只用于C/C++。
问题
- 静态(static)和外部(extern)类型的数组元素初始化元素为0,自动(auto)类型的数组的元素初始化值不确定。
/三者等价
变量修饰符
```c
//用循环函数检测使用时间
time_t start,end;
time(&start);
fori;
time(&end);
printf("%ld",start-end);
函数
c
#add.c
#include <stdio.h>
/*外部变量声明*/
extern int x ;
extern int y ;
int add()
{
return x+y;
}
#test.c
#include <stdio.h>
/*定义两个全局变量*/
int x=1;
int y=2;
int add();
int main()
{
printf(" %d\n",add());
return 0;
}
拓展
编码
- ascii编码规则?常用字符?
- ANSI编码即扩展ascii编码。GB2312(94*94)三种编码方式、GBK用剩余的空间再进行编码、Big-5、JIS
- utf-8编码规则?
格式化输出
- 整形输出:%ld %o %x %u %5d %05d %-5d
- 小数输出:%20.15f %-20.15f %e %g
- 字符输出:%5c
- 指针输出:%p
- 格式修饰符:l、 L、h、m.n、-
visual stdio(vs)和vs code
两者都是microsoft下
vs:通俗的说是一个编译器。不可跨平台,是IDE,可以编译很多高级语言。
vs code:通俗的说是一个编辑器。可跨平台,超级文本编辑器。
vc(visual C++):编译器,只用于C/C++。
问题
- 静态(static)和外部(extern)类型的数组元素初始化元素为0,自动(auto)类型的数组的元素初始化值不确定。