前言
今天上了个数据结构课,直接自闭了,记得学习C++已经是去年的事了,再看语法已经忘得七七八八,所以写一篇基础知识博客,助人也助我
本博客适用于有C++基础,正在学C语言数据结构的同学,或者是肯动脑思考的同学。
如果你正在学C++,不出意外也会有所收获,尤其是指针和结构体(华农非计科这部分考的不多)。
关于代码风格,这个因人而异,开始我也喜欢一对中括号对齐,后来写Java慢慢适应左右分开。
最后,因本人能力有限,文章不足之处还望多多指正。
编程模板
C语言模板
#include <stdio.h>
int main(void) {
printf("我爱帅帅龙!");
return 0;
}
常用数据类型
简单数据类型:
- char
- int
- long
- float
- double
复杂数据类型:
- 各种数组
查看某种数据类型所占字节数:
#include <stdio.h>
#include <limits.h>
int main(){
printf("int 存储大小 : %d \n", sizeof(int));
return 0;
}
//输出int 存储大小 : 4
转义字符
- \n 换行符,类似按下了回车
- \t 横向制表符,输出4个空格
输入输出
C语言使用scanf
进行输入,使用printf
进行输出,当输入简单数据类型的时候要加上&取址符
,但是输出不用加&取址符
,与Python的格式化相似,请看案例。
输入输出一个整数(简单数据类型):
#include <stdio.h>
int main(void) {
printf("请输入一个整数:");
int i;
scanf("%d",&i);
printf("你输入的整数是%d",i);
return 0;
}
输入输出一个字符数组(复杂数据类型):
#include <stdio.h>
int main(void) {
printf("请输入一段字符:");
char ch[20];
scanf("%s",ch);
printf("你输入的一段字符是%s",ch);
return 0;
}
OK,那为什么会这样呢?其实可以这样理解:
在输入的时候,计算机需要知道变量的地址,因此需要加上取址符,在输出的时候只需要写变量就好了,如果这时候再加上取址符就会得到地址,在格式化的时候原来的变量就变成相应的地址了,注意,数字数组不能直接输入输出
,只有字符数组可以,可以看看下面的例子:
#include <stdio.h>
int main(void) {
printf("请输入一个整数:");
int i;
scanf("%d",&i);
printf("你输入的整数是%d",&i);
return 0;
}
除此之外,输入字符还有多种形式,在此不过多介绍
输入输出格式化
格式化方式 | 输入输出含义 |
---|---|
%d | 输入/输出十进制整数,一般对应int类型 |
%c | 输入/输出字符,一般对应char类型 |
%f | 输入/输出十进制实数,一般对应float类型,也可以是double类型 |
%s | 输入字符或字符数组,输出字符数组 |
%% | 输出百分号(%) |
数组的定义与初始化
常见的数组有字符数组,数值数组,数组支持索引的形式访问和修改元素,在创建的时候需要写上数组大小,通常我们使用数组实现顺序表,时间复杂度为O(1)
数值数组的初始化可以这样来:
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
字符数组初始化有两种形式可以选择:
char ch_1[] = "hello";
char ch_2[] = {'h','e','l','l','o','\0'};
除此之外,还有指向数组的指针,我觉得也并不太常用,但是还是介绍一下吧,其实就是通过地址+1
的形式,再解引用获取某数据所在地,再赋值或更新
#include <stdio.h>
int main (){
double balance[5] = {1000.0, 2.0, 3.4, 17.0, 50.0};
double *p = balance;
printf("使用指针的数组值\n");
for (int i=0;i<5;i++){
printf("*(p+%d):%f\n",i,*(p+i));
}
return 0;
}
数组作为函数参数
函数不需要讲,因为现阶段我们一般不会去用。简单提一下数组作为函数参数的情况,函数的参数只需要声明是那种数组,再写个形参名即可,不需要写数组大小,即:
#include <stdio.h>
void printArray(int array[]) {
for (int i=0;i<5;i++) {
printf("%d\n",array[i]);
}
}
int main(void) {
int array[]={1,2,3,4,5};
printArray(array);
return 0;
}
结构体的定义与使用
结构体类似简单的类,用struct
关键字声明,其中有变量或常量存储数据,如果声明后面加上变量名表示创建了一个实例,名为book
,注意,末尾的;
不可省略
struct Books{
char title[50];
char author[50];
char subject[100];
int book_id;
} book;
结构体也可以直接初始化,如下:
struct Books{
char title[50];
char author[50];
char subject[100];
int book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};
指向结构体的指针
如下,LinkList
等价于Node*
,以后函数返回值或直接声明就可以直接使用LinkList,返回的是一个存放地址信息的指针变量类型
#include <stdio.h>
//LinkList为指向Node的结构指针类型,即Node*类型
typedef struct Node {
int data;
struct Node *next;
}Node,*LinkList;
int main (){
Node *p;
p = (LinkList)malloc(sizeof(Node));
p->data = 11;
printf("%d",p->data);
return 0;
}
何为指针与指针类型变量
我们可以使用取址符&
得到变量的地址,其返回值是对应类型的指针类型,例如:
#include <stdio.h>
int main(void) {
int i=1;
int *p = &i;
printf("当前地址是:%d",p);
return 0;
}
字符数组的变量名就表示首地址,所以可以直接输入:
#include <stdio.h>
int main(void) {
char ch[10];
scanf("%s",ch);
printf("输入的是:%s",ch);
return 0;
}
什么叫引用,什么叫解引用?
引用就可以理解为指针类型的数据。例如int *p
,此时p
就可以成为引用,他的值是地址,*p
表示解引用,代表这个地址的对象
#include <stdio.h>
int main(void) {
int i=1;
int *p = &i;
printf("当前地址是:%d\n",p);
printf("数值是:%d\n",*p);
return 0;
}
指针类型变量
以BOOK这个结构体为例,其定义如下:
typedef struct Book {
int book_id;
} Book;
整体代码如下:
#include <stdio.h>
typedef struct Book{
int book_id;
} Book;
int main(void) {
Book book1,book2;
Book *p = &book2;
//结构体类型访问元素使用.
book1.book_id = 10;
//指针类型的结构体访问元素使用->
p->book_id = 10;
//输出一下
printf("book1的id是%d,book2的id是%d",book1.book_id,p->book_id);
return 0;
}
从上面的代码格式可以看出:
- 指针类型变量是指针类型的数据,存放的是某变量的地址,应当先创建变量,再取值
- 如果是结构体类型的数据可以直接使用
.
操作符获取元素或赋值 - 如果是指针类型数据需要使用
->
操作符获取元素或赋值
指针对象作为参数的函数
先看下面的代码,发现运行之后book的id没有发生变化,这是因为传参的问题:
#include <stdio.h>
typedef struct Book{
int book_id;
} Book;
void add(Book book) {
book.book_id += 1;
}
int main(void) {
Book book1={1};
printf("book1的原始id是%d\n",book1.book_id);
add(book1);
printf("book1的现在id是%d\n",book1.book_id);
return 0;
}
如果想使用指针将数据进行修改,需要将参数类型修改成指针类型变量,例如:
#include <stdio.h>
typedef struct Book{
int book_id;
} Book;
void add(Book *book) {
book->book_id += 1;
}
int main(void) {
Book book1={1};
printf("book1的原始id是%d\n",book1.book_id);
add(&book1);
printf("book1的现在id是%d\n",book1.book_id);
return 0;
}
数组的变量名就表示首地址
*(p+1)
和*(balance+1)
的效果是一样的,因为p存放的就是数组的首地址,而数组名也表示数组的首地址
#include <stdio.h>
int main (){
double balance[5] = {1000.0, 2.0, 3.4, 17.0, 50.0};
double *p = balance;
printf("使用指针的数组值\n");
for (int i=0;i<5;i++){
printf("*(p+%d):%f\n",i,*(p+i));
}
printf( "使用balance作为地址的数组值\n");
for (int i=0;i<5;i++){
printf("*(balance+%d):%f\n",i,*(balance+i));
}
return 0;
}
强制类型转换
#include <stdio.h>
int main(){
int sum = 17, count = 5;
double mean;
mean = (double) sum / count;
printf("Value of mean : %f\n", mean );
}
一些排序算法
大家可以看看菜鸟教程实现的六大排序:
https://www.runoob.com/cprogramming/c-sort-algorithm.html
几个可能用到的函数
free——释放内存
把a这个变量给释放掉
int a[10];
free(a);
malloc——申请内存(初始化)
仅仅是申请内存了,类似于C++的new,但是里面怎么初始化咱不知道,可以后面再进行赋值。Linklist是指针类型变量,如果没有声明,直接写原变量类型的指针变量声明形式,这里是链表的部分代码,可以写成Node*
Linklist L=(Linklist)malloc(sizeof(Node));
几个常见的关键字
#define
#define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:
- typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
- typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。
#include <stdio.h>
#define TRUE 1
#define FALSE 0
int main( ){
printf( "TRUE 的值: %d\n", TRUE);
printf( "FALSE 的值: %d\n", FALSE);
return 0;
}
typedef
用typedef
来为类型取一个新的名字,例如给cahr起一个别名叫BYTE,这样,在代码中写char或BYTE都是一个意思了:
typedef char BYTE;
同时,我们还可以为结构体起个别的名字:
typedef struct Books{
char title[50];
char author[50];
char subject[100];
int book_id;
} Book;
const
被修饰的变量或数组具有只读特性,不能够被更改;若想对变量重新赋值,则是错误的。
此外,const修饰变量还起到了节约空间的目的,通常编译器并不给普通const只读变量分配空间,而是将它们保存到符号表中,无需读写内存操作,程序执行效率也会提高。
除此之外,const还能修饰函数参数、指针,不再一一介绍
const int i = 5;
sizeof
sizeof 可以获得数据类型或变量在内存中所占的字节数,同时,用 sizeof 也可以获得整个数组在内存中所占的字节数,例如,这个输出40:
# include <stdio.h>
int main(void){
int a[10] = {0};
printf("sizeof(a) = %d", sizeof(a));
return 0;
}
一些问题
变量定义在函数内出错
变量如果定义在函数内,当执行完函数变量会自行销毁,呜呜呜,别乱定义
使用指针类型变量的时候要malloc
使用指针类型数据声明变量的时候,需要使用malloc分配内存,要不然没有空间,如果是非指针类型数据就不用使用malloc了,它声明的时候自动分配空间了