《C和指针》-学习心得

关于注释:大块注释时,我们肯定都比较喜欢/* ---*/,但是如果有很多这个注释,由于注释无法嵌套,当我们在更大的范围内做注释时,肯定会出现错误,此时我们可以使用#if 0 ----#endif来代替这种注释。比如:

在C语言中,清一色的按值value传递的

定义与声明

  1. 定义:函数定义了需要执行的工作;
  2. 声明:描述了函数和函数将要操作的数据类型。

在C语言中,所有的注释都会被预处理器拿掉,取而代之的是一个空格。因此,注释可以出现在任何空格可以出现的地方。

规定整型值相互之间大小的规则很简单:长整型至少应该和整型一样长,而整型至少应该和短整型一样长。

浮点类型

浮点数字面值在缺省情况下是double类型的。

指针

指针是C语言为什么这么流行的一个重要原因。指针可以有效地实现诸如tree和list这类高级数据结构。用C语言可以比使用其他语言编写出更为紧凑和有效的程序。但同时,C对指针使用的不加限制正是许多令人欲哭无泪和咬牙切齿的错误的根源。

数组的好处和坏处

好处是不需要浪费时间对有些已知是正确的数组下边进行检查,坏处是这样做将使无效的下标引用无法被检测出来。

typedef :

typedef允许为各种数据类型定义新名字。在原来的声明前面加上typedef就可以为数据类型定义新名字。比如typedef char * ptr_to_char;使用typedef声明类型可以减少使声明变得又臭又长的危险,尤其是那些复杂的声明。你应该使用typedef而不是#define来创建新的类型名,因为后者无法正确地处理指针类型。

常量const

int *pi; //一个普通的指向整型的指针;

int const *pci; //一个指向整型常量的指针;

int *const cpi; //一个指向整型的常量指针。

static关键字

当位于不同的上下文环境时,static关键字具有不同的意思。

当static用于函数定义时,或用于代码块之外的变量声明时,static关键字用于修改标识符的链接属性,从external改为internal,但标识符的存储类型和作用域不受影响,用这种方式声明的函数或变量只能在声明它们的源文件中访问;

当static用于代码块内部的变量声明时,static关键字用于修改变量的存储类型,从自动变量修改为静态变量,当变量的链接属性和作用域不受影响,用这种方式声明的变量在程序执行之前创建,并在程序的整个执行期间一直存在,而不是每次在代码块开始执行时创建,在代码块执行完毕后销毁。

所以,static不改变作用域,改变的是链接属性(代码块外或函数)或存储类型(代码块内)。

break和continue

在while语句中,使用break语句可以永久终止循环,而使用continue语句,用于永久终止当前的那次循环。这两条语句的任何一条如果出现于嵌套的循环内部,它只对最内层的循环起作用,你无法使用break或者continue语句影响外层循环的执行。

default子句

对于switch中的default语句,我一直认为要放在最后,但是测试了一下,发现default可以出现在任何一个case标签出现的地方而不仅仅是最后。

单目操作符

单目运算符sizeof对于变量可以不加括号,对于类似int这样的类型,必须加上括号。

注意,对于++和--这样的前置或后置增值运算符,操作的是复制了该变量的一份拷贝而已。

逻辑操作符

这里比较帅的一个特性是短路求值short-circuited evaluation,工作原理如下,&&操作符的左操作数总是首先进行求值,如果它的结果为真,然后就紧接着对右操作符进行求值;但是,如果左操作数的值为假,那么右操作数便不再进行求值。对于||也是同理。

注意这里不要把逻辑操作符和位操作符搞混淆。逻辑操作符用于测试零值和非零值,而位操作符用于比较它们的操作数中对应的位。

条件操作符

expression1 ? expression2 : expression3:首先计算expression1,如果值为真,那么整个表达式的值就是expression2的值,expression3不会进行求值。但是如果expression1的值为假,那么整个语句的值就是expression3的值,expression2不会进行求值。

条件操作符也可以长生简洁的代码,并且可以产生较小的目标代码。

逗号操作符

逗号操作符将两个或多个表达式分割开来,这些表达式自左向右逐个进行求值,整个逗号表达式的值就是最后那个表达式的值。

下标引用、函数调用和结构成员

.和->操作符用于访问一个结构的成员,如果s是个结构变量,那么s.a就访问s中名叫a的成员;当你拥有一个指向结构的指针而不是结构本身,切欲访问它的成员时,就需要使用->操作符而不是.。

左值和右值

左值就是那些能够出现在赋值符号左边的东西,右值就是那些可以出现在赋值符号右边的东西。

左值标示了一个可以存储结果值的地点;

右值指定了一个值;

所以,使用右值的地方可以使用左值,但是使用左值的地方不能使用右值。

*操作符

     当它作为左值使用时,这个表达式就指定需要进行修改的位置,当它作为右值使用时,它就提取当前存储于这个位置的值。

值和类型

不管是int数、float数还是double型的数据,存储在计算机上的全是0或者1。所以对该数值的解析就决定了它的结果。

指针变量的内容

int *d=&a;的含义为d是a的地址,可以分解为int *d; d = &a;千万不要理解成了d是a的值。

指针常量

假设int a的地址为100,那么*100 = 25;代表的意思貌似是将25赋值给a,因为a是位置100所存储的变量,但是这个语句是错 的。这个语句是非法的,因为字面值100的类型是整型,而间接访问操作只能作用于指针类型表达式。所以,如果确实希望实现上面貌似代表的含义,需要使用强制类型转换,即*(int *)100 = 25; 。

指针与下标

下标绝不会比指针更有效率,但指针有时会比下标更有效率。

声明数组参数

int strlen(char *string);

int strlen(char string[]);

这两个其实含义是一样的,调用函数时实际传递的是一个指针,所以函数的形参实际上是个指针。但是为了使程序员新手更容易上手一些,编译器也接受数组形式的函数形参。

但是两个定义中,指针的声明更加准确一些。

字符数组的初始化

当用于初始化一个字符数组时,字符串为一个初始化列表,在其他任何地方,表示一个字符串常量。

指向数组的指针

int matrix[3][10];

int (*p)[10] = matrix;--指向数组的指针

结构声明

结构声明为:

struct tag {member-list} variable-list;

红色部分为可选部分,可选部分不能全部省略,至少要出现两个。

DATA为一个结构体

void test(DATA x);   
//--这种方法是不提倡的;复制DATA,如果DATA结构十分庞大,会占用很大的空间;

void test(DATA *x);  
 //--这种方法是提倡的 ,只是传递一个指针。

void test(register DATA const*x);  
//--这种方法是大力提倡的   ,这种模式可以提高速度,并且可以防止指针被修改。

联合的初始化

联合可以被初始化,但这个初始值必须是第一个成员的类型(如果被赋予其他值,有可能会被强制类型转换),而且它必须位于一对花括号里面。对于C99已经可以支持初始化其他成员而不只是第一个成员了。

预定义符号

符号   含义
__FILE__ 进行编译的原文件名
__LINE__文件当前行的行号
__DATE__  文件被编译的日期
__TIME__文件被编译的时间
__STDC__  如果编译器遵循ANSI C,其值就为1,否则未定义

#define替换

宏参数和#define定义可以包含其他#define定义的符号,但是,宏不可以出现递归。

宏和函数的区别

首先,用于调用和从函数返回的代码很可能比实际执行这个小型计算工作的代码更大,所以使用宏比使用函数在程序的规模和速度方面都更胜一筹;

再者,函数的参数必须声明为一种特定的类型,所以它只能在类型合适的表达式上使用,反之,上面的这个宏可以用于整型、长整型、单浮点型、双浮点型以及其他任何可以使用>操作符比较值大小的类型,也就是说,宏是与类型无关的;

还有一些任务根本无法用函数实现,比如,下面的type参数类型:

宏和函数的不同之处

属性  #define宏 函数
代码长度 每次使用时,宏代码都会插入到程序中,除了非常小的宏外,程序的长度将大幅度增长 函数代码值出现在一个地方;每次使用这个函数时,都调用那个地方的同一份代码
执行速度   更快存在函数调用/返回的额外开销
操作符优先级宏参数的求值是在所有周围表达式的上下文环境里,除非它们加上括号,否则邻近操作符的优先级可能会产生不可预料的结果函数参数只在函数调用时求值一次,它的结果值传递给函数,表达式的求值结果更容易预测
参数求值    参数每次用于宏定义时,它们都将重新求值,由于多次求值,具有副作用的参数可能会产生不可预测的结果   参数在函数被调用前置求值一次,在函数中多次使用参数并不会导致多种求职过程,参数的副作用并不会造成任何特殊的问题
参数类型  宏与类型无关,只要对参数的操作是合法的,它可以使用于任何参数类型 函数的参数是与类型有关的,如果参数的类型不同,就需要使用不同的函数,即使它们执行的任务是相同的。

解决多重包含头文件的方法为:

二进制I/O 

把数据写到文件效率最高的方法是用二进制形式写入。二进制输出避免了在数值转换为字符串过程中所涉及的开销和精度损失。

非本地跳转

setjmp和longjmp函数提供了一种类似goto语句的机制,但它并不局限于一个函数的作用域之内。这些函数常用于深层嵌套的函数调用链。

断言

宏assert(test)用于检测test是否为真。用这种方法可以使调试变得更容易。并且我们可以在头文件assert.h被包含之前,添加#define NDEBUG皆可以禁用所有的断言。

内存分配

获取内存来存储值的三种方案:

静态数组:静态数组要求结构的长度固定,而且,这个长度是在编译时就要确定的。但是这个方案最为简单,而且最不容易出错;

动态分配的数组:可以在运行时才决定数据的度,而且,如果需要的话,可以通过分配一个新的、更大的数组,把原来数组的元素复制到新数组中,然后删除原先的数组,从而达到动态改变数组长度的目的。在决定是否采用动态数组时,你需要在由此增加的复杂性和随之产生的灵活性之间做一番平衡;

动态分配的链式结构:提供了最大程度的灵活性。每个元素在需要时才单独进行分配,所以除了不能超过机器的可用内存之外,这种方式对元素的数量几乎没有什么限制。但是,链式结构的链接字段需要消耗一定的内存,在链式结构中访问一个特定元素的效率不如数组。

树的遍历     

  1. 前序pre-order:根节点、左节点、右节点
  2. 中序in-order:左节点、根节点、右节点
  3. 后序post-order:左节点、右节点、根节点
  4. 层次遍历breadth-first:逐层扫描。

提高程序效率的最好方法是为它选择一种更好的算法。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值