欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Pointers On C (C和指针)读书笔记

程序员文章站 2022-03-11 20:11:47
...
gcc -Dname=stuff main.c -o main
例:gcc -DSize=100 main.c -o main
编译时取消定义:
gcc -Uname main.c -o main
 
void *memcpy(void *dst, void const *src, size_t length);
void *memmove(void *dst, void const *src, size_t length);
void *memcpy(void *dst, void const *src, size_t length);
void *memchr(void const *a, int ch, size_t length);
void *memset(void *a, int ch, size_t length);
任何类型的指针都可以转换为void*坟墓里指针。
 
union{
    int a;
    float b;
    char c[4];

} x = {5};
 

1.如今软件开销的最大之处并非在于编写,而是在于维护。注释如果不正确,那还不如没有。

 

2.一个经常遇到的问题是:为什么ch被声明为整型,而我们事实上需要它来读取字符?答案是EOF是一个整型,它的位数比字符类型要多,把ch声明为整型可以防止从输入读取的字符意外地被解释为EOF。

 

int ch
while((ch = getchar()) != EOF)
{

}

 

3.字符和字符传搜索函数

#include <string.h>
strchr(str, 'char');//搜索字符函数,成功返回指针,否则返回NULL
strstr(str, str_to_find);//搜索字符串函数

 

4.空白字符:空格、水平制表符、垂直制表符和格式反馈字符、换行字符。因为它们被打印出来时,在页面上出现的是空白而不是各种记号。


5.三字母词:??> =} ??< 等等

6.limits.h头文件中有INT_MAX等的定义。

7.浮点数:浮点数家族包括float、double和long double类型。标准同时规定了一个最小范围:所有浮点类型至少能够容纳从10^-37到10^37。
  头文件float.h中定义了名字FLT_MAX,DBL_MAX,和LDBL_MAX,分别表示float、double和long double所能存储的最大值。

8.C数组另一个值得关注的地方是,编译器并不检查程序对数组下标的引用是否在数组的合法范围内。

9.两个相同的字符串常量是否放在一个地址上。??????


10.编译器可以确认4种不同的作用域--文件作用域、函数作用域、代码块作用域和原型作用域。

11.链接属性一共有3种--external、internal和none。没有链接属性的标识符总是被当作单独的个体,也就是说该标识符的多个声明被当作独立不同的实体。属于internal链接属性的标识符在同一个源文件内的所有声明中都指同一个实体,但位于不同源文件的多个声明则分属不同的实体。最后,属于external属性的标识符不论声明多少次、位于几个源文件都表示同一个实体。

12.static只对缺省链接属性为external属性的声明才有改变链接属性的效果。
   当extern关键字用于源文件中一个标识符的第1次声明时,它指定该标识符具有external链接属性。但是,如果它用于该标识符的第2次声明时,它并不会更改由第1 次声明所指定的链接属性。


13.注意,修改变量的存储类型并不表示修改该变量的作用域。例如,函数内部定义的static变量。

14.如果不显示地指定其初始值,静态变量将初始化为0.

15.在每个switch语句中都放上一条default子句是个好习惯,因为这样做可以检测到任何非法值。

16.用flag替代goto这个技巧能实现退出所有循环的目的,但情况被弄的非常复杂。可以把循环放到函数中去。

17.赋值是表达式的一种而不是某种类型的语句。C语言没有赋值语句,只有表达式语句。

18.(类型)操作符被称为强制类型转换(cast).

19. sizeof 为单目运算符。sizoef(a = b + 1),由于判断表达式的长度并需要对表达式进行求值。所以没有对表达式求值。

20. ++a = 10 ;//错误的语句
    ++a的结果是a值的拷贝,并不是变量本身,你无法向一个值进行赋值。

21. 表达式求值时用的是短路求值。(short-circuited evalutation)

22. 条件表达式可以用来减少代码行。

 if ( a > 5 )
          b = 3;
 else
          b = -20;
 ==>  b = a > 5? 3 : -20;

 

 23.下标引用其实是一个操作符。

24.整型提升(integral Promotion)

25.复杂表达式的求值顺序由3个因素决定:操作符的优先级、操作符的结合性以及操作符是否控制执行的顺序。有四个操作拊,它们可以对整个表达式的求值顺序施加控制。它们是:
    '&&' ,    '||'  ,  '? : ' , ','


26.在unix中出现总线错误(bus error)很可能是有未初始化指针。

27.多维数组的用作函数参数:

void fun(int (*mat)[10])
或
void fun(int mat[][10])
但是这样是错的:
void fun(int **mat);//把mat声明为一个指向整型指针的指针。

 

28.strlen 返回size_t类型,因为size_t是unsigned int型的所以当有两个strlen相减时,很可能出错。
(strlen(str1) - strlen(str2) >=0)会永远为真。

29.strcpy和strcat都返回第1个参数的一份拷贝,就是一个指向目标字符数组的指针。
strcat(strcpy(a, b), c);


30.strncpy(dst, src, len);//它总是正好向dst写入len个字符,如果strlen(str)的值小于len,dst数组就用额外的NUL字节填充到len长度。注意:它的结果不会以NUL字节结尾。

31.查找任何几个字符

strpbrk(string, group);//return pointer or NULL
 



32. \v垂直制表和\f换页符对屏幕没有任何影响,但会影响打印机执行响应操作。 \r 回车(CR) 。\a响铃。如果反斜线之后的字符和它不构成转义字符,则’\’不起转义作用将被忽略。
例如:
printf(“a\Nbc\nDEF\n”);
输出:
aNbc
DEF
转义字符也可以出现在字符串中,但只作为一个字符看待。
例 求下面字符串的长度
“\026[12,m” 长度为6
“\0mn” 长度为0。(想想:为什么不是2)


33.高级字符串查找:

strspn(str,group);//reutrn size_t,返回str起始处与group任意字符连续匹配的个数
strcspn(str,group);//return size_t,返回str起始处与group任意字符不匹配的个数。
高级字符串查找标记之查找标记:
char *strtok(char *str, char const *sep);
sep是分隔符,此函数修改str所以要注意。
strtok函数是不可再入的。!!!
例:
char buffer[]="25,142,330,Smith,J,239-4123";
                char *token;
                for(token=strtok(buffer, ",") ; token != NULL; token = strtok(NULL, ",-"))
                {printf("%s\nnextis:%s\n",buffer, token);}//注意第2次定位时传给函数的参数是NULL

 

34.标准库包含了两组函数,用于操作单独的字符,它们的原型位于头文件ctype.h。第1组函数用于对字符分类,而第2组用于转换字符。

if(ch >= 'A' && ch <= 'Z')
这条语句使用ASCII字符集的机器上能够运行,但在使用EBCDIC
字符集的机器上将会失败。应使用函数库。
isupper(ch)
isalpha(ch)//a~z,A-Z
isxdigit(ch)//0~9,a~f,A~F
isdigit(ch)//0~9
isspace(ch)// \v\t\f\r\n ' ',空格

 

35.内存操作
     memxxx函数提供了类似字符串函数的能力,但它们可以处理包地括NUL字节在内的任意字节。memcpy从源参数向目标参数复制由长度参数指定的字节数。memmove函数执行相同的功能,但它能够正确处理源参数和目标参数出现重叠的情况。memcpy函数比较两个序列的字节,每个字节用unsigned char类型比较,memchr函数在一个字节序中查找一个特定的值。最后,memset函数把一序列字节初始化为一个特定的值。

36.结构体例子:

struct
{
    int a;
    char b;
    float c;
}x;
struct
{
    int a;
    char b;
    float c;
}*z;
z = &x;//非法。虽然这两个数据列表完全一样,但是编译器将它们当作两种不同的类型。

 

37.点操作符的结合性是从左到右。下标引用和点操作符具有相同的优先级。

38.->操作符的优先级高于&操作符的优先级。

39.结构体的存储分配:
编译器按照成员列表的顺序一个接一个地给每个成员分配内存。只有当存储成员时需要满足正确的边界对齐要求时,成员之是才可能出现用于填充的额外内存空间。
sizeof操作符能够得出一个结构的整体长度,包括因边界而跳过的那些字节。如果你必须确定结构某个成员的实际位置,应该考虑边界对齐因素,可以使用offstof宏(定义于stddef.h)
sizeof(type, memeber)

40.位段。位段的声明和结构类似,但它的成员是一个或多个位的字段。这些不同长度的字段实际上存储了一个或我个整数变量中。

里面只能用signed int, unsigned int。
位段的声明在本质上是不可移植的,因为它涉及许多与实现有关的因素。但是,位段允许你把长度为奇数的值包装在一起以节省存储空间。
struct CHAR{
    unsigned ch :7;
    unsigned font:6;
    unsigned size:19;
};

 

41.联合体主变量可以被初始化,但这个初始值必须是建立在合第1 个成员的类型,而且它必须位于一对花括号里面。

union{
    int a;
    float b;
    char c[4];

} x = {5};

 

42.动态内存分配:

void *malloc(size_t size);
void free(void *pointer);
void *calloc(size_t num_elements, size_t element_size);
void *realloc(size_t *ptr, size_t new_size);
释放一块内存的一部分是不允许的。动态分配的内存必须整块一起释放。
calloc也用于分配内存。malloc和calloc之间的主要区别是膈者在返回指向内存的指针之前把它初始化为0。
realloc用于修改一个原先已经分配的内存块的大小。

 

43.一个安全的alloc函数:

alloc.h
#ifndef ALLOC_H
#define ALLOC_H
#include <stdlib.h>
#define malloc cann't_call_malloc_directly!
#define MALLOC(num, type) (type *)alloc((num)*sizeof(type))
extern void *alloc(size_t size);
#endif

alloc.c
#include <stdio.h>
#include "alloc.h"
#undef malloc
void *
alloc(size_t size)
{
        void *new_mem;
        new_mem = malloc(size);
        if(new_mem == NULL)
        {
                printf("Out of memeory!\n");
                exit(1);
        }
        return new_mem;
}

main.c
#include <stdio.h>
#include "alloc.h"
#undef malloc
void *
alloc(size_t size)
{
        void *new_mem;
        new_mem = malloc(size);
        if(new_mem == NULL)
        {
                printf("Out of memeory!\n");
                exit(1);
        }
        return new_mem;
}

 

43.使用锤子可能会伤着你自己,所以我们不给你锤子。

44. 函数指针用于:回调函数(如qsort的cmp)和转移表。

转移表例子:
double add(double , double);
double sub(double, double);
doubel mul(double, double);
double div(double, double);
...
double (oper_func[])(double, double){
    add, sub, mul, div, ...
};
调用:result = oper_func[oper](op1, op2);

 

45.如果你使用的是UNIX操作系统,你可以获得一个名叫cdecl的程序,它可以在C语言的声明和英语pp间进行转换。

试了一下这个程序很有意思。
cdecl>explain int (*(*f)())[10];
declare f as pointer to function returning pointer to array 10 of int

 

46.再论字符串常量

当一个字符串常量出现于表达式中时,它的值是一个指针常量。我们可以对它们进行下标引用、间接访问以及指针运算。

"xyz" + 1  是 'y'处的指针"xyz"[2]
putchar("0123456789ABCDEF"[value % 16]);//decimal_to_hex
c语言中对字符串常量只存一次。!!
例如:
printf("%d\n", "xyz");
printf("%d\n", "xyz");
两句的值一样。

 

47.预处理器(Preprocessor)
      它的主要任务包括删除注释、插入被#include指令包含的文件的内容、定义和替换由#define指令定义的符号以及确定代码的部分内容是否应该根据一些条件编译指令进行编译。

48.#define替换:

技巧:使用预处理器把一个宏参数转换为一个字符串。#argument这种结构被预处理器翻译为“argument”
例子:
#define PRINT(FORMAT,VALUE)\
    printf( "The value of " #VALUE \
    " is " FORMAT "\n", vALUE)
PRINT("%d", x + 2);//x = 33
结果:The value of x + 2 is 35
技巧:##结构则执行一种不同的任务。它把位于它两边的符号连接成一个符号。
例子:
#define ADD_TO_SUM( sum_number, value )\     //这左括号的空格到底应该有设有????
    sum ## sum_number += value

ADD_TO_SUM(5, 25);

 

49.编译时指定参数大小:


50.条件编译:

#if constant-expression
    statements
#elif constant-expression
    statements
#else
    other statements
#endif

 

51.是否被定义:

#if defined(symbol)
==>#ifdef symbol
#if  !defined(symbol)
==>#ifndef symbol

 

52.标准要求编译器必须支持至少8层的关文件嵌套。

53.嵌套文件包含:

#ifndef _HEADERNAME_H
#define _HEADERNAME_H 1 //or #define _HEADERANAME_H

/*
the header connent
*/
#endif

 

53.预处理器只支持5个符号(预定义符号):
__FILE__   进行编译的源文件名
__LINE__   文件当前的行号
__DATE__   文件被编译的日期
__TIME__   文件被编译的时间
__STDC__   如果编译器遵循ANSIC,其值就为1,否则未定义


54.有些任务既可以用函数实现也可以用函数实现。但是,宏与类型无关,这是一个优点。

55.文本流:
流为两种类型:文本流和二进制流。

56. FILE *freopen(char const *filename, char cosnt *mode, FILE *stream);
      freopen函数用于打开(或重新打开)一个特定的文件流。
最后一个参数就是需要打开的流。它可能是一个先前从fopen函数返回的流,也可能是标准流stdin,stdout,stderr。
int flose(FILE *f)
成功返回0,失败返回EOF

57.int fflush(FILE *stream);
当我们需要立即指骨输出缓冲区进行物理写入时,应该使用这个函数。

58.文件定位函数:

long ftell(FILE *stream);
int fseek(FILE *stream, long offset, int from);
void rewind(FILE *stream);
int fgetpos(FILE *stream, fpos_t *position );
int fsetpos ( FILE *stream, fpos_t const *position);

 

59.改变缓冲方式:

void setbuf( FILE *stream, char *buf);
void setvbuf( FILE *stream, char *buf, int mode, size_t size);

 

60.流错误函数:

int feof( FILE *stream );
int ferror( FILE *stream);
void clearerr( FILE *stream);

 

61临时文件:

FILE *tmpfile(void);//binary file
fopen(),remove()
char *tmpnam( char *name);

62.文件操纵函数
  有两个函数用于操纵文件但不执行任何输入/输出操作。它们的原型 如下所示。如果执行成功这两个函数都返回零值。如果失败,它们都返回非零值。

int remove ( char const *filename);
int rename( char cosnt *oldname, char const *newname);

      remove 函数删除一个指定的文件。如果当remove被调用时文件处于打开状态,其结果则取决于编译器。
  rename函数用于改变一个文件的名字,从oldname改为newname。如果已经有一个名为newname的文件存在,其结果取决于编译器。如果这个函数失败,文件仍然可以用原来的名字进行访问。