C语言基础知识总结

一、数值类型
1.各种进制之间的转换:(二进制,八进制,十进制,十六进制)
1)对于应用程序,数据参与计算的都是以十进制执行的,故数据类型定义的都是十进制的;
2)对于底层都是以二进制(因为十六进制易于读取,两者易于转换)呈现的;
3)对于二进制、八进制、十六进制,其没有对应的数据类型来展现,故只能以字符的形式保存。

2.各种定义数值的处理:(质数,素数,奇数,偶数,丑数等等)
1)质素(素数)
1>题型1:打印1-N内所有质数
/* 1.判别该数是否为质数 */
bool isPrime(int n)
{
if(n == 2 || n == 3)
{
return true;
}
for(int i = 2; i <= n/2; i++)
{
if(n % i == 0)
{
return false;
}
}
return true;
}
/* 2.循环遍历每一个小于N的数 */
vector<int> arr;
for(int i = 2; i < n; i++)
{
if(isPrime(i))
{
arr.push_back(i);
}
}

2>题型2:打印N的质素
vector<int> arr;
for(int i = 2; i < n;)
{
if(n % i == 0)
{
arr.push_back(i);
n = n / i;
}
else
{
i++;
}
}
2)奇偶数
3)丑数
4)求前N个数的和的时间复杂度为o(n)方法
注:采用的方式是直接替换头和尾,如果采用双层循环,时间复杂度就为o(n2)
for(int j = 0; j < c; j++){
               
           num += arr[j];           
       }
       if(num <= t){
               
               count++;
       }
       for(int k = c; k < n; k++){
               
num -= arr[k - c];
           num += arr[k];
           if(num <= t){
               
               count++;
           }
        }

3.结构体定义的使用
1)struct node
{
int num;
struct node *next;
};
此时定义node结构体变量时,必须写成struct node *nod;
2)typedef struct node
{
int num;
struct node *next;
}node;
此时定义node结构体变量时,可以写成node *nod;
同时必须注意后面必须加上冒号,这是一个语句。

二、字符串处理
1.字符串的定义:
1)char str1[] = "HelloWorld";  // 通过字符数组来定义字符串"HelloWorld",其实包含两个存储空间:1.首先是在栈去分配了一段存储空间,用来存储字符串;2.同时此常量字符串存储在静态数据区。
2)char *str2  = "HelloWorld";  // 通过字符指针来定义字符串"HelloWorld",其也包含两个存储空间:1.首先是在栈上为指针分配存储空间,用来存放指针变量;2.同时常量字符串存储在静态数据区。
 3)#define str3 "HelloWorld";   // 通过宏定义来定义字符串"HelloWorld",等价于str3="HelloWorld"

2.字符串的输入:
1)常规字串输入:
1>采用scanf()函数实现输入操作:必须要取地址,将字符串存入地址中;其不能识别空格,tab等按键。
2)带空格的字串输入
 1>采用gets()函数实现输入操作:可以识别空格和tab等按键;其变量只要是地址名字即可,不要写成取地址的方式;
 2>由于scanf()输入字符串时默认空格作为结束符,故要输入带有空格的字符串时,采用以下格式:scanf("%[^\n]",str)。

3.字符串的输出:
1)常规字串输出
1>printf("%s",a[i]);  它的输出是从a中第i号字符开始输出,直到字符串结束符'\0'输出结束。
2)固定长度字串输出:须知字串长度,采用循环输出
3)带空格字串输出:须知字串长度,采用循环输出

4.字符串作为参数传递:
 1)传递的是地址,数组也是一样,传递的是地址;
 2)对于结构体变量而言,要操作结构内部变量,必须根据地址来操作;
 3)操作数的位数最大为64位,故要实现结构体的单位操作,必须重新编写操作函数。
 
5.字符串函数的使用:字符串函数大全见:附录1:字符串函数大全
1)将字符串转换成数值;
1>实现方式一
double atoi(int n,char* str)
{
int len=n;
double num=0;   //定义变量时,一定要初始化
for(int i=0;i<len;i++)
{
num+=(str[i]-'0')*pow(10.0,--len);   //pow()函数返回的是double型,故num必须定义为double型,或者对pow()函数进行强制类型转换
}
return num;
}

2>实现方式二
double atoi(int n,char* str)
{
int len=n;
double num=0;   //定义变量时,一定要初始化
for(int i=0;i<len;i++)
{
num=num*10+str[i]-'0';
}
return num;
}
问题:1.为什么要减去'0'?
曰:计算器存储运算时,均采用的是二进制表示,故对于所有的字母和常用符号,必须约定自己的一套编码规则,于是ASCII码应运而生。而特别是对于数字,ASCII和整型之间相差的数值是48,即一个'0'字符。
而对于ASCII和数值其显示结果是没有任何区别的,即对于显示,其采用的二进制数值是一样的,而两者的不同,主要在于应用运算上的不同,此时的值也是不同的。
2.当数值超过double的范围时,即大数怎么处理?
曰:此时只能做简单的显示,如果是要做运算,必须采用字符的形式来重新进行运算法则的编写。
2)将数值转换成字符串
1>实现方式一
char* itoa(int m,char *a)
{
int i;
while(m>0)
{
a[i++]=m%10+'0';
m/=10;
}
return a;
}
2>实现方式二
char* itoa(int m,char *a)
{
char b[11]="0123456789";
int i=0;
scanf("%d",&m);
while(m>0)
{
a[i++]=b[m%10];
m/=10;
}
return a;
}
6.字符串数组的操作
1>在C++中string可以直接当做变量来处理;
2>可以通过new string[n]来申请器长度;
3>对于string字符串的传递可以跟变量的传递一样操作;
4>具体实现如下:
int stringPrint(int num,const string str[])
{
for(int i = 0; i < num; i++)
{
cout << str[i] << endl;
}

return 0;
}
int mian(void)
{
int num;
string* p;

cin >> num;
p = new string[num];

for(int i = 0; i < num; i++)
{
cin >> p[i];
}
stringPrint(num,p);

delete []p;  // 对于申请的连续地址空间释放时一定要注意
return 0;
}

三、数据结构
1.数组
1)数组定义:数组定义时,必须指定大小,从而编译器为其分配固定大小的内存空间,内存连续分配;
2)数组初始化:最好为其初始化,如果没有初始化,其会保存垃圾数据;
3)数组的输入:数组实现数据输入时,一般采用循环输入;
4)数组的输出:输出一般也采用循环输出;
5)数组作为参数传递:
1>数组做参数,无法按值传递。这是由C/C++函数的实现机制决定的。
2>传数组给一个函数,数组类型自动转换为指针类型,因而传的实际是地址,因此对其进行操作时,可以对数组值进行修改。所以为了避免数组值的修改,应该使用const固定。
下面三种函数声明完全等同:
void func(const int array[10])
void func(const int array[])
void func(const int *array)

2.链表
1)定义:一种物理存储单元上非连续、非顺序的存储结构。
 2)优势:
1>不定长,可以根据数据存储需要,不断添加和删除;
2>存储空间不用连续。
3)链表的一般操作:
1)链表的创建:
a.创建一个新节点node
nod=(node*)malloc(sizeof(node));
nod->val=val;
nod->next=NULL;
/* 判断内存分配是否为空 */
if(nod==NULL)
{
return 0;
}
b.头指针head,用于对单向链表的遍历,该地址指向链表的头部
if(head=NULL)
{
head=nod;
}
c.位置指针p,保存指针位置
else
{
p->next=nod;
}
p=nod;

2)链表的插入:
a.对于单向链表,先从头开始遍历,找到要操作节点的前一个节点
node* listInsert(node* head,int k)
{
int i=2;
node* p=NULL;
node* nod=NULL;

p=head;
nod=(node*)malloc(sizeof(node));
nod->val=val;
nod->next=NULL;
/* 输入查询节点数必须大于0 */
if(k<1)
{
return NULL;
}
/* 当输入为1时,查询的是头节点 */
if(k==1)
{
nod->next=p;
head=nod;
return head;
}
while(p!=NULL&i<k)
{
p=p->next;
i++;
}
nod->next=p->next;
p-next=nod;
return head;
}

3)链表的删除:
node* listDelete(node* head,int k)
{
int i=2;
node* p=NULL;
node* q=NULL;

p=head;
/* 输入查询节点数必须大于0 */
if(k<1)
{
return NULL;
}
/* 当输入为1时,查询的是头节点 */
if(k==1)
{
head=nod->next;
free(p);
return head;
}
/* 开始查询要删除的节点的前一个节点 */
while(p!=NULL&i<k)
{
p=p->next;
i++;
}
q=p->next;
p->next=q->next;
free(q);
return head;
}

4)链表的打印:
int listPrint(node* head)
{
while(head!=NULL)
{
printf("%x--%d",head,head->val);
head=head->next;
}
return 0;
}

5)链表的逆序打印
1>递归实现
int printList1(Node* head)
{
if(head != NULL)
{
if(head->next != NULL)
{
printList1(head->next);
}
}
cout << head->val << "--0x" << head << endl;
return 0;
}
2>堆栈实现
int printList2(Node* head)
{
stack<Node*> st;
while(head != NULL)
{
st.push(head);
head = head->next;
}
while(!st.empty())
{
cout << st.top()->val << "--0x" << st.top() << endl;
st.pop();
}
return 0;
}

6)链表的逆序实现
Node* ReverseLink(Node* head)
{
    Node* prev = NULL;
Node* next;


while(head != NULL)
{
next = head->next;
head->next = prev;
prev = head;
head = next;

}

    return prev;
}

4.要及时释放内存空间,malloc()和free()两个函数要配合使用。


3.栈
1)定义:先进后出,限定只能在表的一端进行插入和删除操作的线性表;
2)线性表分类:
 1>顺序线性表 (也就是用数组实现的,在内存中顺序排列,通过改变数组序号来实现压栈和出栈)
  a.定义一个结构体,保存栈的基本信息
  typedef struct sqStack
  {
  int size;
  int* sp;
  int* top;
  }sqStack;
  b.栈初始化,定义栈的大小和指针
  int stackInit(sqStack s)
  {
  s->top=(int*)malloc(sizeof(int)*n);
  s->sp=s->top;
  }
  c.压栈
  int push(sqStack* s,int val)
  {
  s->val=val;
  s->sp++;
  return 0;
  }
  d.出栈
  int pop(sqStack* s)
  {
  int val=0;
  s->sp--;
  val=s->val;
  return val;
  }
 
 2>链表 (主要是对链表表头进行操作)
  a.定义一个结构体,作为栈的节点
  typedef struct sqStack
  {
  int val;
  struct sqStack* next;
  }sqStack;
  b.栈初始化
  sqStack* stackInit()
  {
  sqStack* s;
  return s;
  }
  c.压栈
  sqStack* push(sqStack* s,int val)
  {
  sqStack* nod=NULL;
  nod=(sqStack*)malloc(sizeof(sqStack));
  if(nod==NULL)
  {
  return NULL;
  }
  nod->val=val;
  nod->next=s;
  s=nod;
  return s;
  }
  d.出栈
  sqStack* pop(sqStack* s)
  {
  sqStack* nod=NULL;
  int val=0;
 
  val=s->val;
  nod=s;
  s=s->next;
  free(nod);
  return val;
  }
3>栈的实现:可以采用两种方式,可以分别采用顺序表或者链表实现。
4>应用:
a.四则运算
b.符号检测

4.队列
1)定义:先进先出,限定只能在表的一端进行插入和在另一端进行删除操作的线性表;
2)队列的实现:可以采用两种方式,可以分别采用顺序表或者链表实现。
3)应用:

5.二叉树
1)基本概念:
1>定义:每个结点最多有两棵子树,左子树和右子树,次序不可以颠倒
2>性质:
a.非空二叉树的第n层上至多有2^(n-1)个元素
b.深度为h的二叉树至多有2^h-1个结点
3>其他概念:
a.满二叉树:
b.完全二叉树:
2)存储结构
1>顺序存储:将数据结构存在一块固定的数组中
#define LENGTH 100  
typedef char datatype;  
typedef struct node
{  
    datatype data;  
    int lchild,rchild;  
    int parent;  
}Node;    
Node tree[LENGTH];  
int length;  
int root; 

2>链式存储:
typedef char datatype;  
typedef struct BinNode
{  
  datatype data;  
    struct BinNode* lchild;  
    struct BinNode* rchild;  
}BinNode;  
typedef BinNode* bintree;          //bintree本身是个指向结点的指针 
 
3)二叉树的遍历
1>前序遍历:根节点->左子树->右子树
2>中序遍历:左子树->根节点->右子树
3>后序遍历:左子树->右子树->根节点

4)遍历的实现
1>递归实现
1>二叉树的创建
a.创建二叉树节点
typedef struct BiTree
{
char chr;
struct BiTree* lchild;
struct BiTree* rchild;
}BiTree;

b.创建二叉树
BiTree* CreateBiTree()
{
char chr;
BiTree* T=NULL;
scanf("%c",&chr);
if(chr!=' ')
{
T=(BiTree*)malloc(sizeof(BiTree));
if(T==NULL)
{
printf("Memory is failed!\n");
return NULL;
}
T->chr=chr;
T->lchild=CreateBiTree();
T->rchild=CreateBiTree();
}
return T;
}

2>前序遍历
int preOlderTraverse(BiTree* T)
{
if(T)
{
printf("%c",T->chr);
preOlderTraverse(T->lchild);
preOlderTraverse(T->rchild);
}
return 0;
}

3>中序遍历
int inOlderTraverse(BiTree* T)
{
if(T)
{
inOlderTraverse(T->lchild);
printf("%c",T->chr);
inOlderTraverse(T->rchild);
}
return 0;
}

4>后序遍历
int postOlderTraverse(BiTree* T)
{
if(T)
{
postOlderTraverse(T->lchild);
postOlderTraverse(T->rchild);
printf("%c",T->chr);
}
return 0;
}

2>非递归实现
1>创建堆栈
a.创建节点
typedef struct sqStack
{
BiTree* Bt;
struct sqStack* sp;
}sqStack;

b.push()函数实现
int push(sqStack** S,BiTree* Bt)
{
sqStack* Stack=NULL;
Stack=(sqStack*)malloc(sizeof(sqStack));
if(Stack==NULL)
{
printf("Memory is failed!\n");
return 0;
}
Stack->Bt=Bt;
Stack->Bt->lchild=Bt->lchild;
Stack->Bt->rchild=Bt->rchild;
Stack->sp=(*S);
(*S)=Stack;

return 0;
}

c.pop()函数实现
BiTree* pop(sqStack** S)
{
BiTree* Bt=NULL;
sqStack* Stack=NULL;

Bt=(*S)->Bt;
Stack=(*S);
(*S)=(*S)->sp;
free(Stack);

return Bt;
}

2>二叉树的创建
a.创建二叉树节点
typedef struct BiTree
{
char chr;
struct BiTree* lchild;
struct BiTree* rchild;
}BiTree;

b.创建二叉树
BiTree* CreateBiTree()
{
BiTree* T=NULL;
char chr;
scanf("%c",&chr);
if(chr!='#')
{
T=(BiTree*)malloc(sizeof(BiTree));
if(T==NULL)
{
printf("Memroy is failed!\n");
return NULL;
}
T->chr=chr;
T->lchild=CreateBiTree();
T->rchild=CreateBiTree();
}
return T;
}

3>前序遍历
int preOlderVisitBiTree(sqStack* S,BiTree* T)
{
if(!T)
{
printf("The BiTree is empty!\n");
}
else
{
while(T||S)
{
while(T)
{
printf("%c",T->chr);
push(&S,T);
T=T->lchild;
}
T=pop(&S);
T=T->rchild;
}
}
return 0;
}

4>中序遍历
int inOlderVisitBiTree(sqStack* S,BiTree* T)
{
if(!T)
{
printf("The BiTree is empty!\n");
}
else
{
while(T||S)
{
while(T)
{
push(&S,T);
T=T->lchild;
}
T=pop(&S);
printf("%c",T->chr);
T=T->rchild;
}
}
return 0;
}

5>后序遍历

四、排序
1.插入排序
2.希尔排序
3.冒泡排序
4.快速排序
5.选择排序
6.堆排序
7.归并排序
8.基数排序


五、注意事项
1.头文件包含问题
2.内存分配问题

六.简单的编程习惯
1)当系统较为复杂时,为了防止头文件被重复引用,应当用 i f ndef / def i ne/ endi f 结构产生预处理块
2)当编译器引用了未被初始化的变量,可能导致程序错误。故尽可能在定义变量时初始化该变量
3)应当将修饰符 * 和 & 紧靠变量名,避免误解
4)常量命名使用全大写;变量的名字应当使用“名词”或者“形容词+名词”,形式为float oldValue;全局函数的名字应当使用“动词”或者“动词+名词”(动宾词组),形式为DrawBox();
5)if语句条件的使用:
a.布尔变量与零值比较
bool flag;
if(flag)     //表示flag为真
if(!flag)     //表示flag为假
b.整型变量与零值比较
int flag;
if(flag==0)     //表示flag为真
if(flag!=0)     //表示flag为假
c.浮点变量与零值比较
float flag;
if((flag>=-EPSINON) && (flag<=EPSINON))  //EPSINON是允许的误差精度
d.指针变量与零值比较
int *p;
if(p == NULL) //p与NULL显式比较,强调p是指针变量
if(p != NULL)
6)#defined和const都是定义常量的关键字。const主要是C++新增的,其相对于#defined的好处的是定义的常量有数据类型,同时可以进行调试。
7)如果参数是指针,且仅作输入用,则应在类型前加 const ,以防止该指针在函数体内被意外修改。
例如:
void StringCopy(char *strDestination,const char *strSource);
8)return语句不可返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁;如果一定要使用内存,可以定义堆空间,这个不会自动释放,必须认为释放
9)内存分配的问题:
内存分配的三种方式:
a.从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量,常量
b.在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
c.从堆上分配。亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
内存分配的常见问题:
a.内存分配不成功:在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用malloc或new来申请内存,应该用if(p==NULL)或if(p! =NULL)进行防错处理
b.使用free或delete释放了内存后,没有将指针设置为 NULL。导致产生“野指针”
c.对于调用函数中的指针,跳出之后,指针也被释放了,但为了避免出现问题,最后是释放内存之后最好还是初始化为NULL
10)指针和数组
a.内容的复制:
//数组内容的复制
char a[]= "hello";
char b[10]={0};
 strcpy(b,a);
 if(strcmp(b,a)==0)
  ....
//指针内容的复制
char a[]= "hello";
char *p;
int len=0;
len= strlen(a);
p=(char *)malloc(sizeof(char)*(len+1));
strcpy(p,a);
if(strcmp(p,a)==0)
....
b.数组的理解
数组也可以理解为一个固定内存大小的变量,故:
int a[100];
sizeof(a);
计算是是整个数组的大小,但是在对数组进行操作时,由于其他的各种赋值函数都是使用int等进行操作的,故不能直接对数组进行赋值,必须采用strcpy()等函数  
11)关于C语言效率的问题
a.以空间换时间
b.数学方法解决问题,找寻规律
c.使用位操作,使用移位运算来代替四则运算
d.汇编嵌入
12)关于程序时间复杂度的计算
a.复杂度与时间效率的关系
c < log2n < n < n*log2n < n2 < n3 < 2n < 3n < n! (c是一个常量)
|--------------------------|--------------------------|-------------|
较好                     一般              较差
其中c是一个常量,如果一个算法的复杂度为c 、 log2n 、n 、 n*log2n,那么这个算法时间效率比较高 ,如果是 2n , 3n ,n!,那么稍微大一些的n就会令这个算法不能动了,居于中间的几个则差强人意。
b.计算时间复杂度的具体
⑴找出算法中的基本语句。算法中执行次数最多的那条语句就是基本语句,通常是最内层循环的循环体。
   ⑵计算基本语句的执行次数的数量级。只需计算基本语句执行次数的数量级,这就意味着只要保证基本语句执行次数的函数中的最高次幂正确即可,可以忽略所有低次幂和最高次幂的系数。这样能够简化算法分析,并且使注意力集中在最重要的一点上:增长率。
   ⑶用大Ο记号表示算法的时间性能。将基本语句执行次数的数量级放入大Ο记号中。

13)关于volatile关键字的理解
a.对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,而不会利用cache当中的原有数值(或者采用寄存器中的值),以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。
b.一般说来,volatile用在如下的几个地方:
1>中断服务程序中修改的供其它程序检测的变量需要加volatile;
2>多任务环境下各任务间共享的标志应该加volatile;
3>存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义;
另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2 中可以禁止任务调度,3中则只能依靠硬件的良好设计了。



  • 5
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值