C 语言入门

C 语言概述

一、基本概念

  • C 是一门 面向过程 强类型 静态 编译型 高级语言

二、hello world

1、gcc 安装使用

  • 编译 C 程序请先安装 GCC 编译器
  • gcc windows使用
    • gcc haha.c即可编译程序,默认的可执行程序的名称为 a.exe
    • gcc haha.c -o haha.exe指定程序的名称
    • haha.exe控制台直接输入这个就能执行程序
    • getchar()直接打开可执行程序控制台一闪而过的解决方法

2、hello world demo

#include <stdio.h>

int main()
{
   /* 我的第一个 C 程序 */
   printf("Hello, World! \n");  
   return 0;
}

C 基础知识

一、基本语法

  • 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、类型转换

  • 自动转换(隐式转换),基本的方向如下:
    int -> unsigned int -> long -> unsigned long -> unsigned long long -> float -> double -> long double
  • 有时候需要进行 强制转换,下面的例子是否进行强制转换得到的结果是不同的
#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、变量

  • 变量的申明和定义:定义是需要分配存储空间的,而仅仅申明不需要
extern int i; //声明,不是定义
int i; //声明,也是定义
  • 关于左值和右值
    • 左值(lvalue):指向内存位置的表达式被称为左值表达式;
    • 右值(rvalue):指的是存储在内存中某些地址的数值;
    • 总结:变量是左值,可以出现在等号的左右两边;数字是右值,不能出现在赋值号的左边;

2、常量

  • 四种常量
    • 整数常量和浮点常量:
    • 字符常量和字符串常量(区分单括号和双括号):转义字符的用法和其他的语言差不多;
  • 常量定义
#define LENGTH 10   
const int  WIDTH  = 5;

3、存储类(修饰符)

  • auto:局部变量默认的存储类,只能修饰局部变量;
  • register:修饰的还是局部变量,但是这个局部变量比较特殊,一般是存在寄存器(CPU)中的(不是一定的),不能使用一元的 & 运算符(不在内存中);
  • 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};

参考:C语言中 数组名 与 ”&数组名” 的区别

2、数组作函数参数和返回值

作返回值
  • 返回数组一定要将函数内的数组声明为 static;
  • 不允许返回一个完整的数组,只能返回一个指针(数组名);
  • 函数的定义的时候返回值也要写指针;
int main(){
	int* getArr();
	int* arr = getArr();
	int i;
	for(i=0;i <2;i++){
		printf("%d\n",arr[i]);
	}	
	getchar();
	return 0;
}

int* getArr(){
	static int arr[] = {12,45};
	return arr;
}
作参数
  • 类似指针作形参 void myFunction(int *param),可以传递一个数组(这里的变量名 param 和数组名实际代表的都是地址,可以等同看待);
int main(){
	int balance[]={12,23,10};
	int sum = add(&balance,3);
	printf("sum is %d \n",sum);
	getchar();
	return 0;
}

int add(int arr[],int len){
	int sum =0;
	int i;
	for(i=0;i < len;i++){
		sum += arr[i];
	}
	return sum;
}

int add(int* arr,int len){
	int sum =0;
	int i;
	for(i=0;i < len;i++){
		sum += arr[i];
	}
	return sum;
}

C语言随机数使用:srand( (unsigned)time( NULL ) );rand();
计算机内存的基本单位是 byte,而不是 bit;

3、指向数组的指针和指针数组

指向数组的指针
  • 在如下定义的前提下,*(balance + 4) 是一种访问 balance[4] 数据的合法方式?
int test[]={12,34,56};
int (*p)[3];
p = test;
printf("(*p)[1]=%d\n",(*p)[1]);
printf("p=%d\n",p);
printf("*p=%d\n",*p);
指针数组
  • 首先这是一个数组,其次这个数组的元素都是指针,存的是指向其他变量的地址;
  • 可以用一个指向字符的指针数组来存储一个字符串列表,使用如下:
int *p[2] ;
int a=11,b=22;
p[0] = &a;
p[1] = &b;
printf("[%d,%d]\n",*p[0],*p[1]);
指向指针的指针
  • 多级的间接寻址的方式,会形成指针链
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,二者基本是等效的,都能取到函数的地址;
回调函数
  • 函数指针变量可以作为某个函数的参数来使用的
  • 回调函数就是一个通过函数指针调用的函数
  • 回调函数是由别人的函数执行时调用你实现的函数
#include <stdlib.h>  
#include <stdio.h>
 
// 回调函数
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
    for (size_t i=0; i<arraySize; i++)
        array[i] = getNextValue();
}
 
// 获取随机值
int getNextRandomValue(void)
{
    return rand();
}
 
int main(void)
{
    int myarray[10];
    populate_array(myarray, 10, getNextRandomValue);
    for(int i = 0; i < 10; i++) {
        printf("%d ", myarray[i]);
    }
    printf("\n");
    return 0;
}

四、字符串

1、字符串的本质和定义

  • 字符串的本质就是字符数组,其定义的形式是这样的:char greeting[] = “Hello”

2、字符串常用函数

  • 如下的函数都是在 c 的标准库<string.h>中的
  • 需要注意的是c语言中字符串是不能直接赋值的,要使用strcpy函数来赋值(定义初始化的时候例外);
    在这里插入图片描述

五、结构体

有点类似于 java 中的类的概念

1、结构体的定义、初始化和访问

  • 数组是用来存储相同类型的数据的,而结构体是用来组织相关联的不同类型的数据的
  • 结构体成员的访问方式和 java 中类成员的访问方式是类似的(book.title)
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book = {"C 语言", "test", "编程语言", 123456};

2、结构体做函数参数和指向结构的指针

  • 结构体做函数的参数的使用和其他类型做函数参数没啥不同;
  • 指向结构体的指针使用:定义和通过指针访问结构体的成员
struct Book book;
struct Book *b1 = &book;
book.book_id = 123;
strcpy(book.title, "love");
strcpy(book.author, "allen");
printf("%s\n",b1->author);

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;
}

六、共用体

1、共用体说明

  • 共用体是一种特殊的数据类型,允许在相同的内存位置存储不同的数据类型;
  • 可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值,且占用的空间是按成员中占用空间最大的那个来算的;
  • 共用体变量同时使用会导致后面的覆盖前面的,使得前面的赋值出现损失,所以不要同时使用;
  • 共用体的定义和使用和结构体是差不多的:
union Data
{
   int i;
   float f;
   char  str[20];
};
union Data haha;
haha.i = 10;

2、关于 typedef

  • typedef 可以用来为内置的数据类型和自定义的数据类型取别名,定义之后使用该类型可以直接用别名替代;
  • typedef 和 #define:
    • typedef 仅限于为变量类型定义别名,#define 既可以为变量类型定义别名,也可以为常量定义别名;
    • typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的;

输入&输出

一、控制台输入输出

  • 需要标准库的支持<stdio.h>
  • printf 和 scanf:格式化输出
    • scanf("%s %d", &str, &i) 期待你的输入的类型和需要的类型相同,否则有可能报错;
    • scanf 遇到空格就会停止读取,所以 “this is test” 对 scanf 来说是三个字符串;
  • getchar 和 putchar:一个字符的输入和输出
  • gets 和 puts:一行一行地读写,需要提供一个缓冲区
char str[100];
gets( str );
puts( str );

二、文件读写

  • 文件打开(fopen)
  • 文件关闭(fclose)
  • 文件写入(写字符:fputc 写字符串:fputs 和 fprintf)
  • 文件读取(读字符:fgetc 读字符串:fgets 和 fscanf ----fgets 按行读取,而 fscanf 是以空格作为结束的)
  • 二进制输入输出函数(fread 和 fwrite)
int main(){
	FILE *f ;
	f = fopen("aa.txt","r");
	//fprintf(f,"%s %s %s %d","we","are","in",2014);
	/*int i = 0;
	while(i < 5){
		fputs("\nhello hello what is your name?",f);
		i ++;
	}	*/
	char buffer[255];
	while(!feof(f)){
		fgets(buffer,255,f);
		//fscanf(f,"%s ",buffer);
		printf("%s",buffer);
	}	
	fclose(f);
	return 0;
}

预处理器和头文件

一、预处理器和宏

1、二者的概念

  • 预处理器就是一个文本替换的工具;
  • C 语言中的宏就是实现文本替换功能的代码;

2、预处理器指令和运算符

  • 常见的预处理器指令如下:
    在这里插入图片描述
  • 预处理器运算符:续行符(\)、字符串常量化运算符(#)、标记粘贴运算符(##)、defined() 运算符

3、预定义宏和参数化宏

  • 预定义宏常用的如下,不能直接修改:
    • DATE: 当前日期,一个以 “MMM DD YYYY” 格式表示的字符常量
    • TIME :当前时间,一个以 “HH:MM:SS” 格式表示的字符常量
    • FILE :这会包含当前文件名,一个字符串常量
  • 可以使用参数化的宏来模拟函数,必须要注意的是宏的本质就是用来文本替换的,所以下面的实例中括号绝对不能少,少了可能出错:
#define square(x) ((x) * (x))
//二者实现的功能是一样的
int square(int x) {
   return x * x;
}

二、头文件

这里的用法类似于java中的import

1、基本语法

#include <file>      引用系统头文件
#include  "test.c"       引用用户头文件

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

错误&递归&可变参&内存管理&命令行参数

一、错误处理

  • C 语言没有提供类似 java 的错误处理的机制,只能在错误发生的时候输出一些错误的信息;
  • 在发生错误时,大多数函数会返回 1 或 NULL,同时会设置一个错误代码 errno,该错误代码是全局变量
  • C 语言提供了 perror() 和 strerror() 函数来显示与 errno 相关的错误消息
#include <stdio.h>
#include <errno.h>
#include <string.h>

extern int errno ;

int main ()
{
   FILE * pf;
   pf = fopen ("unexist.txt", "rb");
   if (pf == NULL)
   {
      fprintf(stderr, "错误号: %d\n", errno);
      perror("通过 perror 输出错误");
      fprintf(stderr, "打开文件错误: %s\n", strerror( errno));
   }
   else
   {
      fclose (pf);
   }
   return 0;
}

程序的退出状态:程序正常退出exit(0);程序发生异常exit(-1);

二、递归

  • 所谓的递归就是函数自己调用自己,最经典的例子就是斐波那契数列的实现;
  • 递归一定要有退出条件,不然会形成死循环,导致程序出错;
  • 递归的次数不可过多,次数过多的话效率十分低下,还容易导致栈的溢出;
#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 getAve(int num,...){
	va_list valist;
	va_start(valist,num);
	double sum =0;
	int i;
	while(i < num){
		sum += va_arg(valist,int);
		i ++;
	}
	va_end(valist);
	return sum/num;
}
 
int main()
{
   printf("ave is %f\n",getAve(3,10,5,15));
   return 0;
}

四、内存管理

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];
   strcpy(name,"allen");
   char *des;
   des = malloc(50*sizeof(char));
   if(des == NULL){
	   fprintf(stderr,"can not allocate memery");
   }else{
	   strcpy(des,"allen is a good student\t");
   }
   des = realloc(des,100*sizeof(char));
   if(des == NULL){
	   fprintf(stderr,"can not allocate memery");
   }else{
	   strcat(des,"allen is 18 and he is very good-looking");
   }
   printf("%s\n",name);
   printf("%s\n",des);
   free(des);
   return 0;
}

五、命令行参数

  • int main( int argc, char *argv[] ):argc 是指传入参数的个数,argv[] 是一个指针数组,指向传递给程序的每个参数;
  • argv[0] 存储程序的名称,argv[1] 是一个指向第一个命令行参数的指针;
  • 多个命令行参数之间用空格分隔,如果参数本身带有空格,应把参数放置在双引号或单引号内部;
int main(int num,char *haha[])
{
   int i =0;
   while(i <num){
	   printf("%s\n",haha[i]);
	   i ++;
   }
   return 0;
}

标准库参考:https://www.runoob.com/cprogramming/c-standard-library.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值