深入学习C语言和数据结构

第一章:C语言数据类型和表达式

1. main 函数也可以传入参数

#include <stdio.h>
int main(int argc, char *argv[])
{
	printf("Hello World!\n");
	return 0;
}
//argc表示传入参数的个数,argv[]表示数组

例如:

ls -l /usr/local/src/

以上命令会以数组的形式存入argv[ ]中

char *argv[3]={"ls","-l","/usr/local/src/"}

2. file 命令

file hello			//hello是个编译后的文件
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), 
for GNU/Linux 2.6.18, not stripped

ELF:linux下的可执行程序的格式(比如C程序)
LSB:小端字节序
executable:可执行的
dynamically linked:动态库

3. rz 命令

把 linux 中文件下载到 windows 下。

4. 编程语言发展历程

机器语言

00	01	02			// 00表示加
01	03	02			// 01表示减
02					// 02表示搬运

汇编语言

ADD	01	02
SUB	03	02

as 汇编器把汇编语言翻译生成二进制的机器语言
由于不同的机器"ADD"等命令不同,还有汇编器不同,不具有可移植性

C语言

1+2

C语言 ——> 汇编(gcc命令) ——> .o (机器语言)——> .exe(可执行)

5. 程序入门

#include <stdio.h>						// 单行注释:包含系统的头文件
int add(int a, int b);					// 函数声明
int main(void)
{										// 花括号、程序体、代码块
    int    a = 500;  		    		// 定义变量初始化
    int    b;             				// 定义变量,那这时的值是随机值

    b = 20;            					// 变量赋值

    printf("Total: %d\n", add(a, b));   // 函数调用,这里的a和b是实参
}

/* 返回 a+b 的值 */
int add(int a, int b)					// 函数定义,这里的a和b是形参
{
     return a+b;
}

6. 关键字

signed   unsigned    char     short    int      long   double   float
void     enum        union    struct   volatile

auto     const      register  restrict   sizeof   static   typedef   

if   else  for   while   do   switch  case  default   break   continue   goto  

inline   extern   return 

C90关键字:   signed   void
C99关键字:   _Bool(true false)    _Complex  _Imageubary  

char ch = ‘a’
// 存的数据并不是a,存的是0x61
volatile

volatile int reg=10;			// volatile,防止被优化
sleep(2);
if( reg==10 )					// 此语句是废话,在寄存器中会被优化 
{
	do_something;
}

const 常量

const a=10;
a=20;							// 不能修改a的值

const 相当于把a这个变量封口了,不允许改动里面的值。

register 寄存器常量

int a=10;						// a存放到内存
register int a=10;				// a存到寄存器,cpu取数据是在寄存器里面取的

//寄存器是有限的,如果没有那么多的寄存器,a还是存放到内存中

sizeof

char *p="Hello world";
sizeof(p);						// 4/8,p是一个指针,指向数组的首地址
strlen(p);						// 11

static静态
static定义一个变量的时候,这个变量放在堆栈中

typedef

typedef	char *	PTR;			// PTR可以代替char *
define	PTR2	char *;			// 宏只是简单的替换

PTR		p1,p2;					// p1和p2都是char * 类型的指针
PTR2	p1,p2;					// 等于char * p1,p2; 所以p1是指针,p2是变量

7. 运算符与表达式

a = 10 ; 左值 与 右值 的区别
左边为寄存的空间,右边必须为值;
if(15 == b) ;这样写是可以的, “==”是比较符号

8. 强制类型转换

隐式转换:

  • 出现在表达式里,有符号和无符号的 char 和 short 都将自动转化成 int ,需要的转化成 unsigned int ;
  • 两种数据类型的运算,低级别往高级别转换。级别由低到高:int -> unsigned int ->long ->unsigned long ->long long -> unsigned long long -> float -> double -> long double
  • 最后赋值时,最终结果将转换成要赋值给的那个变量的类型;
  • 当作为函数参数时,char和short会转化成int, float会转换成double,但函数原型可以阻止自动提升的发生

显示转换:
int mice;
mice = 1.6 + 1.7( 先作1.6+1.7=3.3 然后再强制转换成mice类型为3
mice = (int )1.6 + (int)1.7 1+1 = 2

强制类型转换相当于变形,要把你变成我理想中的状态。
int是整形,我只要前面的整数,后面的小数点我不要你,不管你有多大!

9. 条件判断

a < b && b > c || b > d
max = (a>b) ? a : b
在这里插入图片描述
错误代码:for语句后面不用分号

第二章:函数与字符串函数

1. 函数

函数是用于完成特定任务的程序代码的自包含单元,在设计程序的时候,一个功能模块抽象成一个函数。
函数的定义:

  void  my_func(char  ch, int  num)  
  {
                function_body
  }
  • 函数先定义,后使用
  • 函数的声明
  • 形参与实参
  • 按值传递 void swap(int a, int b)函数
  • 黑盒子观点
  • 递归函数

2. 多个C文件

原则:

  • 一个功能一个函数
  • 一个模块一个C文件
  • 一个C文件一个头文件
  • C文件放定义,头文件放声明

3. 字符

字符 与 字符串 ‘0’ 0 \0 0x00

全缓冲 行缓冲 不带缓冲

重定向: 标准输入 标准输出 标准出错

字符标准输入 getchar()
字符串标准输入 gets()
字符标准输出 putchar()
字符串标准输出 puts()

第三章:指针与数组

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。

1. 指针基础

int main (int argc, char **argv)	// argv是指向char *类型的指针
{
    int       a = 10;				// 开辟4个字节的内存,这个内存单元的名字叫a,从低到高存的值分别为:0x0A,00,00,00
    int       b = 20;				// 同上,从低到高存的值分别为:0x14,00,00,00
    int       *p;
    int       **q;
   
     p = &a;             
     q = &p;							// 此时只能取p的地址
    
     a = 20  VS   *p=20   VS **pp=20;	// 等价操作
     p = &b  VS   *q=&b;

}

在这里插入图片描述

2. 指针与地址

int main (int argc, char **argv)
{
    int       a = 0x12345678;

    int       *p1 = NULL;			// 从低到高(小端字节序),取4个字节
    char      *p2 = NULL;			// 从低到高(小端字节序),取1个字节 

    p1 = &a;						
    p2 = (char *)&a;				// 强制类型转换	
    printf("p1 address: %p p1 value: %p p1 point memory value: 0x%x\n", &p1, p1, *p1 );
    printf("p2 address: %p p2 value: %p p2 point memory value: 0x%x\n", &p2, p2, *p2 );
    p1 ++;          p2++;
    printf("p1 address: %p p1 value: %p p1 point memory value: 0x%x\n", &p1, p1, *p1 );

    return 0;
}

在这里插入图片描述
从结果来看,数据存储应该是大端字节序。

总结:

  • 指针p 指向的是首地址
  • “ * ” 取值符号,同时要和前面的类型结合,int类型一般取4个字节的值,*p 和 a=1 中的a有相同的效果,*p指前一个数;
  • 以后看见 *p 就理解为 “a” ;

3. 数据交换

void swap(int  a, int b)					// main函数中的a和b并没有改变
{
     int     tmp;
     tmp = a;
     a = b;
     b = tmp;
}

int main(void)
{
    int     a = 10;
    int     b = 20;

    printf("a=%d b=%d\n", a, b);
    swap(a, b);
    printf("a=%d b=%d\n", a, b);
}

void swap(int  *p1, int *p2)				// 改变了main函数中a和b的值
{
     int     tmp;

     tmp = *p1;
     *p1 = *p2;
     *p2 = tmp;
}

int main(void)
{
    int     a = 10;
    int     b = 20;

    printf("a=%d b=%d\n", a, b);
    swap(&a, &b);
    printf("a=%d b=%d\n", a, b);
}

4. const 关键字

const char *p; 		*p=30; 			// 不合法
char * const p;		 p=&b;			// 不合法
const char * const p; *p=30; p=&b;  // 均不合法

5. C程序内存布局

栈区: 局部变量(自动分配,{}内有效,离开{}自动失效)
堆区: malloc分配的内存(自己管理,用完free,否则泄漏)
在这里插入图片描述
.bss: 未初始化的全局变量或static变量
.data: 初始化过的全局变量或static变量
.rodata: const, #define,char *ptr="string"等定义的数据常量

6. malloc和free

{
    int     i;
    int     *ptr = NULL;

    ptr =  (int *) malloc(10*sizeof(int));

    for(i=0; i<10; i++)
    {
       ptr[i] = i;
    }

    free(ptr);
}

7. 举个例子

statistic( void )
{
	int  a=10;
	a++;
}

statistic( );
statistic( );
statistic( );
statistic( );
statistic( );
我连续调用statistic( );函数,其中a的值不变,因为a的值存在栈中,函数调用就清空了。

statistic( void )
{
	static  int  a=10;
	a++;
}

statistic( );
statistic( );
statistic( );
statistic( );
statistic( );
static常量就是把a的值存到数据段中。函数调用结束后不会消除a的值。

数据段:

.bss未初始化的全局变量或者static变量
.data初始化过的全局变量或者static变量
.rodataconst, #define, char *ptr=“string” 等定义的数据常量

第四章:复合数据类型

1. 联合 union

联合是一个能在同一个存储空间里(但不同时)存储不同类型数据的数据类型。

union u_data
{
    unsinged char      a;
    unsigned int       b;
} data;

sizeof(data) = ?                  // 答案是4,联合的存储空间应该由最大的成员决定

data.b = 0x12345678;
data.a = ?                        // 答案是0x78,小端字节序,取一个字节

data.a = 0xef;					  // 只修改一个字节
data.b = ?					      // 答案是0x123456ef

在这里插入图片描述

问题:写一段代码,判断当前系统是小端字节序还是大端字节序?

方法1:联合

/* 返回值1:LSB 返回值0:MSB */

int is_lsb( void )
{
	union test
	{
		unsigned char a;
		unsigned int b;
	}data;							//先定义一个联合

	data.b = 0x12345678;
	
	if( data.a == 0x78 )
		return 1;
	else
		return 0;
}

方法2:指针

int is_lsb( void )
{
	unsigned int data = 0x12345678;
	unsigned char *ptr = NULL;
    
    ptr = (unsigned char *) &data;   //指针有两种含义,一是指针存的是地址,二是指针的类型(要记得强制类型转化)
	if( *ptr == 0x78 )
		return 1;
	else
		return 0;
}

两种方法都是判断最下面的存储空间的字符
如果最下面存的是小端那么就是小端字节序,如果存的是大端那么就是大端字节序。

2. 枚举 enum

可以使用枚举类型声明表示整数常量的符号名称。

   enum 
   {
           Gold,
           Wood,
           Water,
           Fire = 8,
           Earth,  
   };

    switch(five_line)
    {
           case Gold:
                 do_something;
                 break;
           default:
                 break;
    }

作用:定义常量,方便写代码。

3. 结构体

如果我们想保存学生的信息,这些信息包括学生的姓名,年龄,性别,成绩等。在我们已知的类型中是否有一种数据类型能够描述这所有信息?
结构体可以由基本数据类型构造出一种新的复合数据类型来。

enum { male, female };
struct   st_student
{
     char     name[32];
     int        gender;
     int        age;
     float     score;
};

struct st_student student = {“zhangsan”, male, 20, 90.0};
struct st_student student = {.name=“zhangsan”, .gender=male, .age=20, .score=90.0};
结构体存储方式是按顺序存储的。

4. Typedef

Typedef 用来定义一种新的数据

typedef unsigned char   u8;                        u8             var1, var2;
typedef unsigned short  uint_16;                   uint_16        var1, var2;
ypedef  char    *       pchar;                     pchar          var1, var2;
typedef int     *       pint;                      pint           var1, var2;

#define PCHAR  char  *                             PCHAR          var1, var2;

宏定义要与 Typedef区别开,宏定义是简单的替换

  PCHAR  var1, var2;     =     char  *var1, var2;                //等价关系

var1是char *类型,var2是char类型。
所以,以后要把‘ * ’要与后面的变量写在一起,防止看错。

Typedef 与结构体结合

typedef struct    _st_student    //前面有个下划线,表示是底层函数,不要调用它
{
     char     name[32];
     int        gender;
     int        age;
     float     score;
} st_student ;				    //st_表示结构体, un_表示联合, en_表示枚举, 便于读取方便( 见其名,知其意 )


st_student   student;       =   struct _st_student student;    //等价关系

5. 结构体对齐

笔试经常考的题目,结构体的存储空间。

typedef struct _st_struct1
{
    char        a;
    short       b;
    int         c;
} st_struct1;

sizeof(st_struct1) = ?        // 答案是 8

在这里插入图片描述

typedef struct _st_struct2
{
    short       b;
    int         c;
    char        a;
} st_struct2;

sizeof(st_struct2) = ?      // 答案是 12

在这里插入图片描述

  • 自身对齐值: 自身对齐值就是结构体变量里每个成员的自身大小;
  • 指定对齐值: 指定对齐值是由宏#pragma pack (N)指定的值,里面的N必须是2的幂次方,如1,2,4,8,16等。如果没有通过#pragma pack宏那么在32位的Linux主机上默认指定对齐值为4,64位的Linux主机上默认指定对齐值为8,ARM CPU默认指定对齐值为8;
  • 有效对齐值: 结构体成员自身对齐时有效对齐值为自身对齐值与指定对齐值中较小的一个,结构体圆整时,为所有成员中自身对齐值最大的与指定对齐值较小的一个;

步骤:

  1. 结构体各成员对齐 (存放的位置应该是自身大小的整数倍
  2. 整个结构体圆整(4的整数倍

6. 结构体数组

如何保存和访问一个班上的学生的信息?

#define    MAX_STUDENTS         60
st_student                      students[MAX_STUDENTS];

// 访问结构体数组,名字赋值错误
students[1]={.name="lisi", .gender=male, .age=21, .score=80.0};

// 访问结构体数组,赋值正确
students[1]={.gender=male, .age=21, .score=80.0};
strcpy(students[1].name, "lisi");     //字符串要用strcpy( )赋值

// 遍历结构体,打印
for(i=0; i<MAX_STUDENTS; i++)
{
    printf("Name: %s Gender: %s Age:%d Score:%f\n", 
    students[i].name, students[i].gender, students[i].age, students[i].score);
}

7. 结构体指针

st_student        student;
st_student       *ptr = NULL;
ptr = &student;

在这里插入图片描述

strncpy(student.name, "zhangsan", 32);
student.gender = female;
student.age = 19;
student.score = 95.0;

strncpy(ptr->name, "zhangsan", 32);
ptr->gender = female;
ptr->age = 19;
ptr->score = 95.0;

(*ptr).gender = female;
(*ptr).age = 19;
(*ptr).score = 95.0;

第五章: 链表、栈和队列

1. C程序内存布局

在这里插入图片描述

2. 程序内存布局

在这里插入图片描述

3. 链表原理示意图

对于大小固定的值,我们用数组存储
对于大小可能会变化的值,我们用链表存储
在这里插入图片描述
链表中节点的位置并不是一段连续的地址空间
数组可以直接通过下标来访问值,效率高,但是会浪费空间
在这里插入图片描述
头插法:排队插到前面,先进后出(栈)
尾插法:排队,先到的先出(队列)

4. 栈原理示意图

栈就是一种可以实现后进先出(Last-In First-Out, LIFO)的存储结构。其实现的方法有两种可选方案:数组和链表。
必读代码实现参考链接: http://blog.csdn.net/jjzhoujun2010/article/details/6856164

栈的链表实现其实就是 链表的头插法。下面讲解以数组方式实现栈的原理:
在这里插入图片描述

5. 队列–静态数组

队列是一种“先进先出的数据结构FIFO”,数据从tail中入队列,从front中读出。队列的存储可以由数组和链表两种方式实现。
队列的链表实现,其实就是链表的尾插法:http://blog.csdn.net/keheinash/article/details/51143573

在这里插入图片描述

下面分析数组的实现过程,假设队列的总长度为5,也即数组的大小为5:
在这里插入图片描述

6. 队列–循环buffer

存在问题:
1,如何将地址线性增长的数组抽象成环形?
2,空时条件 front = = rear,满时也为front = = rear,如何判断循环buffer是否为空或满?

解决方法:
1,rear = (rear+1)%rb_size;
2,空一个存储单位不存储数据,这样:
空条件: rear==front
满条件: (rear+1) % rb_size == front
buffer中数据大小: (rear+rb_size-front)%rb_size
静态队列(循环buffer)实现:
http://blog.csdn.net/keheinash/article/details/51125063

在这里插入图片描述

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值