码迷,mamicode.com
首页 > 编程语言 > 详细

C语言1——基础

时间:2015-12-08 23:49:41      阅读:400      评论:0      收藏:0      [点我收藏+]

标签:

1、gcc指令

  gcc -o a a.c  编译a.c文件,链接,生成名字为a的可执行文件

    上面的指令完整一点应该写成gcc -o a.exe a.c,即生成a.exe可执行文件

    但是在Windows系统下可执行文件的后缀是exe,但是在Linux系统下可执行文件的后缀是out

    当然,在Linux下也可以写gcc -o a.exe a.c,不会有任何错误,但是会生成一个a.exe文件,从名字上来看不是很好

  gcc -E -o a1.i a.c 预编译a.c文件,声称名字为a1的c源文件,因为预编译过程就是把类似#include<stdio.h>这样的头文件引入语句替换成头文件的内容

    用记事本打开a1文件,打开之后滚动到最下方会发现#include<stdio.h>没有了,被替换成了stdio.h的内容

    同时预编译后注释也会被删掉

  gcc -S -o a1.s a1.c 将预编译之后的a1.c编译为汇编语言程序

  gcc -c -o a1.o a1.s 将汇编语言程序a1.s编译成a1.o目标文件

  gcc -o a1.exe a1.o 链接生成最终的可执行程序

2、Unix和Linux是互相兼容的,但是和Windows是不兼容的

  Linux下写的C语言程序移植到Unix下几乎可以不做任何修改就可以跑

  但是Linux或者Unix下的C语言程序在windows上几乎不能跑

  windows下写的C语言程序在Linux或Unix下也不能跑

  windows对POSIX的支持很差,但是Linux和Unix对其支持非常好

  在写代码的过程中尽量调用POSIX里面的函数

3、引入头文件时,通过尖括号<>引入代表让C语言编译器去系统目录下寻找相关的头文件,通过双引号引入代表让C语言编译器去用户当前目录下寻找相关文件 

4、C语言是与汇编语言相兼容的

int a=1;
int b=2;
int c=a+b;
//将上面语句转换成汇编语言
//__asm加一个大括号就是嵌入一段汇编代码
__asm
{
    mov a,1
    mov b,2
    mov eax,a    将a放入寄存器
    add eax,b    a和b相加,再放入寄存器
    mov c,eax    
}

5、指令集

  RISC精简指令集 例如手机系统中的ARM指令集,UNIX中的指令集
  CISC复杂指令集 例如PC和笔记本电脑中的x86指令集,Linux是基于x86的操作系统

  复杂指令集包含所有的指令,包括常用指令和不常用指令
  20%的指令为常用指令,在一个程序执行的时候调用比例达到80%
  80%的指令为不常用指令,在一个程序执行的时候调用比例只有20%

  精简指令集只包含常用指令,复杂指令需要自己写程序实现

  Sun公司有自己的CPU,是基于SPARTC指令集的,其实就是一款RISC的CPU

6、操作系统的结构

  计算机内存分为两个区域,用户区域和内核区域
  操作系统、设备驱动程序运行在内核区域
  我们自己写的普通程序在用户区域
  32位操作系统最大内存为4G,操作系统占用1G,剩下的3G给用户

7、32位和64位系统区别

  CPU内部又可分为运算器、控制器、寄存器
  CPU里面有多个寄存器
  A B C D是8位寄存器
  AX BX CX DX是16位寄存器AX=AH+AL BX=BH+BL CX=CH+CL DX=DH+DL
  EAX EBX ECX EDX都是32位寄存器,这里的EAX就是上面的汇编代码中的EAX
  REAX REBX RECX REDX都是64位寄存器
  CPU的寄存器决定了CPU的位数,寄存器位数越大,同一时刻能够寄存的位数越大

  32位的CPU不能运行64位的操作系统,因为CPU的计算能力达不到64位
  64位的CPU可以运行32位的操作系统,因为可以向下兼容

8、C语言中的常量分为define定义的常量、const定义的常量、字符串常量

9、变量类型所占空间大小

  short  32位系统下         2字节

  long    32位系统下        4字节

       64位Windows系统下  4字节

         Unix系统下        8字节

  int     任何系统下         4字节

  long long 任何系统下         8字节  对于32位操作系统,CPU寄存器是32位,所以计算long long类型的数据,效率很低
  float    32位系统下        4字节
  double   32位系统下        8字节
 

10、赋值

  int i1=0x11111111; //0001 0001 0001 0001 0001 0001 0001 0001
  short abc1=-2; //1111 1111 1111 1110
  i1=abc1; //赋值时会判断abc1值的正负,如果是abc1是正数的话(例如值为2)会赋值为
        //0000 0000 0000 0000 0000 0000 0000 0010
        //如果是负数的话(例如值为-2)会赋值为
        //1111 1111 1111 1111 1111 1111 1111 1110
        //即小值赋给大值时,符号位不会丢失

11、大端对齐和小端对齐

  int a1=0x12345678;
  printf("%p\n",&a1);//0030F8E4
  但是我们通过查看内存可以看到在0030F8E4这块内存处是
  78 56 34 12 cc cc cc cc
  看起来好像是倒着放的

  对于ARM、Intel这种x86架构的复杂指令CPU,整数在内存中是倒着存放的,低地址放低位(7和8),高地址放高位(1和2),这就叫小端对齐
  但对于UNIX服务器的CPU,更多是采用大端对齐的方式存放整数

12、回车和换行

  \n是回车加换行的效果
  \r是换行
  回车是指回到当前行的开头,换行才会到下一行

13、volatile关键字

  volatile代表变量是一个可能被CPU指令之外的地方改变的

  volatile int i=100;告诉编译器,不要智能的针对变量i优化代码

  例如:

例如:
int i;
i=100;
i=i+50;
i=i+20;
//这段代码如果直接编译的话编译器会将其合并,成为
int i;
i=100;
i=i+70;
//但是我们有时候不希望编译器这样给我们智能的操作,这时可以写成
volatile int i;
i=100;
i=i+50;
i=i+20;

汇编语言在底层的操作:

int i;
i=100;
i=i+50;
i=i+20;
//上面这段程序如果翻译为汇编语言,即为
__asm{
    mov i,100
    mov eax,i
    //注意这样写是不可以的:add i,50,因为CPU中的运算必须要在寄存器里面进行,所以必须要将内存中的变量放到寄存器当中运算,运算完毕之后再将结果返回给内存
    add eax,50
    mov i,eax
    mov eax,i
    add eax,20
    mov i,eax
}
//但是这段汇编可以优化为
__asm{
    mov i,100
    mov eax,i
    add eax,50,
    add eax,20,
    mov i,eax
}

14、register

register int i;//register修饰了i之后,i就会直接在寄存器里面
i=100;
i=i+50;
i=i+20;
这样一来上面的这段代码在汇编过程中就可以转换为
__asm{
    mov eax,100
    add eax,50
    add eax,20
}
register是建议型的指令,而不是命令型的指令,如果CPU没有寄存器的话这个变量是不会被放入寄存器的

15、字符串格式化输入输出

字符串是内存中一段连续的char空间,以‘\0‘结尾

%hd short int
%hu unsigned short int
%f float/double
%p void *以16进制形式输出指针

将l附加在d u x o前代表输出长整数
%6ld
%-6ld 左对齐
%06ld 将前面用0补齐
注意0和-不能一起使用,例如%-06ld这样写会将0的作用忽略掉

16、去掉安全提示

在Visual Studio下如果用scanf的话需要,定义一个宏
#define _CRT_SECURE_NO_WARNINGS
或者用
#pragma warning(disable:4996)屏蔽掉VS里面4996这个错误

17、左值和右值

左值:表示可以被更改的数据对象
右值:能赋给左值的就是右值 右值通常是表达式

18、数组初始化

int array[2][3];//定义了一个二位数组,一个int的存储空间是4个字节,这个二维数组的存储空间占24个字节
二维数组初始化int array[2][3]={{1,2,3},{4,5,6}};
int array[2][3]={0};//让数组每个值都初始化为0

字符数组
char array[100]={‘a‘,‘b‘,‘c‘};//array[3]是0 长度为100
char array[100]="abc";//写法等同于上面
char array[100]={0};//初始化为空串
char array[]="abcd";//长度为5 即sizeof(array)为5
printf("%s\n",array);//abc

19、字符串

//字符串逆置
char str[100]="hello world";
int i=0;
while(str[i++]);//i最终的值是12,循环到\0就跳出循环
int min=0;
int max=i-2;
while(min<max){
    char tmp=str[min];
    str[min]=str[max];
    str[max]=tmp;
    min++;
    max--;
}

如果要逆置汉字字符串,这里如果还按照上面的方法来的话,就会出现乱码

char str[100]="你好世界";
因为此处的汉字是GBK编码的
GBK编码是用两个字节存放一个汉字
char buf[100]="你";//我们在内存中可以可以看到汉字"你"的存储方式是"c4e3"
其中c4是一个字节,e3是一个字节
如果按照上面的方式调换位置就变成了e3c4,所以就出现了乱码
通过以下程序打印出其长度也可以说明这个问题

char str[100]="";
int i=0;
while(str[i++]);
printf("%d\n",i);//得到3

所以汉字的反转代码如下

char s[100]="你好世界";
int len=0;
while(s[len++]);
int min=0;
int max=len-2;
while(min<max){
    char tmp=s[min];
    s[min]=s[max-1];
    s[max-1]=tmp;

    char tmp2=s[min+1];
    s[min+1]=s[max];
    s[max]=tmp2;

    min+=2;
    max-=2;
}

注:这段代码是不跨平台的,在Linux下又会出现乱码,因为Linux下的编码是UTF-8,而UTF-8中每个汉字占3个字节

20、字符串和字符数组

char s[5]={0};
s[0]=a;
s[1]=b;
s[2]=c;
s[3]=d;
printf("%s\n",s);//在这里printf得到的就是一个字符串,因为最后一个字符是0
//但是如果写成下面这样
char s[5]={0};
s[0]=a;
s[1]=b;
s[2]=c;
s[3]=d;
s[4]=e;
printf("%s\n",s);//在这里printf得到的将不再是字符串,而是一个字符数组,程序在读到s的第4个字符的时候发现不是字符串,那么程序将会继续向后读取直到读取到0为止,因此屏幕上除了输出abcde之外后面还会跟一堆乱码
char s[100]="hello world";
s[4]=0;
printf("%s\n",s);//hell 而对应的,如果在一个字符数组中间将某个字符改为0,该字符之后的字幕将都不会再显示

根据上面代码中的这个规律,我们可以将一个字符串右边的空格去掉

char s[100]="hello world";
int len=0;
while(s[len++]);
len--;//得到长度

int i=0;
//while(s[i]){
//    if(s[i]==‘ ‘){
//        s[i]=0;
//        //s[i]=‘\0‘;这种写法等价于s[i]=0
//        break;
//    }
//    i++;
//}
for(i=len-1;i>=0;i--){
    if(s[i]!= ){
        s[i+1]=0;
        break;
    }
}
printf("%s\n",s);

去掉字符串左边的空格思路有所不同

//去掉字符串左边空格
char s[100]="  hello";
int len=0;
while(s[len++]== );
len--;
int i=len;
while(s[i]){
    s[i-len]=s[i];
    i++;
}
s[i-len]=0;
printf("|%s|\n",s);

21、混合统计汉字与ASCII字符串字符数量

解决这个问题之前我们需要知道一些预备知识:ASCII码用%d输出之后都是正值,GBK和UTF-8表示的汉字第一个字节是负值

char buf[100]="你好abc";
int len=0;
int i=0;
while(buf[i]){
    if(buf[i]<0){
        i++;
    }
    i++;
    len++;
}
printf("%d\n",len);

22、随机数

rand()和srand() 需要引入#include<stdlib.h>
单用rand()是有问题的,需要配合srand()来使用
只要能保证每次调用srand函数的时候,参数的值是不同的,那么rand函数就一定会产生不同的随机数
想要让srand参数的值不同,可以通过传入时间来实现
想要在程序中用时间,必须引入时间库#include<time.h>

time_t tm=time(NULL);
srand(tm);//称为随机数种子发生器
printf("%d\n",rand());

23、字符串的输入输出操作

char s[100]={0};
scanf("%s",s);//scanf是以回车作为输入完成的标识,但是回车键本身并不会作为字符串的一部分
//如果scanf参数中的数组长度小于用户在键盘输入的长度,那么scanf就会缓冲区溢出,导致程序崩溃
printf("%s\n",s);

scanf将回车、空格都认为字符串输入结束的标志,可以用gets函数替代它

int main(){
    char s[100]={0};
    gets(s);//gets认为回车输入是结束标识而空格不是
    //gets和scanf一样,也存在缓存区溢出的问题
    int i;
    for(i=0;i<10;i+){
        printf("%d\n",s[i]);
    }
}

scanf和gets都是危险的,fgets函数是安全的
fgets(s,100,stdin);参数分别是char数组 数组缓冲区大小 标准输入
调用fgets的时候只要能保证第二个参数小于数组实际的大小那么就可以避免缓冲区溢出的问题
第二个参数通常可以写成sizeof(s)-1

puts函数和printf类似,用来打印字符串
char s[100]={"hello world"};
puts(s);//puts函数自动会输出完成之后打印一个‘\n‘

fputs是puts的文件操作版本
char s[100]={"hello world"};
fputs(s,stdout);

24、字符串转化为整型

char a[100]={0};
char b[100]={0};
gets(a);
gets(b);
int i1=atoi(a);//将字符串转化为一个整数
int i2=atoi(b);//atoi函数需要引入stdlib库
printf("%d\n",i1+i2);

25、字符串操作库函数(需要引入string.h)

int main(){
    char s[100]="hello world";
    int len=strlen(s);//这个长度不包含结尾的\0

    //很多函数如果确认不会返回小于0的值,那么就会使用size_t类型,例如strlen sizeof就是返回size_t类型的,size_t其实就是unsigned int

    char s1[100]="abc";
    strcat(s,s1);//这个函数需要加上忽略安全检查宏定义
    //之所以要加忽略安全检查,是因为当s的长度很小而s1很大时,将s1全部追加到s后面,s会放不下
    //strncat可以取代strcat这个方法从而规避安全的问题,例如strncat(s,s1,6);只将s1的6个字符追加给s
    printf("%s\n",s);

    //strcmp 字符串比较
    //注意:不能通过str1==str2来比较字符串
    if(strcmp(s1,s2)==0){//如果strcmp的返回值为0,代表参数中的两个字符串内容相同,反之不相同
        printf("s1和s2相同");
    }

    //strncmp(s1,s2,5)比较s1和s2的前5个字符

    //strcpy(s1,s2);将s2的内容拷贝到s1 条件是s2的长度一定要小于s1

    //char s1[100]="123456";
    //char s2[100]="abcdef";
    //strncpy(s1,s2,3);将s2的前三个字符拷贝到s1 s1变成了abc456 即只有前三个字符被覆盖了 后面的字符没有被覆盖

    //int i=100;
    //sprintf(s,"i=%d",i); 将格式化后的字符串赋给s 执行完这条语句后字符串s就变成了"i=100"

    sprintf(s,"%d",i);//将整型的i转变为字符串赋给s
    //注意:atoi是将字符串转换为整型 虽然有itoa这个函数 但itoa()不是标准C语言库函数 但是atoi()是标准的C语言库函数

    char s[100]="5+6=";
    int a=0;
    int b=0;
    sscanf(s,"%d+%d",&a,&b);//比scanf多了一个参数,a和b就对应5和6,意思就是将5和6赋值给a和b
    printf("%d\n",a+b);

    //注:字符串初始化完成之后就不可以再通过等号赋值了
    //例如:char s[100]="abcdef";
    //s="ghijkl"; 这样做是错误的
    //可以通过strcpy来赋值 strcpy(s,"ghijkl");

    const char *buf=strchr(s,o);//返回一个指针 strchr(s,‘o‘);代表在s这个字符串中间查找第二个参数指定的字符,如果找到的话返回从该字符开始往后的字符串,找不到返回null
    printf("%s\n",buf);

    buf=strstr(s,"ll");//功能和返回值类似strchr 注意第二个参数是字符串,而不是字符,将返回"ll"后面的部分,包含"ll"
    strcpy(s,"abc_def_ghi");//如果希望返回abc def ghi,即以_为分隔符
    buf=strtok(s,"_");
    printf("%s\n",buf);//abc
    buf=strtok(NULL,"_");//第二次调用的时候第一个参数要传为NULL
    printf("%s\n",buf);//def
    buf=strtok(NULL,"_");
    printf("%s\n",buf);//ghi

    //将上述代码用循环代替
    buf=strtok(s,"_");
    while(buf){
        printf("%s\n",buf);
        buf=strtok(NULL,"_");
    }

    //atoi 将字符串转换成整数 需要引入stdlib库函数
    //atof
    //atol
    char s[100]="200";
    int i=atoi(s);
    printf("i=%d\n",i);
}

26、自己实现字符串转数字

//字符串转换成整型 类似parseInt
//1 要知道字符串有多长
//2 将字符串每个字符读取出来 转换为整数后 乘以10的 长度减1次方
//3 将每个位计算和加起来就是转化后的结果
char s[100]={5,8,1};
int len=0;
while(s[len++]);
len--;

int value=0;
int i=0;
int tmp=len;  //3
for(i=0;i<len;i++){
    int base=10;
    if((tmp-i-1)==0){
        base=1;
    }else{
        int j;
        for(j=1;j<tmp-i-1;j++){
            base*=10;
        }
    }
    value+=(base*(s[i]-‘0‘));
}

27、自己实现数字转字符串

void myitoa(int n,char s[]){
    int status=0;//0代表正数,1代表负数
    if(n<0){
        status=1;
        n=0-n;
    }
    int i=0;
    while(n){
        int a=n%10;//取出整数的个位
        char c=a+0;//将整数转化为字符
        s[i]=c;//将转化后的char依次放入字符串s中
        i++;
        n/=10;
    }
    int min=0;
    int max=i-1;
    while(min<max){
        char tmp=s[min]
        s[min]=s[max];
        s[max]=tmp;
        min++;
        max--;
    }
    if(status==1){//负数
        int len=0;
        for(len=i-1;len>=0;len--){
            s[len+1]=s[len];
        }
        s[0]=-;
    }
}
int main(){
    int i=456;
    char s[100]={0};
    myitoa(i,s);
    printf("");
}

28、宏定义注意事项

写一个头文件供别的文件引用时,一定要加上#ifndef和#endif,否则,当我们多次预编译主文件过后,函数的声明就会引入多次
#ifndef __AH__ //如果没有__AH__这个宏,就编译#ifndef __AH__和#endif之间的代码,如果有的话就不编译了
#define __AH__ //具体宏的名字是自定义的
int max(int a,int b);
int add(int a,int b);
#endif

define定义的宏,习惯大写加下划线

29、进制转换

//==============10进制转换为2进制
void bin(int n){
    int i=n%2;
    if(n>0){
        bin(n/2);
        printf("%d\n",i);//后续递归
    }
}
//==============10进制转换为16进制
void hex(int n){
    int i=n%16;
    if(n>0){
        bin(n/16);
        printf("%c\n",hex1(i));//后续递归  hex1函数主要处理大于9的数,对于小于等于9的数,直接输出对应字符串
    }
}

30、递归得到字符串长度

int mystrlen(char s[],int n){
    if(s[n]){ //当循环到字符串最后的\0时,条件为假
        return mystrlen(s,s[n+1]); //这里如果不加return的话VS编译器有可能会有警告
    }else{
        return n;
    }
}
//调用的时候通过mystrlen("aaa",0);

 

C语言1——基础

标签:

原文地址:http://www.cnblogs.com/zhaohuiziwo901/p/5022569.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!