c语言

准备

目标:快速学习c语言
编程工具:腾讯在线编译器 Cloud Studio 官网
条件:具备其他编程语言的基础

Clould Studio的使用

① 进入上面的网址
② 注册绑定完完账号,点击产品,点击Cloud Studio
③ 新建一个文件夹(可省略此步骤),新建一个文件,必须以.c结尾


Cloud Studio设置图1.png

④ 完成语言的编写后,在控制台输入 gcc xxx.c 回车,再次输入./a.out 回车,即可运行你的c语言


Cloud Studio设置图2.png

C语言的应用

① 操作系统
② 语言编译器
③ 汇编器
④ 文本编辑器
⑤ 打印机
⑥ 网络驱动器
⑦ 现代程序
⑧ 数据库(MySQL)
⑨ 语言解释器
⑩ 实体工具

你好!世界!

#include <stdio.h>

int main() {

  printf("123");       /* 格式化输出 */
  return 0;           /*退出程序
}

- 程序的第一行是预处理命令,告诉程序编译之前要包含stdio.h文件
- 下一行是主函数main,函数从这里开始执行
- 注释使用 /* xxxx */

C的令牌和规则

printf("123");

1. c的令牌可以是关键字,标识符,常量,字符串值,或者是一个符号
2. 这5个令牌分别是:
printf
(
"123"
)
;

3.c的注释
//单行注释
/* 单行注释 */
/*
 多行注释
 多行注释
 多行注释
*/

4.c的标识符
不能带标点字符,如@,%,#这些,而且c是区分大小写的

C的关键字

//关键字不能作为常量名,方法名或者其他标识符名称

auto   //声明自动变量
break   //跳出当前循环
case   //开关语句分支
char   //声明字符变量或者返回值类型
const   //声明只读变量
continue //结束当前循环进行下一轮循环
default   //开关语句分支的默认分支
do   //循环语句的循环体
double //双精度浮点数,和char都是小数类型,但它的准确度更高
else   //否定分支,与if连用
enum   //声明枚举类型
extern   //声明变量或者函数是在是它文本的位置定义
float   //单精度浮点数
for   //一种循环语句
goto   //无条件跳转语句
if   //条件语句
int   //整型
long   ///长整型
register   //声明寄存器变量
return   //返回语句,可以带参数,也可以不带
short   //短整型
signed   //有符号变量
sizeof   //计算数据类型或者变量长度(即所占字节数)
static   //声明静态变量
struct   //声明结构体类型
switch   //switch...case循环语句
typedef   //用于给数据类型起别名
unsigned   //声明无符号类型
union   //声明共用体类型
void   //声明无返回值,无指针类型
volatile   //说明变量在程序执行中被隐含的改变
while   //while循环关键字

//为什么我这里罗列出来是因为可以对c的语法有一个大概的印象

C的数据类型

① 在 C 语言中,数据类型指的是用于声明不同类型的变量或函数的一个广泛的系统。变量的类型决定了变量存储占用的空间,以及如何解释存储的位模式。

C中的数据类型可以分为以下几类:
基本类型:整数类型 + 浮点型
枚举类型
void类型:表示无可用值
派生类型:指针类型、数据类型、结构类型、共用体类型、函数类型
数组类型和函数类型叫做集合类型,函数类型指的是函数的返回值类型

整数类型

整数类型.png

//使用sizeof(xxx)关键字取得数据类型的字节数

#include <stdio.h>
#include <limits.h>

int main() {
    printf("int类型的字节数为:%lu \n",sizeof(int));   //取得int类型的字节数 ,输出结果为4
    return 0;
}

浮点类型

浮点类型.png

#include <stdio.h>  /*头文件*/
#include <limits.h>
#include <float.h>

int main() {

  printf("float最大值:%E \n",FLT_MAX);     //%E是指以指数形式输出单双精度浮点数
  printf("float最小值:%E \n",FLT_MIN);
  printf("精度值 %d \n",FLT_DIG);

  return 0;
}

//输出结果为:
//float最大值:3.402823E+38
//float最小值:1.175494E-38
//精度值 6 

void类型
① 函数的返回类型为空,如void exit(int status);
② 函数参数为空,如 int rand(void);
③ 指针指向void,代表对象的地址,如void *malloc(size_t size); 返回的是指向void的指针

c的变量

变量及其描述:


变量及其描述.png
//c中变量的定义和解释

#include <stdio.h>

int main() {

  char c = 'a';       //变量的声明
  float f = 123;
  double db = 666;

  extern int a;      //二者的区别在下方解释
  int b;              
                      
                      
  return 0;
}

//声明变量带extren和不带extern的却别
//extern不带是立即声明和建立存储空间,是声明和定以
//带的就是只声明不定义,没有建立相应的存储空间
//变量a又可以在别的文件中定义
//extern关键字的案例如下

#include <stdio.h>

int x;      //函数外定义变量x和y
int y;

int plus() {

  extern int x;  //声明为外部变量
  extern int y;

  x = 1;        //给外部变量赋值
  y = 4;

  return x+y;   //返回俩个变量之和
}

int main() {

  int res;
  res = plus(); //调用plus方法
  printf("res的结果为:%d",res);   //输出结果为5,正确。
  return 0;
}

//如果我们要在一个文件中引用另一个文件中的变量,要需要在这个变量前面加上***extern***即可

C的左值(LValues)和右值(RValues)
左值:指向内存位置,可出现在赋值号的左边和右边
右值:指的是存储到内存地址下的具体的数值,只能在赋值号右边存储
③ 如 10 = 20;,就是错误的语句,右值不能在左边

C常量

c的常量是固定值,在程序执行期间不会改变,又叫做字面量,常量定义后不可修改
① 以下是各种类型的整数常量的示例:


整型常量的格式.png

② 浮点类型的常量:


浮点类型常量的格式.png

③转义字符
转义字符表.png
#include <stdio.h>
#define indentifier value       //使用#define预处理器


#define LENGTH 20          //定义常量方式一,使用#define XXXX  value 的形式
#define WIDTH 5
#define NEWLINE '\n'

int main() {

  #define Ax 996

  printf("Hello\?\nWorld!");

  const int length = 30;   //定义常量方式二,使用const type xxxx = value 的形式,二者都行
  const int width = 100;
  const char c = '\n';

  int result;
  result = length + width;

  int area;
  area = LENGTH * WIDTH;

  printf("value of area:%c %d",NEWLINE,area);
  printf("result is:%c %d",c,result);
  printf("Ax value is: %d",Ax);

  return 0;

}
//方法一  #define xxx value
//方法二 const type xxx = value;

//以上输出结果为:
//Hello?
//World!value of area:
//100result is:
//130Ax value is: 996 
//%d是指以十进制有符号形式输出整数,正整数符号不显示

C的存储类

c的存储类的关键字是控制变量或者函数的可见性生命周期
auto
register
static
extern

#include <stdio.h>

void func1(void);         //定义一个方法
static int count = 10;   //全局静态变量

int main() {

{
  int mount;        //auto是所有局部变量默认的存储类        
  auto int month;   //auto只能用在函数内部,修饰局部变量
}

{
  register int miles;   //register存储类一般用于计数器,自身不能运用&运算符,因为无内存位置
}

  while(count--) {
    func1();
  }
  return 0;
}

void func1(void) {        //初始化方法

  static int thingy = 5;  //局部静态变量
  thingy++;
  printf("count is %d,thingy is %d \n",count,thingy);
}

//thingy使用static修饰后,不会在每次调用时重置
//auto是所有局部变量的存储类
//register一般用于计数器,无内存地址
//extern,当你想要取得一个已定义的变量或者函数的内存地址的时候,使用它。
//extern,相当于在另一个文件中声明全局变量或函数

C的运算符

算数运算符

算数运算符.png

#include <stdio.h>

int main() {
  int a = 10;
  int b = 3;
  int c;

  c = a + b;
  printf("a+b=: %d \n",a+b);
  c = a - b;
  printf("a-b=: %d \n",a-b);
  c = a * b;
  printf("a*b=: %d \n",a*b);
  c = a % b;
  printf("a求余b=: %d \n",a%b);
  c = a++;
  printf("a++ =: %d \n",c);
  c = a--;
  printf("a-- =: %d \n",c);

  return 0;
}

//输出结果为:
//a+b=: 13 
//a-b=: 7 
//a*b=: 30 
//a求余b=: 1 
//a++ =: 10    赋值后+1  c = 10 a = 11      
//a-- =: 11      赋值后-1   c = 11,a = 10
//++和--就是先赋值还是先计算自增自减的问题,在前先计算再赋值,在后,先赋值再计算

逻辑运算符

image.png

#include <stdio.h>

int main() {

  int a = 1;
  int b = 2;

  if(a && b) {
    printf("true\n");
  }

  if(a || b) {
    printf("true\n");
  }

  a = 0;
  b = 3;

  if(a && b) {
    printf("true\n");
  }else{
    printf("false\n");
  }

  if(a || b) {
    printf("true\n");
  }else{
    printf("false\n");
  }

  if(!a) {
    printf("a为0\n");
  }

  return 0;
}

//a && b =a和b都是不为0返回真
//a || b =a和b有一个不为0就返回真
//!取反操作

//输出的结果为:
//true
//true
//false
//true
//a为0

位运算符

二进制位操作.png

#include <stdio.h>
 
int main()
{
 
   unsigned int a = 60;    /* 60 = 0011 1100 */  
   unsigned int b = 13;    /* 13 = 0000 1101 */
   int c = 0;           
 
   c = a & b;       /* 12 = 0000 1100 */ 
   printf("Line 1 - c 的值是 %d\n", c );
 
   c = a | b;       /* 61 = 0011 1101 */
   printf("Line 2 - c 的值是 %d\n", c );
 
   c = a ^ b;       /* 49 = 0011 0001 */
   printf("Line 3 - c 的值是 %d\n", c );
 
   c = ~a;          /*-61 = 1100 0011 */
   printf("Line 4 - c 的值是 %d\n", c );
 
   c = a << 2;     /* 240 = 1111 0000 */
   printf("Line 5 - c 的值是 %d\n", c );
 
   c = a >> 2;     /* 15 = 0000 1111 */
   printf("Line 6 - c 的值是 %d\n", c );
}

//&& 操作有一个0就为0
//|| 操作有一个1就为1
//^ 相同即为0,其他都为1
//最后都是以十进制进行输出

赋值运算符


赋值运算符.png
#include <stdio.h>

int main() {
  int a = 20;
  int c;

  c = a;
  printf("Line1 - =:%d\n",c);
  c += a;
  printf("Line2 - +=:%d\n",c);
  c -= a;
  printf("Line3 - -=:%d\n",c);
  c *= a;
  printf("Line4 - *=:%d\n",c);
  c /= a;
  printf("Line5 - /=:%d\n",c);
  c %= a;
  printf("Line6 - 求余=:%d\n",c);
  c <<= a;
  printf("Line7 - <<=:%d\n",c);
  c >>= a;
  printf("Line8 - >>=:%d\n",c);
  c &= a;
  printf("Line9 - &=:%d\n",c);
  c |= a;
  printf("Line10 - |=:%d\n",c);
  c ^= a;
  printf("Line11 - ^=:%d\n",c);

  return 0;
}

//输出结果为:
//Line1 - =:20
//Line2 - +=:40
//Line3 - -=:20
//Line4 - *=:400
//Line5 - /=:20
//Line6 - 求余=:0
//Line7 - <<=:0
//Line8 - >>=:0
//Line9 - &=:0
//Line10 - |=:20
//Line11 - ^=:0

其他运算符

其他运算符.png

#include <stdio.h>

int main() {

  int a = 5;
  short b;
  double c;
  int* ptr;   //prt用于指向一个变量

  //sizeof 获得该数据类型的字节数
  printf("Line1 - int:%lu\n",sizeof(a));
  printf("Line2 - short:%lu\n",sizeof(b));
  printf("Line3 - double:%lu\n",sizeof(c));


  ptr = &a;     //&a 返回变量a的内存地址

  printf("a - = %d\n",a);
  printf("ptr - = %d\n",*ptr);  //ptr指向了一个内存地址,必须带上*号


  b = (a == 100)?123:125;  //三元运算符,用法和其他面向对象编程的方法一样
  printf("b的值为:%d",b);

  return 0;
}

//sizeof(type) sizeof(xxx)是取得某个数据类型或者变量的数据类型的字节数
//type* 使该变量成为一个指向内存地址的变量,使用是必须加上* xxx
//&xxx 取得某个变量的内存地址

//输出的结果为:
//Line1 - int:4
//Line2 - short:2
//Line3 - double:8
//a - = 5
//ptr - = 5
//b的值为:125  

c的运算符的优先级

运算符的优先级.png

一般我们只需要知道先乘除后加减即可,特殊需求可加()解决

C判断

#include <stdio.h>

int main() {
  int a = 25;
  int b = 30;
  int c = 35;
  int grade = 'a';

  if(a > 10) {                 /* c把非零和非空假定为true,为0或者为null假定为false*/  
    printf("a大于10\n");       /* if语句 */
  }

  printf("a的值为:%d\n",a);


  if(b > 20) {                      /* if-else语句 */
    printf("b的值大于20\n");
  }else{
    printf("b的值小于20\n");
  }
  printf("b的值为:%d\n",b);



  if(c < 5) {                     /*if-else if-else语句*/
    printf("c小于5\n");
  }else if(c>5 && c<=10) {
    printf("c在5~10之间\n");
  }else if(c>10 && c<=15) {
    printf("c在10~15之间\n");
  }else{
    printf("c大于15\n");
  }


  if(c>30) {                    /* if嵌套语句 */
    if(c == 35) {
      printf("c等于35\n");
    }
  }

  switch(grade) {                    /* switch-case循环语句 */
    case 'a':                        /* 条件分支的语法和c# java完全一样 */
      printf("优秀\n");
      break;
    case 'b':
      printf("一般\n");
      break;
    case 'c':
      printf("差\n");
      break;
    default:
      printf("未知成绩!\n");
      break;
  }


  return 0;
}

//基本语法和c# java类似,不再赘述

//输出结果为:
//a大于10
//a的值为:25
//b的值大于20
//b的值为:30
//c大于15
//c等于35
//优秀

C循环

C的循环基本和其他语言一样,它有一个无限循环是这样 for(;;) { 表达式 }
还有就是循环的控制语句:
break 跳出循环
continue 继续循环
goto 语句

#include <stdio.h>

int main() {

  int a = 4;                      

  while(a < 20) {                  /* While循环 */
    printf("a = %d \n",a);
    a++;
  }
  printf("while循环完成\n");



  for(int i = 0;i<5;i++) {         /* for循环 */
    printf("ABC\n");
  }
  printf("for循环结束\n");


 
  int d = 1;                      /* do while循环 */
  do {
    printf("one do-while loop~\n");
    d = d + 1;
  }while(d < 5);


  for( ; ; ){                     /* 无限循环,按下ctrl + c可以终止循环,注意分号之间的空格必须有 */
    printf("无限循环\n");
  }

  return 0;
}


//基本循环的语法和面向对象的语言基本一样,不再赘述。

C函数

C 标准库提供了大量的程序可以调用的内置函数。例如,函数 strcat() 用来连接两个字符串,函数 memcpy() 用来复制内存到另一个位置。
函数的四个主体:
return_type function_name( parameter list )
{
body of the function
}
return_type 返回值类型,如void
function_name 方法名
parameter 参数
body of the function 方法体

#include <stdio.h>

int max(int,int);         //声明一个方法

int main() {

  int a = 99;
  int b = 120;

  int c = max(a,b);                     //调用

  printf("结果为:%d\n",c);

  return 0;
}

int max(int num1,int num2) {    //定义max方法
  int result;

  if(num1 > num2) {
    result = num1;
  }else {
    result = num2;
  }

  return result;
}

//c与其他语言的不同就是他可以定义方法
//有点类似C#中的委托类型


//关于c的函数的参数
//其他编程语言,如c#,我们会知道值类型参数和引用参数
//c语言管他们叫做传值参数和引用参数
//传值相当于复制新值给函数的形式参数,不会影响原先的值
//而引用参数则是给的是参数的内存地址的指针,会改变原来的值


//输出结果为:
//结果为:120

C的作用域规则

全局变量:存储在内存的全局存储区中,占用静态的存储单元
局部变量:存储在栈中,只有函数被调用是才会动态的分配存储单元
形式参数:如果全局和局部变量的标识符一样,会采用局部变量(就近原则)
全局变量会自动初始化,局部变量则不会

默认初始化的值.png

C的数组

#include <stdio.h>

const int MAX = 4;

int main() {

  //数组的声明定义和遍历

  int nums[4];
  int i,j;

  for(i=0;i<MAX;i++) {
    nums[i] = i + 100;
  }

  for(j=0;j<MAX;j++) {
    printf("Line%d - %d\n",j,nums[j]);
  }

  printf("数组定义声明遍历End\n");               


  //多维数组
  //先行后列,访问多维数组时行和列的索引
  int numsArray[3][3] = {
    {3,6,9},
    {2,4,8},
    {6,6,6}
  };

  printf("2行2列的值为:%d\n",numsArray[1][1]);
  printf("多维数组End\n");

  return 0;
}

//这个和c#不同,c#是在数据类型后加[],而c语言是在变量名之后加[]
//c的一维和多维数组都是这样

C的枚举

#include <stdio.h>
#include <stdlib.h>

#define MON_DEF 1          /* 以#define形式声明变量 */
#define TUE_DEF 2
#define WED_DEF 3
#define THU_DEF 4
#define FRI_DEF 5
#define SAT_DEF 6
#define SUN_DEF 7


enum DAY {                /* 直接声明枚举 */
  MON=1,TUE,WED,THU,FRI,SAT,SUN
};

enum Color {              /* 声明枚举和变量 */
  RED = 1,
  GREEN,
  BLUE
}color;

enum Fruit {
  APPLE = 1,
  ORANGE,
  BANANA
};

int main() {

  enum DAY day;      /* 声明枚举变量 */
  day = SAT;
  printf("%d\n",day);


  color = GREEN;
  printf("%d\n",color);

  return 0;
}

//c的枚举的变量给在声明枚举类型是给出
//如果不指定枚举值,默认为0,下一个自动+1
//值类型转换为枚举类型需要加 (enum enumType) xxx;

//输出的结果为:
//6
//2

C指针

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

const int MAX = 3;
const int LEN = 10;

int main() {
  int a = 10;
  char c[10];
                                        //使用 &xxx 取得一个变量的内存地址  %p表示输出内存地址
  printf("a的内存地址为 %p\n",&a);       //输出 a的内存地址为 0x7ffd9484e96c
  printf("b的内存地址为 %p\n",&c);       //输出 b的内存地址为 0x7ffd9484e970


  //声明指针 type *var_name;
  //每一个内存地址都是十六进制
  int *azz;
  float *fzz;
  double *dzz;
  char *ch;


  //使用指针输出内存地址和对应的值
  int var = 10;
  int *izz;

  izz = &var;

  printf("var的指针内存地址为: %p\n",izz);         //输出 var的指针内存地址为: 0x7ffc6c88fb04
  printf("var的指针的值为: %d\n",*izz);            //输出 var的指针的值为: 10



  //空指针赋值
  int *nzz = NULL;
  printf("nzz的指针内存地址为: %p\n",nzz);         //输出 nzz的指针内存地址为: (nil)
  
  //检测指针为空或者不为空
  if(nzz) {
    printf("nzz不为空\n");
  }
  if(!nzz) {
    printf("nzz为空\n");       //nzz为空 
  }




  //指针的算数运算
  //递增一个指针
  int arr[] = {11,33,99};
  int *ptr;

  ptr = arr;                             //默认会赋值第一个数组的元素,可以不加&符号

  for(int i=0;i<3;i++) {
      printf("地址为: %p\n",ptr);        //会分别输出他们的指针地址和值
      printf("值为: %d\n",*ptr);
      ptr++;
  }
  printf("递增指针End!\n");


  //递减一个指针
  int otherArr[] = {777,666,777};        //取最后一个数组元素的指针,必须加上&符号
  int *oazz;

  oazz = &otherArr[MAX-1];

  for(int i=0;i<MAX;i++) {
      printf("地址为: %p\n",oazz);        //会分别输出他们的指针地址和值
      printf("值为: %d\n",*oazz);
      oazz--;
  }
  printf("递减去指针End!\n");

  
  //指针的比较
  int myArr[] = {10,20,200};
  int *head;
  int *end;

  head = myArr;
  end = &myArr[MAX-1];

  while(head < end) {
      printf("myArr元素的指针的内存地址为: %p\n",head);
      printf("myArr元素的值为: %d\n",*head);
      head++;
  }

  printf("指针比较End!\n");



  //指针数组
  int mineArr[] = {5,2,0};
  int *zzArr[MAX];


  for(int i=0;i<MAX;i++) {
      zzArr[i] = &mineArr[i];            //指针也可以有数组,指针数组需要对每一个指针进行赋值
  }

  for(int i=0;i<MAX;i++) {
      printf("指针数组里的值为: %d\n",*zzArr[i]);
  }

  printf("指针数组End!\n");





  //指向指针的指针
  int simpleValue = 998;
  int *szz;
  int **szzzz;


  szz = &simpleValue;
  szzzz = &szz;                             //定义指向指针的指针需要再加一个*号,例如 **xxxx,使用时也要加上俩个*号

  printf("simpleValue的值为: %d\n",simpleValue);
  printf("指针szz所指向的值为: %d\n",*szz);
  printf("指针szzzz所指向的值为: %d\n",**szzzz);

  printf("指针的指针End\n");




  //从函数返回指针

  //定义返回指针的函数 int * xxxx() { ... }
  int * getRandom() {
      static int r[10];

      for(int i=0;i<LEN;i++) {
          r[i] = rand();
      }
      return r;
  }

  int *g;

  g = getRandom();

  for(int i=0;i<LEN;i++) {
      printf("随机的数为: %d\n",*(g+i));     //遍历数组的指针需要 +i 例如*(g+i)
  }
  printf("从函数返回指针End\n");


  return 0;
}

//难点和易忘的点是,遍历数组的指针时要记得更新指针,如上边的*(g+i)
//还有就是返回指针的函数的定义,int * xxx() {。。。}

C函数指针和回调函数

关于回调函数的一段话:
以下是自知乎作者常溪玲的解说:
你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {

  //c的函数指针
  int max(int x,int y) {
    return x>y?x:y;
  }

  int a,b,c,d;
  int (* m)(int,int) = &max;         //函数的指针的声明方式为 返回值 (* xxx)(参数类型,参数类型,参数类型,...)
                                     //使用时就是用这个你定义的标识符即可

  printf("请输入三个数~\n");
  scanf("%d %d %d",&a,&b,&c);

  d = m(m(a,b),c);
  printf("d的值为: %d\n",d);
  printf("函数指针End\n");




  //c回调函数
  int getRandomValue(void) {        //没有参数可以使用void表示,也可以省略
    return rand();
  }

  void MyMethod(int *array,int size,int (* getRandom)(void)) {  //参数需要传入一个函数指针,作为回调函数
    for(int i=0;i<size;i++) {
      array[i] = getRandom();
    }
  }

  int myArr[3];
  int size = 3;

  MyMethod(myArr,size,getRandomValue);

  for(int i=0;i<size;i++) {
    printf("myArr Result: %d\n",myArr[i]);
  }
  printf("回调函数End\n");

  return 0;
}

//这个功能就相当于c#的委托 delegate

C字符串

#include <stdio.h>
#include <string.h>        

int main() {

  //声明字符串
  char h1[6] = {'h','e','l','l','o','\0'};    //声明时必须致指定size的大小
  char h2[] = "hello2"; 

  printf("h1: %s\n",h1);
  printf("h2: %s\n",h2);


  //字符串的一些方法
  char newStr[5];
  strcpy(newStr,h2);                             //copy方法   strcopy(s1,s2);  将s2复制到s1
  printf("newStr的值为: %s\n",h2);

  char s1[] = "hello,";
  char s2[] = "world!";
  strcat(s1,s2);
  printf("s1的值为:%s\n",s1);                   //concat方法  strcat(s1,s2);  将s2连接到s1


  int length = strlen(s1);
  printf("连接后的字符串的长度为: %d\n",length);  //strlen方法  strlen(s1);     求s1字符串的长度


  char *zz_1;
  zz_1 = strchr(s1,'e');                          //strchr(s1,'x');  返回x字符在s1字符串第一次出现的指针地址
  printf("e在s1字符串首次出现的内存地址为: %p\n",zz_1);

  char *zz_2; 
  zz_2 = strstr(s1,"ell");                        //strstr(s1,s2);   返回s2字符串在s1字符串中第一次出现的位置
  printf("字符串ell在s1字符串中首次出现的内存地址为: %p\n",zz_2);

  return 0;
}

//使用字符串相关的方法必须引入  <string.h>的头文件

C结构体

需要注意一下位域的定义和位域变量还有位域成员的运算

#include <stdio.h>
#include <string.h>

//声明枚举方法1
struct Book{
  char title[50];
  char author[50];
  char subject[50];
  int book_id;
}book;


//声明枚举方法2
struct Simple1 {
  int a;
  short b;
  float c;
};

//声明枚举方法3,带有多个变量
struct Simple2 {
  int a;
  short b;
  float c;
} s1,s2,s3;


//声明枚举类型方法4,使用typedef + struct关键字
typedef struct {
  int a;
  short b;
  float c;
} Simple3;


//使用枚举变量声明
struct Simple2 t1,t2[20],*t3;        //使用struct xxxx{} 构造的结构体,声明变量时必须带上struct关键字
Simple3 t4,t5[20],*t6;               //使用typedef struct {}xxxx; 构造的结构体,声明变量不带struct关键字


//结构体可以包含其他结构体
struct Example {
  int a;
  int b;
  struct Simple2 c;
};

//结构体可以包含自身的指针地址
struct Example1 {
  int a;
  int b;
  struct Example1 *next_node;
};




//如果俩个结构体互相包含,那么对其中的一个结构体进行不完整声明
struct B b;

struct B {
  struct A *a; //结构体B包含结构体A的指针
};

struct A{
  struct B *b; //结构体A包含结构体B的指针
};



//结构体变量的初始化
struct MyBook {
  char title[50];
  char author[50];
  char subject[50];
  int book_id;
} myBook = {"C语言","C语言之父","编程语言",12313};


void MyPrint(struct MyBook mk);
void MyPrint2(struct MyBook *mk);

int main() {

  printf("myBook的作者是%s,属于%s科目\n",myBook.author,myBook.subject);


  //访问结构体成员并赋值
  struct MyBook book1;

  strcpy(book1.title,"c语言入门到精通");
  strcpy(book1.author,"知名大佬");

  printf("%s写了一本%s\n",book1.author,book1.title);


  //结构体还可用作参数
  MyPrint(book1);

  //结构体指针参数
  MyPrint2(&book1);


  return 0;
}


//位域的定义和位域变量
//8bit = 1byte
struct bs {
  int a:8;
  int b:2;
  int c:6;
}data;

struct bs2 {
  int a:4;
  int  :4;   //空域,不能使用,如果存储的空间不够,那就从c开始存储,不能大于8位
  int c:4;   //再存储只能从第二个字节开始
  int d:8;
}data2;

  //位与算符 &= |=   例如一个结构体的指针 ptr->a&=3; ptr->a&=1; 都是位于运算

//结构体作为参数
void MyPrint(struct MyBook mk) {
  printf("这本书叫%s\n",mk.title);
}

//结构体指针作为参数,访问结构体的成员需要加上 -> 符号
void MyPrint2(struct MyBook *mk) {
  printf("MyPrint2方法的书的作者是:%s\n",mk->author);
}

C共用体

它允许你在同一个内存地址上有不同类型的数据成员,但是只能给其中一个成员初始化,否则会出现数据损坏的情况

#include <stdio.h>
#include <string.h>

//共用体1
union Data {
  int i;
  float f;
  char c[20];
};

//共用体2
union Exmple {
  int i;
  int j;
  char ch[30];
};

int main() {

  union Data data;

  data.i = 20;
  data.f = 128.8;
  strcpy(data.c,"C语言学习中...");

  printf("i是: %d\n",data.i);              //会出现数据错误的情况,输出 i是: -1380980669
  printf("f是: %f\n",data.f);              //会出现数据错误的情况,输出 f是: -0.000000
  printf("最后的字符串的数据是: %s\n",data.c);  //数据输出正确, 输出 最后的字符串的数据是: C语言学习中...



  //如果想使数据正确输出的话,那么必须在初始化后立即使用,这也应了我们之前那句话只能对其中一个成员赋值

  union Exmple e;

  e.i = 55; 
  printf("完整的i是:%d\n",e.i);          //输出 55

  e.j = 66;
  printf("完整的j是:%d\n",e.j);          //输出 66

  strcpy(e.ch,"this is my test~");       //输出 this is my test~
  printf("完整的ch是:%s\n",e.ch);

  return 0;
}

C的位域

位域是一种更好利用内存空间的结构体

#include <stdio.h>
#include <string.h>

int main() {

  //声明位域的格式
  /* struct {
       type var_name:width;         //type是数据类型  width是位数宽度必须小于等于该类型的位数
     }
  */

  //这个结构占8字节
  struct {
    unsigned int i1;
    unsigned int i2;
  }e1;

  //这个结构占4字节
  struct {
    unsigned int i1:1;
    unsigned int i2:1;
  }e2;


  printf("e1的字节数为:%lu\n",sizeof(e1));         //%lu 输出的是32位无符号整数,输出8
  printf("e2的字节数为:%lu\n",sizeof(e2));         //%lu 输出的是32位无符号整数,输出4




  //尝试超过位域的限制位数
  struct {
    unsigned int num:3;
  }bitvar;


bitvar.num = 5;
printf("num的值为%d\n",bitvar.num);

bitvar.num = 7;
printf("num的值为%d\n",bitvar.num);

bitvar.num = 8;                        //这里说不能超过8,我们num的位的数量进行了限制,不能超过7
printf("num的值为%d\n",bitvar.num);     //去掉上面的限制 :3 我们就可以输出8了


return 0;

}

C的typedef和#define

typedef #define都相当于给我们一个自定义数据类型的功能

#include <stdio.h>
#include <string.h>

#define RIGHT_IDEA 1
#define ERROR_IDEA 0

int main() {

  // typedef 和 #define 关键字都相当于起别名的功能
  //typedef 是由编译器执行 
  //#define 则是预处理,后边不加分号

  typedef struct Books {
    char title[50];
    char author[50];
    char subject[50];
    int book_id;
  }books;



  books b1;

  strcpy(b1.title,"代码之美");
  strcpy(b1.author,"美国某个大佬");
  strcpy(b1.subject,"计算机语言");
  b1.book_id = 12312;

  printf("%s发明了一本%s,属于%s类书籍\n",b1.author,b1.title,b1.subject); //正确输出了一句话,美国某个大佬发明了一本代码之美,属于计算机语言类书籍

  printf("正确的想法是:%d\n",RIGHT_IDEA);     //输出 1
  printf("错误的想法是:%d\n",ERROR_IDEA);     //输出 0

  return 0;
}

//typedef是由编译器执行解释的
//#define是预处理命令,是由预编译器执行解释的

C的输入和输出

printf格式化输出符号表
%d 一般用于输出数字(十进制有符号整数)
%u 十进制无符号整数
%f 输出float浮点数
%s 输出字符串
%c 输出单个字符
%p 输出指针的值
%lu 32位无符号整数
%llu 64位无符号整数

#include <stdio.h>

int main() {

  //getchar();  putchar();    输入和输入的方法,只能限制在一个字符
  int c;

  printf("请输入一个字符:");
  c = getchar();

  printf("你的字符为:");
  putchar(c);
  printf("\n");


  //gets(char str[]));  puts(char str[]);  方法是输出输入一个字符串
  //这里编译器会提示错误,应该是不支持



  //scanf函数
  int nums;
  char strs[50];

  printf("请输入一个数和字符串:");
  scanf("%d %s",&nums,strs);             //期间的输入的间隔可以使用

  printf("\n");
  printf("您输入的数是%d字符串为%s\n",nums,strs);

  return 0;
}

//主要是学习了scanf()输入函数,他需要传递的是变量的指针
//getchar();
//putchar(char c);
//gets(char str[]);
//puts(char str[]);

C文件读写

C文件读写关键字


文件读写关键字.png
#include <stdio.h>

int main() {
    
    FILE *fp = NULL;

    fp = fopen("m_test.txt","w+");         //在Cloud Studio的文件目录下查找你的文件,可以发现成功的打印了
    fprintf(fp,"this is test1~\n");
    fputs("this is test2~\n",fp);  
    fclose(fp);

    printf("文件写入完成!\n");



    char buff[255];

    fp = fopen("m_test.txt","r");         //使用 fscanf(File *fp,formatChar,char ch); 读取数据,遇到第一个空格就会停止
    fscanf(fp,"%s",buff);
    printf("1: %s\n",buff);


    fgets(buff,255,fp);                   //第一次使用 fgets(char ch,int length,File *fp);  输出下一半 2:  is test1~
    printf("2: %s",buff);


    fgets(buff,255,fp);                   //第儿次使用 fgets(char ch,int length,File *fp);  完整输出了 3: this is test2~
    printf("3: %s",buff);
    fclose(fp);


    //二进制I/O函数
    //这俩个函数用于存储块的读写,通常是数组或者结构体

    /*
    size_t fread(void *ptr, size_t size_of_elements, 
             size_t number_of_elements, FILE *a_file);
              
    size_t fwrite(const void *ptr, size_t size_of_elements, 
             size_t number_of_elements, FILE *a_file);
    */

    return 0;
}

//如果你使用的是cloud stuido编译器,那么生成的xxx.txt文件请在你的编译器的文件目录下查找

C预处理器

c预处理器就相当于一个文本处理器
它会在编译器实际编译之前完成所需处理
我们把c的预处理器叫做CPP(C Preprocessor)


c的预处理指令.png

系统预定义宏.png
#include <stdio.h>

#define MAX_ARRAY_LENGTH 20   //告诉c预处理器将 MAX_ARRAY_LENGTH 替换为 20

// #include "mystdio.h"          //从本地加载mystdio.h,吗,没有此文件编译器会报错

#undef FILE_SIZE              //取消 FILE_SIZE的定义
#define FILE_SIZE 30          //将FILE_SIZE 重新定义为 30


#ifndef MESSAGE               //这个指令告诉CPP,如果MESSAGE没有定于则定义为 you win
  #define MESSAGE "you win!"
#endif


#ifdef DEBUG                  //这个指令告诉编译器,如果定于了DEBUG,则要执行处理语句
  /*执行处理语句*/
#endif


//预处理运算符
// \是延续命令符,如预定义命令太长的话可以使用
#define Msg_for(a,b) \
printf(#a "and" #b",we love you!\n");



//标记粘贴运算符
// ##是粘贴运算符
#define tokenpaster(n) printf("token" #n "=%d",token##n)



//参数化的宏,用宏模拟函数
#define MAX(x,y) ((x)>(y)?x:y)
#define PF(X) (X*X)


int main() {

   printf("File :%s\n", __FILE__ );       //**注意**  系统预定义的命令前后是 2个_
   printf("Date :%s\n", __DATE__ );
   printf("Time :%s\n", __TIME__ );
   printf("Line :%d\n", __LINE__ );
   printf("ANSI :%d\n", __STDC__ );


   Msg_for("LAOWANG","LAOLI");            //输出 "LAOWANG"and"LAOLI",we love you! ,#xxx相当于使用这个变量


   int token32 = 36;                      //相当于 token32 = token32 = 36,因为token和32粘贴在一起就成了一个变量
   tokenpaster(32);


   printf("MAX的结果为:%d",MAX(154,6));   //求最大值宏
   printf("参数的平方是:%d",PF(7));       //求平方的宏
   return 0;
  
}

C头文件

#include <xxxx.x>  //是引用系统的头文件
#include "xxxx.x"    //是引用我们自定义的头文件


#ifndef HEADER_FILE           //只引用一次头文件
#define HEADER_FILE

the entire header file file

#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"         //使用c的自定义宏代我们自己定义的文件名
 ....
 #include SYSTEM_H

C强制类型转换

#include <stdio.h>

int main() {

  //类型转换
  int x=15,y=7;
  double res;
  res = (double)x/y;             //强制类型转换和c#是一样的,直接就是 (type_name)xxx
  printf("res is: %f",res);


  //整数提升
  int a = 17,sum;
  char c = 'c';
  sum = a+c;
  printf("结果为:%d\n",sum);
  printf("c的结果为:%d",c);

  return 0;
}

//一般小到大的数据类型编译器必须使用强制转换(type_name)xxx
//大到小就是编译器自动隐式转换

C错误处理

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.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", strerror( errnum ));
   }
   else
   {
      fclose (pf);
   }


   //被零除的错误的检验
   int a = 20;
   int b = 0;
   int res;

   if(b == 0) {
     fprintf(stderr,"除数不能为0退出运行!\n");
     exit(EXIT_FAILURE);  //exit(-1)也可以
   }
   res = a/b;

  exit(EXIT_SUCCESS);     //exit(0)也可以
   printf("验证完的结果为:%d\n",res);

   return 0;
}

// perror("你的提示");
// fprintf(stderr, "打开文件错误: %s", strerror( errnum ));
//程序执行一般会返回exit(EXIT_SUCCESS); 即exit(0);
//退出程序就是 exit(EXIT_FAILURE);

C递归

阶乘:例如求3的阶乘,就是1x2x3的值为6
斐波那契数列就是 1,1,2,3,5,8,13 这样前俩个数相加得到够一个的值,下面还有一个视频讲解斐波那契数列。
视频链接

#include <stdio.h>


//求一个数的阶乘
double factorial(unsigned int i) {

  if(i <= 1) {
    return 1;
  }
  return i * factorial(i-1);
}

//打印斐波那契数列
int fibonaci(int i) {
  if(i==0) {
    return 1;
  }

  if(i==1) {
    return 1;
  }  
  return fibonaci(i-1) + fibonaci(i-2);
}

int main() {

  int i = 3;                    //3的阶乘为 3*2*1=6
  printf("i的阶乘为:%f\n",factorial(i));

 
  for(int i=0;i<10;i++) {       //打印了斐波那契数列  1,1,2,3,5,8,13,21,34,55,
    printf("%d\n",fibonaci(i));
  }

  return 0;
}

C可选参数

#include <stdio.h>
#include <stdarg.h>


double average(int num,...) {
  
  //va_list 是一个类型
  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("test1 result: %f\n",average(4,2,5,3,6));
  printf("test2 result: %f\n",average(2,10,10));

  return 0;
}
//1.定义一个va_list 类型的集合存储所有输入的参数
//2.保证方法的第一个参数传递的是所有参数的个数,有几个参数就传入几
//3. va_start(valist,num);  进行初始化
//4. for循环+num,va_arg(valist,int); 遍历每一个参数
//5. va_end(valist);  清理valist的内存区域

//输出的结果为:
//test1 result: 4.000000
//test2 result: 10.000000

C的内存管理

c的内存管理.png
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {

  char name[100];
  char *des;

  strcpy(name,"WangDaNa");

  /* 动态分配内存 */
  //malloc也可以使用calloc替代,只不过每个空间的初始化的值是未知的
  des = (char *)malloc(30 * sizeof(char));
  // des = (char *)calloc(200,sizeof(char));
  if(des == NULL) {
    fprintf(stderr,"Error - unable to allocate required memory\n");
  }else{
    strcpy(des,"This is my descryption,this is my test!");
  }

  /* 输出结果 */
  printf("Name:%s\n",name);
  printf("Description:%s\n",des);

  /*
  1.如果是定义数组的形式,我们一但定义,内存的大小就是固定的我们无法改变
  2.使用malloc或者calloc动态分配内存则不一样,可以使用realloc重新分配内存
  */


  /* 假如我想要更大的内存 */
  des = (char *)realloc(des,100 * sizeof(char));
  if(des == NULL) {
    fprintf(stderr,"Error - unable to allocate required memory\n");
  }else{
    strcat(des,"This is my descryption2,this is my test!2");
  }

  printf("NewDescription:%s\n",des);

  /* 释放内存 */
  free(des);
  
  return 0;
}

//分配内存使用malloc或者calloc函数
//重新分配内存使用realloc函数
//释放内存使用free函数

C的命令行参数

#include <stdio.h>

/* argc是参数个数,argv是指针数组 */
int main(int argc,char *argv[]) {

  if(argc == 2) {
    printf("argv[1]:%s\n",argv[1]);
  }else if(argc > 2) {
    printf("Too many arguments supplied.\n");
  }else {
    printf("One argument supplied.\n");
  }

  return 0;
}

//传递参数时可以不加引号,每个参数空格分隔开
//也可以加上引号,这样就成了一个参数
//argv[0]一直会存储程序的名称,第一个参数从索引1开始存储

The End!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,430评论 3 44
  • 1.语言中变量的实质 要理解C指针,我认为一定要理解C中“变量”的存储实质, 所以我就从“变量”这个东西开始讲起吧...
    金巴多阅读 1,743评论 0 9
  • 1、c语言的函数有以下特点: (1)才源程序由函数组成,一个主函数main()+若干其他函数 C程序中的函数类似文...
    滕王阁序阅读 1,335评论 0 6
  • 注:这是第三遍读《C语言深度解剖》,想想好像自从大学开始就没读完过几本书,其中谭浩强的那本《C语言程序设计(第四版...
    HavenXie阅读 1,717评论 1 6
  • C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构。C程...
    小辰带你看世界阅读 816评论 0 1