C语言笔记

    • 琐碎知识点
    • 指针相关
      • sizeof和strlen的区别
      • 字符指针
        • 字符指针创建
        • 用指针进行字符修改
      • 指针大小
      • 指针运算
        • 与自增自减运算符结合
      • 二维数组指针数组数组指针
      • 函数指针与指针函数
      • typedef
    • 结构
      • 结构声明
      • -运算符与等结合使用
    • 联合
      • 联合的大小
    • 位字段
    • 输入与输出
      • sprintf
      • 变长参数表
      • scanf格式化输入
    • 文件操作
    • stringh
      • 实用的切割字符串strtok
    • stdlibh字符串转换随机数生成内存分配其他
      • 实用的qsort和bsearch

琐碎知识点

  1. %d,有符号整型数,字节数看机器,本机4字节

    printf("%d\n",0x7fffffff);//2147483647 2e31-1 32位 4字节
    printf("%d\n",0xffffffff);//-1 最高位是符号位

    C语言笔记_第1张图片

    此外,
    %ld long类型
    %lld longlong类型
    宽度和精度可以用 * 表示,如:
    printf("%.*s", max, s);
    其中max为int类型,表示从字符串s中打印最多max个字符。
    printf("%m.nf",xxx)表示**一共最小**m列,小数点后面n位。
    .表示之前一共最小是之前的那个整数,不够则用空格补上,-是右边补空格。多了则无影响。

    printf返回值为打印的字符数。

    printf使用第一个参数判断后面参数的个数及类型。
    需注意:

    printf(s); /* FAILS if s contains % */
    printf("%s", s); /* SAFE */
  2. 返回值非int的函数用前需声明(除非函数声明在使用之前)。
  3. 如果函数返回值大于1个,可以考虑传入待求值地址的指针,在函数体内对指针指向的数据进行赋值,来达到“返回”多个值的目的。
  4. 本机64位,int 4; long 4; long long 8;
  5. 变量名不占空间,用来标识某个指定了大小的内存块。
  6. 指针名、数组名、函数名、结构名就是地址,它们分别表示指针所指向元素的地址、数组的首地址、函数的入口地址、结构的入口地址。
  7. 类和结构只有事例化时才为它分配空间,从而不能用取地址符号&来获得类名或结构名的地址。
  8. 指针取指向的值运算符‘*’优先级低于:结构运算符‘.’和‘->’、下标符‘[]’、函数调用符‘()’。这四个同时出现遵循从左至右的结合原则。其他优先级见:百度百科
  9. 结构长度不等于结构各成员长度的和。因为不同对象有不同的对齐要求,所以,结构中可能会出现未命名的“空穴。”
  10. 字符指针数组长度获取方法:
    sizeof(argv)/sizeof(char*))
  11. char *p 和char a[20]是不同的,前者p指针变量本身在栈上,指向的内容仅为一个字符;a只是个数组名,本身不占运行时程序的空间,只是在源程序中用来标记一个字符数组(即其首地址),而数组也是存储在栈上的。
  12. 文件目录路径获取方法:

    include
    main()
    {
    char buffer[100];
    printf("%s",getcwd(buffer,100));
    }
  13. 递归时,函数不断加到栈中,处理不好可以会栈溢出(常常是因为基线条件没弄好,不然很少发生)。
  14. 堆栈申请方式:
    • stack: 由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间
    • heap: 需要程序员自己申请,并指明大小,
      • 在c中malloc函数,如p1 = (char *)malloc(10);
      • 在C++中用new运算符,如p2 = new char;
        但是注意p1、p2本身是在栈中的。
  15. for循环里面最后用++i效率比i++高,因为后者还要保存原值。
  16. const的修饰问题:
    如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向的为常量;如果const位于星号的右侧,则const修饰的是指针本身,即指针本身是常量
  17. 非常量作为常量函数的参数会编译出错;反之可以。这意味着写函数时,形参尽可能加上const,好让调用时无论是常量还是非常量都编译通过。

指针相关

sizeof和strlen的区别:

  • strlen通过’\0’识别数组的结束,返回元素个数;sizeof通过分配的内存,返回数组字节数,而不是指向数组的指针的长度;
  • strlen编译阶段不处理;sizeof是关键字,是在编译阶段处理的,也就是说程序没有运行前,sizeof(arr)就被替换成了一个固定的常量,保存在了.out文件中了。
char str[20]="0123456789"
int aa=strlen(str);
int bb=sizeof str;
printf("aa=%d\n",aa); //aa=10
printf("bb=%d\n",bb); //bb=20

char str1[]="0123456789"
int cc=sizeof str1;
printf("cc=%d\n",cc); //bb=11,最后多个'\0'

int array[20]={1,2,3,4};
int ai=strlen(array); //实质上没有意义,但可以看看结果
int bi=sizeof array;  //可以不加括号,因为sizeof不是函数,而是编译器提供的操作符
printf("ai=%d\n",ai); //ai=1,1的第二个字节为0,等于字符串结束符'\0'
printf("bi=%d\n",bi); //bi=80,内存总共分配的字节数
printf("size=%d\n",bi/sizeof(array[0]));//数组个数size=20

字符指针

  • C语言没有提供将整个字符串作为一个整体进行处理的运算符。(K&R. P89)

字符指针创建

char *p = "how are you doing?";
printf("p=%d\n",p);  // p=4210824,首元素'h'地址
printf("*p=%c\n",*p);//*p=h,首元素'h'

用指针进行字符修改

char ap[] = "how are you doing?";
*(ap+1)='w';//或者 ap[1]='w';
printf("ap[1]=%c\n",ap[1]);// ap[1]=w,修改成功

char *p = "how are you doing?";
*(p+1)='w'; //运行到此停住了,修改不了
printf("*(p+1)=%c\n",*(p+1));

第一种方式:
"how are you doing?"保存在栈空间数组里,数组名为ap,函数名为数组的首地址。可以通过*(ap+1)='w';或者 ap[1]='w';的形式来修改数组内容。

第二种方式:
char *p = "how are you doing?";
"how are you doing?"保存在文字常量区,该数据不能修改,默认有只读属性。由指针p指向,不能通过指针p来修改此值。

  • void类型的指针可以存放指向任何类型的指针,但它不能间接引用其自身。(K&R. P80)
  • C语言是以传值的方式将参数值传递给被调用函数,因此,被调用函数不能直接修改主调函数中变量的值。(K&R. P81)
//无效的swap函数
void swap(int a, int b)
{
    int temp = a;
    a = b;
    b = temp;
}

//有效的swap函数
void swap_p(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

指针大小

32位机4字节;64位机8字节

char ap[] = "how";
printf("%d\n",sizeof(ap));     //4,数组大小
printf("%d\n",sizeof(&ap[0])); //8,指针大小,获取第一位指针,不能直接用ap来获取指针大小
printf("%d\n",sizeof(ap+1));   //8,指针大小

指针运算

  • 指针加1,结果是对该指针增加1个储存单位—-指针指向的数据类型所占的内存的字节数。不同类型的指针加1后,增加的大小不同。
  • 对于二维数组,创建时列数必须声明,行数可以不用(可以理解为一维数组默认列数为1,故不用声明)。
char arr[][3] = {{1,2,3},{4,5,6}};
printf("arr=%d\n",arr);                  //10485120,arr指向首元素,即arr[0],此处为arr[0]地址
printf("*arr=%d\n",*arr);                //10485120,arr指向首元素,即arr[0],*arr取出其指向的元素,即arr[0]的值
printf("*arr+1=%d\n",*arr+1);            //10485121,arr指向首元素,即arr[0],*arr取出其指向的元素,即arr[0]的值,arr[0]的值也是指针,指向1字节数据,故*arr存储单位为1字节,下一个对象的地址+1
printf("&arr[0][0]+1=%d\n",&arr[0][0]+1);//10485121,整个arr[0][0]为整体(存储单位:1字节),下一个对象的地址+1
printf("&arr[0]+1=%d\n",&arr[0]+1);      //10485123,整个arr[0]为整体(存储单位:3字节),下一个对象的地址+3
printf("arr+1=%d\n",arr+1);              //10485123,arr指向首元素,即arr[0](存储单位:3字节),下一个对象的地址+3
printf("&arr+1=%d\n",&arr+1);            //10485126,整个arr为整体(存储单位:6字节),下一个对象的地址+6
printf("**arr=%d\n",**arr);              // 1
printf("*(*arr+1)=%d\n",*(*arr+1));      // 2
printf("**(arr+1)=%d\n",**(arr+1));      // 4

与自增/自减运算符结合

  • 类似于* 和 ++ – 这样的一元运算符遵循从右至左的结合顺序。(K&R. P80)

    ++*p等价于(*p)++


二维数组,指针数组,数组指针

int a[][3] = {{1,2,3},{4,5,6}};//a是二维数组,内存中连续排列
int (*p)[3] = a;//p是数组指针,指向int[3]类型数据(int3时,共12字节)
printf("%d\n",sizeof(*p));// 12
printf("%d\n",p); // ....296,首地址
printf("%d\n",*p);// ....296,首地址
printf("%d\n",sizeof(*(p+1)));// 12
printf("%d\n",p+1);//....308,加上sizeof(*p)的大小
printf("%d\n",sizeof(*(p+2)));// 12,不管该处地址是否真的有数据,进行加减法时统一乘上指针指向数据大小(12printf("%d\n",(*p)[2]);   // 3
printf("%d\n",(*(p+1))[2]);// 6,注意括号,因为[]优先级高于*

再来看看指针数组:

int a[][3] = {{1,2,3},{4,5,6}};//a是二维数组,内存中连续排列
int *q[3] = {&a[0][0],&a[0][1],&a[0][2]};//等价于 int *(q[3]); q是指针数组,每个指针指向int型数据
printf("%d\n",q); // ....264
printf("%d\n",sizeof q); // 24=3*8(每个指针大小为8),
printf("%d\n",q[0]); // ...296,存1的地址
printf("%d\n",q[1]); // ...300,存2的地址
printf("%d\n",*(q[2])); // 3
printf("%d\n",**(q+2)); // 3

264+24=288,与296中间隔了8字节,不知道是啥。跳过中间8字节,可以直接访问到a数组(虽然有点无厘头…):

printf("%d\n",sizeof *(q+4)); //8
printf("%d\n",*(q+4)); //1,访问到了
printf("%d\n",*(q+5)); //3,加一以8字节为单位,故跳过了2
printf("%ld\n",*(q+5)); //还是3,原因是本机long也是4字节,故也只读了4个
printf("%d\n",*(*(q+2)+1)); // 4
printf("%d\n",*(q+2)+1); //....308
}

那怎么访问到2?

强制转换!!!
由于本机long也是4字节,故先将指针提取到的值转换成long long类型(8字节),再右移4字节,提取到高4位,也就是2。

printf("%d\n",((long long)*(q+4))>>32); //2

由于q是指针数组,即*q是指针,故*(q+4)也是指针,虽然我们知道q+4后指向的并不是指针,但在编译时编译器不知道,会报错:

error: invalid operands to binary >> (have ‘int *’ and ‘int’)

故需要强制转换成long long类型通过编译器检查,好进行非法的内存访问…


函数指针与指针函数

  • 函数名同数组名一样指代地址,前面不用加取地址运算符‘&’.(K&R. P103)
    *优先级低于(),本质就看变量与哪个先结合。
#include

int f1(void)
{
    printf("f1 was called!\n");
    return 1;
}

int* f2(void)
{
    int i = 1;
    printf("f2 was called!\n");
    return &i;
}

main()
{
    int f1(void); //函数指针
    int (*p)(void);//p为 指向返回值为int的函数 的指针
    p = f1;//f1是函数名,表示函数在内存中的地址
    p();  //调用函数

    int *f2(void);//指针函数,f2为 返回值为指向int型数据的指针 的函数
    f2();//调用函数
}

typedef

为一种数据类型定义一个新名字。这里的数据类型包括内部数据类型(int,char等)和自定义的数据类型(struct等)。详见:百度百科


结构

结构声明

#include 

main()
{
    struct point{
        int x;
        int y;
    };

    struct point p1 = {1,2};
    struct {
        int x;
        int y;
    } p2 = {2,3};

    printf("p1:x=%d,y=%d\n",p1.x,p1.y);
    printf("p2:x=%d,y=%d\n",p2.x,p2.y);
}

point 是 结构标记,等价于花括号内内容(含花括号)。
struct声明定义了一种数据类型,故也可以用typedef为此类型起别名:

typedef struct point{
    int x;
    int y;
} Point;
Point p3 = {3,4};
printf("p3:x=%d,y=%d\n",p3.x,p3.y);

此处类型struct point 等价于Point

C中没有像java中那样的String类型,可以自己定义,如下是几种定义方式,分别使用了typedef和define

//typedef 对结构类型重命名
typedef struct String{
    char *value;
} String1;
String1 str1 = {"string"};
printf("%s\n",str.value);

//typedef 对字符指针类型重命名
typedef char* String2;
String2 str2 = {"string"};
printf("%s\n",str2);

//define 给文本段起别名,编译时替换(较高级的替换,若带参数也会替换)
#define String3 char*
...
String3 str3 = "string";
printf("%s\n",str3);

->运算符与++、*等结合使用

先定义结构实例origin及指向origin的指针p,并初始化实例内成员值。

struct {
    int x;
    char *str;
} origin,*p=&origin;

//两种指针访问结构内部成员方式
(*p).x=1;
p->str="string";

//x=1, str=string
printf("x=%d, str=%s\n",p->x,p->str);
  • 测试1:
printf("x=%d, first_str=%c\n",p->x,*p->str++);//x=1, str=s
printf("x=%d, str=%s\n",p->x,p->str);//str=tring

第一个printf:先读取指针str指向的字符,再对str进行++,而不是读到的字符;
第二个printf:str++后指向的首字符为t,以字符串输出(后移输出直至’\0’)。

  • 测试2:
printf("x=%d, str=%c\n",p->x,(*p->str)++);
printf("x=%d, str=%s\n",p->x,p->str);

运行后程序无反应,说明有问题。
第一个printf里,++对象是str指向的字符串,该字符串存于常量区,故不能进行修改操作,++操作不能进行;
让str指向可以修改的字符串就行了:

char string[] = "string";//栈区分配内存来创建字符数组
p->str=string;//指向栈区的字符数组,就能进行修改了。

输出结果为:

x=1, first_char=s
x=1, str=ttring
  • 测试3
printf("x=%d, first_char=%c\n",p->x,*p++->str);//x=0, str=s
printf("x=%d, str=%s\n",p->x,p->str);//x=0, str=

先读取指针str指向的对象的值,*p++->str语句结束后p执行+1,故p->x为0(未定义值)。后面的输出也都是没有定义的值。

联合

  • 联合就是一个结构,它的所有成员相对于基地址的偏移量都为0,此结构空间要大到足够容纳最宽的成员,并且,其对齐方式要适合于联合中所有类型的成员。(K&R. P130)
  • 联合只能用其第一个成员类型的值进行初始化。(为什么?)(K&R. P130)

联合的大小

猜测是最大的数据类型所占内存。

#include
union var{
        long long l;//本机8字节
        int i;
}
main(){
        union var v;
        v.i = 5;
        printf("v size is %d\n",sizeof v);//8
        printf("v.l is %d\n",v.l);//5
}

位字段

机器字:指计算机进行一次整数运算所能处理的二进制数据的位数。

一个位域必须存储在同一个机器字中,不能跨两个机器字。如一个机器字所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:

#include
struct {
    unsigned int is_a :1;
    unsigned int is_b :20;
    unsigned int is_c :20;
} flags;
main(){
    printf("v size is %d\n",sizeof flags);
}

结果是8,is_c无法存放于32-21=9位中,故存于另一个机器字(本机一个机器字为32位)中。

#include
struct {
    unsigned int is_a :1;
    unsigned int :0;
    unsigned int is_b :20;
    unsigned int is_c :20;
} flags;
main(){
    printf("v size is %d\n",sizeof flags);
}

结果是12,这里用无名字段特殊字符0强制在下一个机器字边界上对齐。


输入与输出

字符读取来源可以不是从键盘,也可以通过:
1. 文件,如下:
test.exe < lines.txt
此时,不包含在argv的命令行参数中,argc=1,sizeof(argv)/sizeof(char*))=1(为test.exe)

#include 

main()
{
    int c;
    while((c = getchar())!=EOF)
        printf("%c",c);
}
  1. 管道,如下:
    from.c:
#include 
main()
{
    printf("output from another program");
}

test.c:

#include 

main()
{
    int c;
    printf("it's ");
    while((c = getchar())!=EOF)
        printf("%c",c);
}

运行:from.exe | test.exe
得到:
it's output from another program
from.exe的输出没有直接出来,而是通过管道重定向给了test.exe。

sprintf

转换方式和printf一样,但将输出保存到一个字符串中。
int sprintf(char *string, char *format, arg1, arg2, ...);

变长参数表

标准头文件中包含一组宏定义,他们对如何遍历参数表进行了定义。va_list ap 将使ap依次引用各参数参数。
va_start用于初始化ap,使其指向首个无名参数。
调用va_arg,返回ap指向的一个指定类型的参数,并将ap指向下一个参数。

#include 
#include 
void minprintf(char *fmt,...)
{
    va_list ap;
    char *p, *sval;
    int ival;
    double dval;

    va_start(ap, fmt);
    for (p = fmt; *p; p++){
        if (*p != '%'){
            putchar(*p);
            continue;
        }
        switch(*++p){
        case 'd':
            ival = va_arg(ap, int);
            printf("%d",ival);
            break;
        case 'f':
            dval = va_arg(ap, double);
            printf("%f", dval);
            break;
        case 's':
            for (sval = va_arg(ap, char *); *sval; sval++)
                putchar(*sval);
            break;
        default:
            putchar(*p);
            break;
        }
    }
    va_end(ap);
}
main()
{
    void minprintf(char* fmt,...);
    minprintf("%d,%s",44,"string");
}

scanf(格式化输入)

与printf(格式化输出对应)
C语言笔记_第2张图片
参数必须是指针。
返回值为成功匹配并赋值的输入项个数。

#include 

main()
{
    double sum, v;
    sum = 0;
    while (scanf("%lf", &v) == 1)//表示将输入的字符串(以'\t','\n',' '等作为字符串结束标志)以double形式保存到地址&v中
        printf("\t%.2lf\n", sum += v);
}

sscanf用于从一个字符串中读取字符序列,而不是键盘或其他方式:
int sscanf(char *string, char *format, arg1, arg2, ...)
可以用于检测某个string是否符合某个标准格式。


文件操作

fopen若文件读取失败,返回NULL
1. putc 和 getc(文件的单字符输入输出)
int getc(FILE *fp)从文件返回下一个字符。
int putc(int c, FILE *fp)将c写入文件,返回写入的字符。

以下将read.txt文件内容转换为大写后保存到write.txt中

#include 
#include 

main()
{
    FILE *fp_r;
    FILE *fp_w;
    fp_r = fopen("read.txt","r");
    fp_w = fopen("write.txt","w");

    int c;
    while((c=getc(fp_r))!=EOF){
        printf("%c",c);
        putc(toupper(c),fp_w);
    }
}
  1. fscanf 和fprintf (文件的格式化输入输出)
    int fscanf(FILE *fp, char *fmt, ...)若为文件末尾,返回EOF
    int fprintf(FILE *fp, char *fmt, ...)
  2. fgets和fputs(文件的行输入与行输出)
    出错!
#include 

main()
{
    FILE *fp_r;
    FILE *fp_w;
    fp_r = fopen("read.txt","r");
    fp_w = fopen("write.txt","w");
    char *str;
    char *c;

    while((c=fgets(str,1000,fp_r))!=NULL){
        printf("%s",str);
        fputs(str,fp_w);
    }
}
函数 描述 正常返回值 异常返回值
fputs 将字符串写入文件 非负值 EOF
fgets 从文件读取一行到字符数组中 字符数组指针 NULL
ferror 文件流是否出错 否:0 是:非0值
foef 文件流是否达到结尾 否:0 是:非0值

string.h

  • 拷贝字符串:strcpy
  • 前缀长度函数:strspn
  • 查找字符串B中任意字符在字符串A中所在位置函数:strpbrk,可以用于得到某个字符之后的所有字符printf("%s",strpbrk("qqqwer","w"));得到wer
  • 查找字符串B在字符串A中所在位置:strstr

指定操作长度的函数:

  • 从字符串B拷贝指定长度字符到字符串A:memmove

    
    #include 
    
    
    #include 
    
    main()
    {
        char p[]="qqqwer";
        printf("%s",(char *)memmove(p,"w",1));//wqqwer
    }
    

    注:字符串A不能是位于常量区不可修改的字符串

  • 比较前n个字符:memcmp

  • 在字符串的前n个字符里查找某个字符:memchr
  • 将字符串的前n个字符替统一换为某个字符:memset

实用的切割字符串strtok

char *strtok(char *s, const char *delim)
s为要分解的字符,delim为分隔符字符(如果传入字符串,则传入的字符串中每个字符均为分割符)。首次调用时,s指向要分解的字符串,之后再次调用要把s设成NULL。
注:原来的string会变掉。

#include 
#include 

main()
{
    char str[100] = "abc,d,e|f";
    char *sep = ",|";
    char *p = strtok(str,sep);
    while(p != NULL){
        printf("%s\n",p);
        p = strtok(NULL,sep);
    }
    printf("final str is %s",str);
}

stdlib.h(字符串转换+随机数生成+内存分配+其他)

字符串转换成其他类型

  • atof:->double
  • atoi:->int
  • atol:->long

随机数生成

  • rand:0-RAND_MAX
  • srand:重设种子

内存分配

  • calloc:n*size数组分配
  • malloc:size分配

其他

  • atexit:登记一个函数,在程序正常终止时调用。
  • system:将字符串传递给执行环境,如:
    system("echo hhh");
  • getenv:获得某个name相关的环境变量字符串,如:
    printf("%s",getenv("classpath"));

实用的qsort和bsearch

  • qsort:快排

    void qsort(void *base,int n,int size,int (*cmp)(const void *,const void *));

  • bsearch:二分查找

    void *bsearch(const void *key, const void *base, size_t nmem, size_t size, int (*cmp)(const void *, const void *));第一个参数必须是某个待比较值的指针,不能是某个待比较值。

#include 
#include 

int cmp(const void *a, void *b){
    return *(int *)a-*(int *)b;
}
main()
{
    int arr[] = {3,5,4,2,1};
    qsort(arr, sizeof(arr)/sizeof(int), sizeof(int), cmp);

    int i;
    for (i=0; i<5; i++)
        printf("%d ",arr[i]);

    int *res,key = 3;
    res = (int*)bsearch(&key,arr,5,sizeof(int),cmp);
    printf("\n二分查找3结果为:%d",res==NULL?-1:*res);
    key = 7;
    res = (int*)bsearch(&key,arr,5,sizeof(int),cmp);
    printf("\n二分查找7结果为:%d",res==NULL?-1:*res);
}
1 2 3 4 5
二分查找3结果为:3
二分查找7结果为:-1

复杂结构的排序:

#include 
#include 
#include 
typedef struct
{
    char name[6];
    int score;
}Student;

int cmp_name(const void *a, void *b){
    return strcmp(((Student *)a)->name,((Student *)b)->name);
}

int cmp_score(const void *a, void *b){
    return ((Student *)a)->score-((Student *)b)->score;
}
main()
{
    Student s[5] =
    {
        "Zhang", 94,
        "Wu",89,
        "Wei",88,
        "Li",99,
        "Fang",90
    };
    int i;

    printf("sort by name:\n");
    qsort(s, 5, sizeof(Student), cmp_name);
    for (i=0; i<5; i++)
        printf("%-6s %d\n",s[i].name,s[i].score);

    printf("\nsort by score:\n");
    qsort(s, 5, sizeof(Student), cmp_score);
    for (i=0; i<5; i++)
        printf("%-6s %d\n",s[i].name,s[i].score);
}
sort by name:
Fang   90
Li     99
Wei    88
Wu     89
Zhang  94

sort by score:
Wei    88
Wu     89
Fang   90
Zhang  94
Li     99

你可能感兴趣的:(c语言)