Tianger's Family

Leave no regrets every day


  • 首页

  • 分类

  • 关于

  • 归档

  • 公益404

C与指针-第七章-函数

发表于 2016-06-05   |   分类于 C与指针   |  

函数是指一段在一起的、可以做某一件事的程序,也叫子程序、方法。

定义:

返回类型 函数名字(形式参数)
{
代码块
}

形式参数列表包括变量名和它们的类型声明。
代码块包含了局部变量的声明和相关执行语句。
compare.c中

1
2
3
4
5
6
7
8
9
10
11
int max(int value_left, int value_right)
{
if (value_left > value_right)
{
return value_left;
}
else
{
return value_right;
}
}

如果函数的类型声明为void,没有返回值;否则,需要相应类型的返回值。

声明:

当我们在一个源文件中定义的函数想被其他源文件使用的话,我们需要在头文件中声明这个函数,想调用这个函数的源文件中,include这个头文件即可。
.h文件中按如下方式进行声明。
返回类型 函数名字(形式参数);

1
2
3
4
5
6
7
8
9
10
internel.h中
int max(int value_left ,int value_right);

在 main.c中使用
int main(void)
{

int max_value;
max_value(10,20);
printf(“max value is %d\n”,max_value);
}

函数形参:

C函数的所有参数均以“传值调用”方式进行传递。但是当我们传进来的参数为一个数组时,并在函数㕜使用下表引用该数组的参数,那么实际上函数对该数据的修改是调用程序中数组的元素,这个行为被称为“传址调用”。

下面这个数组,length为传值调用,array为传址调用

1
2
3
4
5
6
7
8
9
10
int array[10];
void reset(int *array,int length)
{

while (length >0)
{
/*把数组的元组清零*/
array[lengh] = 0;
length—;
}
}

递归与迭代:

递归算法:

直接或间接调用自身的函数。
把操作的内容放入栈,等函数递归完成后,出栈打印
下面这个简单的例子来说明递归调用方式。
程序作用:输入一个值,循环打印它的除以10 后剩余的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void fun(int input)
{
if (input >0)
{
fun(input/10);
}
printf("%d\n",input);
}

int main(void)
{
fun(1234);
return 0;

}

输出:
0
1
12
123
1234

这里递归的操作依次为,先把第一个input放入栈,此时为1234;当再次调用时得到input的值,input为123,把123放入栈,一直大牌最后input的值为0,结束递归,打印时,先从栈顶取出数据,栈顶数据为0,栈底数据为1234,打印上面输出的结果
数据操作如下图所示:

迭代算法:

迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值。

迭代算法是用计算机解决问题的一种基本方法。它利用计算机运算速度快、适合做重复性操作的特点,让计算机对一组指令(或一定步骤)进行重复执行,在每次执行这组指令(或这些步骤)时,都从变量的原值推出它的一个新值。

对于上面递归的算法,我们换成迭代来实现:

1
2
3
4
5
6
7
8
9
10
void fun(int input)void fun(int input)
{
int ret;
while(input >0)
{
ret = input;
printf("%d\n",ret);
input = input/10;
}
}

总结:

递归的使用可以使代码更加简洁清晰,可读性更好,但由于递归需要系统堆栈,所以空间消耗比非递归代码要大很多。
理论上,递归与迭代在时间复杂度方面是等价的,但实际上递归的效率确实比迭代低。
一般情况下,能不用递归我们就尽量不使用递归,我们使用递归的先决条件是,当且仅当一个存在预期的收敛时,我们才使用递归算法,否则我们还是用迭代。

可变参数:

我们之前将的函数传进来的参数个数都是固定的,那么我们有没有办法,函数的参数有多个,但是我们只调用其中一部分呢,C的可变参数实现了这一功能。
我们看下面这个例子,msg函数有多个参数,但是我们只调用其中一个,这种方式我们可以用在log打印上,可以打印不同类型的log,以及多个参数列表实现。

1
2
3
4
5
void msg(int x,int y…);
void main(void)
{

msg(10);
}

C的可变参数使用,需要用到下面一些函数:
void va_start( va_list arg_ptr, prev_param );
type va_arg( va_list arg_ptr, type );
void va_end( va_list arg_ptr );

va_list:用来保存宏va_start、va_arg和va_end所需信息的一种类型。为了访问变长参数列表中的参数,必须声明va_list类型的一个对象 定义: typedef char * va_list;

va_start:访问变长参数列表中的参数之前使用的宏,它初始化用va_list声明的对象,初始化结果供宏va_arg和 va_end使用;

va_arg: 展开成一个表达式的宏,该表达式具有变长参数列表中下一个参数的值和类型。每次调用va_arg都会修改用va_list声明的对象,从而使该对象指向参数列表中的下一个参数;

va_end:该宏使程序能够从变长参数列表用宏va_start引用的函数中正常返回。
va在这里是variable-argument(可变参数)的意思.

这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个头文件.下面我们写一个简单的可变参数的函数,改函数至少有一个整数参数,第二个参数也是整数,是可选的.函数只是打印这两个参数的值.

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

void msg_print(char *msg,...)
{

va_list arg_ptr; /* 定义保存函数参数的结构 */
int ipara =0; /* 定义参数个数 */
char *para = NULL;
char flag;

/* arg_ptr指向传入的第一个可选参数,msg是最后一个确定的参数 */
va_start(arg_ptr, msg);
while(*msg)
{
flag = *msg++;
if (flag != '%')
{
putchar(flag); /* 如果字符不包括%号,把字符输出到终端上 */
continue;
}
flag = *msg++; /* 如果字符是%号,指针后移一位,判断对应格式 */
switch(flag)
{
case 's': /* 检索到%s,进行字符串的打印 */
para = va_arg(arg_ptr, char *); /* 取出当前的参数,类型为char *. */
printf("%s",para);
break;
case 'd':
ipara = va_arg(arg_ptr, int);
printf("%d", ipara);
default:
break;

}
}
va_end(arg_ptr);
}

int main(void)
{

msg_print("The first print %d\n",100);
msg_print("The second print %s,%d\n","hello",200);
return 0;
}

输出:
he first print 100
The second print hello,200

C与指针-第六章-指针

发表于 2016-06-04   |   分类于 C与指针   |  

指针

含义:

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。

指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址

&操作符:取变量的地址
*操作符:对指针做解引用操作

地址&内存:

计算机的内存由数以亿万技的位(bit)组成,每个位可以容纳0和1.

由于位的标示范围太有限,计算机中为了可以存储较大范围的值,通常把许多个位合成一组作为一个单位。在现代大多数的机器中,常用的是用8个位来组成一个单位,这里成为字节(byte)。1个字节可以存储无符号值0~255,或有符号值-128~127。

其实字节标示的大小也比较有限,为了继续扩大存储的范围,我们把更多个字节组合在一起,作为一个更大的单位。

例如:

int –4个字节 ,32位为4个字节
float –4个字节
long –4个字节
long long —8个字节

需要注意的:
1)内存中的每个位置由一个独一无二的地址标识
2)内存中的每个位置都包含一个值

int a =112,b = -1;
float c = 3.14;
int d = &a;
float
e = &c;

a,b,c,d,e分别表示什么
a是112,b为-1,c为3.14
d是指向int型的指针,这里指向a的地址,a的地址是100,所以d的值为100
e为指向float型的指针,这里指向c的地址,c的地址为108,所以e的值为108

间接访问操作符:

含义:

通过一个指针访问它所指向的地址的过程称为间接访问或解引用指针。

操作符:对应的操作符为*

例如:
我们上面一节中的d为100,d即为间接访问,d的值为int类型,d的值为100,d标示访问内存位置100并查看哪里的值,因此d的右值为112.

未初始化和非法的指针:
下面这个代码有没有问题:

int a; a = 10;

语句描述:这个语句声明创建了一个名叫a的指针变量。
*a = 10表示把12 存储到a所指向的内存位置。

正常编译这个语句没有问题,但是在正常使用的过程中,会存在一些隐患。这里的a没有做初始化,那么a指向哪个地址我们不清楚,所以这个10存储到哪个内存区域,其实我们也不能清楚的知道。

> 如果指针变量是静态的,它会自动初始化为0
> 如果指针变量是自动的,那它根本不会被初始化。

所以当,赋值操作进行时,如果a的初值是个非法地址,这个语句将会出错,程序终止;如果a的初值是个合法地址,那么地址上的值被修改,但是我们不确定那个地址是否是我们想要修改的地方。
所以,在对指针进行间接访问之前,要确保它已经初始化。

空指针:

含义:表示不指向任何东西(地址或内容)
用法:要使一个指针变量位NULL,就需要给它赋一个零值。
想要测试一个指针是否为空,我们需要拿它与零值进行比较。这个零值由源代码约定得出。
指针与零值比较来判断是否为空,我们最好用!这个符号来表示,这样可以排除隐士类型转换,也增强了可读性
例如:

int a = 10;
int *b = &a;
if (!b)
{
printf(“point p is not null\n”);
}

指针的指针:

我们用**来进行表示指针的指针,例如:

int a = 10;
int b = &a;
int *
c = &b;

假设a的地址为101
那么b = 101, b = 10;
**c相当于
(c),我们必须从里向外逐成求值。c访问c所指向的位置,即为变量b,为101,(c)这个间接访问操作,即访问c所指向位置的地址,这里就死&b,就是变量a,值为10.

指针运算:

指针运算包括 算数运算和关系运算:

运算符:+ ,-,++,—

这种形式只能用于指向数组中某个元素的指针,结果类型也是指针。
指针的算数运算都需要操作一段连续的内存空间,例如数组

指针运算:+,++

int value[5]
int *p;
(1) p = &value[0];//p指向value数组的第一个元素
P += 2; //p向后偏移两个int长度的位置,即指向了value数组的第三个元素

(2) for (p = &value[0]; p< &value[5];)
{
p++ =0; //这边先进行p = 0的操作,即把value[0]赋初值为0,再指针后移一位,循环初始化value这个数组
}

当p这个指针指向的地址大于value[5]的地址,循环结束,p <&value[5],这里经过了5此循环后,p就指向了value数组最后一个数组外面的那个地址,指针可能可以合法的获取这个值,但对它执行简介访问操作可能意外的原先存储于这个位置的变量,我们一般不知道这个变量是什么,因此,这种情况下,一般不允许对指向这个位置的指针进行间接操作,即*p = 10这种操作

指针运算: -,–

指针相减运算,只适用于数组的操作,即两个指针都指向同一个数组中的元素时,才允许一个指针减去另一个指针。

int value[5]
int p;
(1) p = &value[4];//p指向value数组的第一个元素
P -= 2; //p向前偏移两个int长度的位置,即指向了value数组的第三个元素
(2) for (p = &value[4]; p>= &value[0]; p–)
{
p =0; //这边先进行*p = 0的操作,即把value[4]赋初值为0,再指针减减,向前移动一位,循环初始化value这个数组
}

当p这个指针指向的地址小于value[0]的地址,循环结束,p > = &value[0],经过5次循环后,p指针指向了数组第一个元素之前,这里我们同样不能再对p这个指针进行间接操作。

总结:

1)未初始化的指针变量,不能进行解引用操作
2)初始化为NULL的指针,不能进行解引用操作
3)指针进行加减法操作后,指向了数组外面的内存地址后,不能对它们进行间接操作
4)指针的减法操作只能针对同一个数组的元素进行

C与指针-第五章左移、右移操作

发表于 2016-06-02   |   分类于 C与指针   |  

本文参考了京月飞鸿的《C语言中的左移与右移》

左移操作:

含义:

顾名思义就是把一个数的所有位都向左移动若干位,在C用用<<运算符标记
例:

int i = 1;
i = i << 2

1的二进制为00000001(这里用8位标示,不同系统,不同的位数),向左移两位,即把1向左移动两位,后面补两个0
即 00000100,转换成10进制就变成了4

需要注意的是,int类型为有符号的类型,最左端的1位是符号位,即0正1负,那么移位的时候就会出现溢出
例:

int i = 0x40000000; //二进制位01000000000000000000000000000000
i = i << 1

i左移1位后变为0x80000000,符号位被置为了1,其它位全是0,变成了int所能表达的最小值,32位的int为-2147483648,溢出。
如果此时i再左移1位后,丢弃了最高为后,i变成了0

右移操作:

含义:

顾名思义就是把一个数的所有位都向右移动若干位,在C用用>>运算符标记
右移分为逻辑右移动和算术右移。

逻辑右移:

方法:
左边移入的位用0填充
int i = 150;//二进制为10010110
i = i>>2;
i向右移动两位,则最后面两位移除,最开始的添加两个0,变成00100101,十进制为37

算术右移:

方法:
左边移入的位由原先该值的符号位决定,符号位为1则移入的位均为1,符号位为0,则移入的位都为0,这样能够保持原数的正负数形式不变。
int i = 150;//二进制为10010110
i = i>>2;
i向右移动两位,原来的符号位即首位为1,即最后两位移除,最开始添加两个符号位,这里为1,即变成11100101,十进制为229

总结:

左移时总是移位和补零。右移时无符号数是移位和补零,此时称为逻辑右移;
而有符号数大多数情况下是移位和补最左边的位(也就是补最高有效位),移几位就补几位,此时称为算术右移

C与指针-第三、四章const\extern\static

发表于 2016-06-01   |   分类于 C与指针   |  

C与指针第三章重点需要掌握的是 const\extern\static 这三个修饰关键字。
第四章主要讲的是if\while\switch\for\do..while\goto等常用语句的用法,这作为基础内容,本章就不进行讲解。

const:

const关键字是一个很常用的关键字,它限定一个变量不允许被改变,产生静态作用。

定义:

const修饰的数据类型是指常类型,常类型的变量或对象的值是不能被更新的。

目的:

const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点。

用法:

1.基本用法

const int a = 10;
含义:定义了一个int类型的常量a,值为1,a为只读变量,程序允许过程中不能够直接修改它的值,即编译器编译经过类型检查后,直接用10在编译时替换,这使得编译器编译const常量过程中,少了一步存储与读内存的操作,提高了效率

2.指针

int b = 10;
1)const int a =&b;
2)int const
a = &b;
3)int const a = &b;
含义:
1)表示指针所指向的对象是只读的(
a 只读),a指向的内容不能够通过a改变

a = a +1; //错误,*a为只读,不能被赋值

2)与1)完全相同
3)表示指针指向是常量,不能够修改去指向其他内容

1
2
3
int c =20;
a = &c; //错误,a为只读,不能指向其他内容
*a = *a +1; //正确, *a不为只读

3.函数

1)修饰函数参数
void foo(const int a);
含义:传进来的a为只读,不能被修改
void foo(const int a);
含义:传进来的
a 为只读,不能被修改,同上用于把数据作为参数传进来,函数只能读取这个指针的内容,当函数返回内部的类型时,已经是一个数值,当然不可被赋值更新,因此这种写法无意义

2)修饰函数返回值
const int foo();
含义:返回值为const类型,指返回的原函数里的变量初值不能被修改

4.类型检查

const的值只能赋值给const对象,不能赋值给非const对象,只能强制类型转换才可以

1
2
3
const int b =10;
int *a = &b; //错误,a为非const对象,b为const对象
int *a = (int *)&b; //正确,强制类型转换

extern:

定义:

extern可置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量或函数时,在其它模块中寻找其定义。通过这种行为它告诉编译器:该变量/函数的定义已经存在在某个地方了,让编译器到其他的模块去寻找它的定义。

用法:

extern用在变量声明中常常有这样一个作用:你要在.c文件中引用另一个文件中的一个全局的变量,那就应该放在.h中用extern来声明这个全局变量。
在定义函数的时候,这个extern可以被省略,函数的默认定义就是extern类型

extern int a;//声明一个全局变量a ,这是一个声明,只有这样写,才能被其他的C调用

int a; //定义一个全局变量a ,这是一个定义,这不能被其他C调用

extern int a =0 ;//定义一个全局变量a 并给初值。一旦给予赋值,一定是定义,定义才会分配存储空间。

i nt a =0;//定义一个全局变量a,并给初值,
声明之后你不能直接使用这个变量,需要定义之后才能使用。

static:

static内容参考了
keyeagle的《C语言static分析》
adriano119的《static用法总结》

一个进程在内存中的布局入下图所示:

|        栈区     |
------------------
| 堆栈增长区  |
------------------
|       堆区       |
-----------------
|      其他段     |
-----------------
|        .bss段      |
-----------------
|        .data段     |
-----------------
|        .text段     |
==========

其中.text段保存进程所执行的程序二进制文件,.data段保存进程所有的已初始化的全局变量,.bss段保存进程未初始化的全局变量(其他段中还有很多乱七八糟的段,暂且不表)。在进程的整个生命周期中,.data段和.bss段内的数据时跟整个进程同生共死的,也就是在进程结束之后这些数据才会寿终就寝。

一般程序的由malloc/new产生的动态数据存放在堆区,函数内部的自动变量存放在栈区,全局变量和static变量放在.data段内(已初始化)或.bss段内(未初始化),但是它只在定义它的源文件内有效,其他源文件无法访问它

1、[静态全局变量]

//在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。
静态全局变量有以下特点:
1)该变量在全局数据区分配内存;
2)未经初始化的静态全局变量会被程序自动初始化为0(自动变量的值是随机的,除非它被显式初始化);
3)静态全局变量在声明它的整个文件都是可见的,而在文件之外(extern)是不可见的;
定义全局变量就可以实现变量在文件中的共享,但定义静态全局变量还有以下好处:

1)静态全局变量不能被其它文件所用;
2)其它文件中可以定义相同名字的变量,不会发生冲突;

示例:
test1.c中,静态全局变量ch,只能在test1.c中使用,其他c文件不能使用

1
2
3
4
5
6
7
8
9
10
11
static  char *ch = "Hello World!";
void printstr()
{

printf("%s\n",ch);
}

test2.c
int main(void)
{

printf("%s\n",ch); //这里会报错,因为这个ch为test.c中的全局变量,只能在test1.c中使用
}

2、[静态局部变量]

在局部变量前,加上关键字static,该变量就被定义成为一个静态局部变量。

在函数体内定义了一个变量,每当程序运行到该语句时都会给该局部变量分配栈内存。但随着程序退出函数体,系统就会收回栈内存,局部变量也相应失效。每次调用这个局部变量在栈上的位置都不一定相同。如果我们想再两次的调用期间保留这个局部变量,就变的不可靠,一种方法使定义全局变量,另一个版本是使用静态局部变量

静态局部变量保存在全局数据区,即.data段或.bss段,而不是保存在栈中,每次的值保持到下一次调用,直到下次赋新值。

静态局部变量有以下特点:

1)该变量在全局数据区分配内存;
2)静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
3)静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
4)它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束;

3、静态函数

在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。

定义静态函数的好处:

1)静态函数不能被其它文件所用;
2)其它文件中可以定义相同名字的函数,不会发生冲突;

C与指针-第二章 转义字符

发表于 2016-05-31   |   分类于 C与指针   |  

C与指针第二章的重点就是转义字符和编程风格,这里只列举一下我们常用的转义字符,至于编程风格,这个可以根据不同的平台有不同的风格,谷歌与微软的风格不相同,但有一点我们要记住,就是需要与我们编写的工程代码中的风格保持一致。

所有的ASCII码都可以用“\”加数字(一般是8进制数字)来表示。而C中定义了一些字母前加”\”来表示常见的那些不能显示的ASCII字符,如\0,\t,\n等,就称为转义字符,因为后面的字符,都不是它本来的ASCII字符意思了。
8进制,16进制转义字符

转义字符 意义 ASCII码值(十进制)
\a 响铃(BEL) 007
\b 退格(BS) 008
\f 换页(FF) 012
\n 换行(LF) 010
\r 回车(CR) 013
\t 水平制表(HT) 009
\v 垂直制表(VT) 011
\ 代表一个反斜线字符’’\’ 092
\’ 代表一个单引号(撇号)字符 039
\” 代表一个双引号字符 034
\? 代表一个问号 063
\0 空字符(NULL) 000
\ddd 1到3位八进制数所代表的任意字符 三位八进制
\xhh 1到2位十六进制所代表的任意字符 二位十六进制

示例:

1
2
3
4
5
6
void main(void)
{

int a,b,c;
a=1; b=2; c=3;
printf("%d\n\t%d %d\n %d %d\t\b%d\n",a,b,c,a,b,c);
}

此程序练习转义字符的使用:
a、b、c分别为整数1,2,3
调用printf显示程序运行结果:
1
23
12 3

C与指针-第一章scanf\strncpy\strcpy\memcpy

发表于 2016-05-30   |   分类于 C与指针   |  

第一章的主要重点是,如何从键盘进行输入、以及相应的字符串拷贝。
这里主要用到四个C的基础API–scanf\strcpy\strncpy\memcpy

scanf
作用: 是格式输入函数,即按用户的制定格式从键盘上把数据输入到制定变量中
头文件: #include
调用形式:scanf(“<格式说明字符串>”,<变量地址>);
注意第二个参数是一个地址,记得加&符号,假如你是一个数组,不需要加入&符号,如果是一个数组带下表,需要加上
int column[8];
int num;
scanf(“%d”,&num)
scanf(“%d”,coloumn);
scanf(“%d”,&coloumn[1]);

例如:
scanf(“%d %d”,&a,&b);
如果a和b都被成功读入,那么scanf的返回值就是2
如果只有a被成功读入,返回值为1
如果a和b都未被成功读入,返回值为0
如果遇到错误或遇到end of file,返回值为EOF。
且返回值为int型.

&a,&b,&c中的&是地址运算符,&a指a在内存中的地址。scanf的作用是:按照a,b,c的内存地址将输入的数据存到a,b,c中去。变量a,b,c的地址是在编译连续阶段分配的(存储顺序由编译器决定)。

注意部分:
如果scanf中%d是连着写的如“%d%d%d”,在输入数据时,数据之间不可以加逗号,只能是空格或tab键或者回车键——“2 3 4” 或 “2(按tab)3(按tab)4(按tab)”。若是“%d,%d,%d”,则在输入数据时需要加“,”,如“2,3,4”.

相关输入格式
变量 类型
%d int
%ld long
%f float
%lf double
%c char
%s char数组

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <stdlib.h>

int main(void)
{

int a;
float b;
char c;
scanf("%d,%f,%c",&a,&b,&c);
printf("a=%d,b=%f,c=%c\n",a,b,c);
scanf("%d%f %c",&a,&b,&c);
printf("a1=%d,b1=%f,c1=%c\n",a,b,c);
return 0;
}

输入:

1
2
3
4
1,2.3,d //输入
a=1,b=2.300000,c=d //输出
1 2.4 e //输入
a1=1,b1=2.400000,c1=e //输出

  1. strcpy函数:顾名思义字符串复制函数:原型:extern char strcpy(char dest,char *src); 功能:把从src地址开始且含有NULL结束符的字符串赋值到以dest开始的地址空间,返回dest(地址中存储的为复制后的新值)。要求:src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串。
    一般函数原型实现方式:
    1
    2
    3
    4
    5
    6
    7
    8
    char * strcpy(char * strDest,const char * strSrc)
    {

    char * strDestCopy=strDest; //[3]
    if ((strDest==NULL)||(strSrc==NULL)) //[1]
    throw "Invalid argument(s)"; //[2]
    while ((*strDest++=*strSrc++)!='\0'); //[4]
    return strDestCopy;
    }

该函数的参数是字符指针,也就是可以是字符串变量和字符数组,因为它们的变量名代表首字符地址。字符串默认有一个null结束符,字符数组没有。所以此处需要注意:因为src要求有null结束符,所以字符数组的长度必须大于等于src包含null结束符的总长度。例如,char* src=”abcd”; char dest[5]; 这里dest的长度就至少为5。

  1. strncpy函数:多个n代表可以指定字符个数进行赋值。原型:char strncpy(char dest, char src, size_t n); 功能:将字符串src中最多n个字符复制到字符数组dest中(它并不像strcpy一样遇到NULL才停止复制,而是等凑够n个字符才开始复制),返回指向dest的指针。要求:如果n > dest串长度,dest栈空间溢出产生崩溃异常。该函数注意的地方和strcpy类似,但是n值需特别注意,
    1)src串长度<=dest串长度,(这里的串长度包含串尾NULL字符)
    如果n=(0, src串长度),src的前n个字符复制到dest中。但是由于没有NULL字符,所以直接访问dest串会发生栈溢出的异常情况。这时,一般建议采取memset将dest的全部元素用null填充,如:memset(dest,0,7)(7为从dest起始地址开始前7个位置填充null,dest可以为字符指针和数组名)。注意:char
    pc=”abc”; char chs[5]; sizeof(pc)为4(包含null)(有些编译器不行),sizeof(chs)为5。
    如果n = src串长度,与strcpy一致。
    如果n = dest串长度,[0,src串长度]处存放于desk字串,(src串长度, dest串长度]处存放NULL。
    2)src串长度>dest串长度
    如果n =dest串长度,则dest串没有NULL字符,会导致输出会有乱码。如果不考虑src串复制完整性,可以将dest最后一字符置为NULL。
    所以,一般把n设为dest(含null)的长度(除非将多个src复制到dest中)。当2)中n=dest串长度时,定义dest为字符数组,因为这时没有null字符拷贝。

3、memcpy函数
void memcpy(void s1, const void *s2, size_t n);
说明:
函数memcpy从s2指向的对象中复制n个字符到s1指向的对象中。如果复制发生在两个重叠的对象中,则这种行为未定义。
返回值:
函数memcpy返回s1的值。

memcpy用来在内存中复制数据,由于字符串是以“\0”结尾的,所以对于在数据中包含“\0”的数据只能用memcpy。
Strncpy和memcpy很相似,只不过它在一个终止的空字符处停止。当n>strlen(s1)时,给s2不够数的空间里填充“\0”;当n<=strlen(s1)时,s2是没有结束符“\0”的。

Android取经之路--学习启动篇

发表于 2016-05-29   |   分类于 Android取经之路   |  

    现如今,几乎人手一台手机,据统计,平均每人每天花在手机上的时间达到了3-4小时,现在主流的支柱型手机系统是Android、IOS。
    根据Gartner去年第四季的调查,Android于智慧型手机市场的占有率为80.7%,排名第二的苹果iOS则是17.7%。在过去的一年全球推出了超过600款Android手机,程序安装数量已经突破了650亿。在今年5月份谷歌开发者大会上,Android系统发布了Android7.0-Android N,Android也向前迭代了几十个版本。
    既然Android那么流行,那么到底他是如何工作的,他的内部构造是什么,很值得我们深入研究学习。最近也看了老罗-罗升阳的Android之旅,老罗不愧为大神级人物,每个模块讲解的都很深入,但是其中都是基于Android2.2,与现在的版本已经差了好几个大版本,现在最常用也最稳定的版本是Android6.0-Android M,因此接下来我会参考老罗的相关文章以及其他网络大神的相关文章,基于Android6.0的源码进行展开学习。

准备学习分享的内容从以下几个方面进行:
语法:
C语言:
入门书籍:《The C ProGramming Language》
进阶书籍:《C与指针》
参考我的博客:《C与指针》

JAVA语言:
入门书籍:《Head first java》
进阶书籍:《thinking in java》

C++语言:
建议书籍:《C++ primer》

MakeFile:
入门书籍:《跟我一起写Makefile》

Python:
入门书籍:
参考我的博客:《跟我一起写Python》

阅读全文 »

Python实例--移除git pull冲突文件

发表于 2016-05-08   |   分类于 Python实例   |  

作用:移除git pull冲突文件

说明:当我们本地的一些工程比较老旧时,想同步服务器上最新的代码,但是当我们git pull时,会出现很多文件冲突,如果不想保留自己的修改,直接同步代码的话,可以使用本脚本来进行冲突文件的删除

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/user/bin/python
# -*- coding:utf-8 -*-
import os
import os.path

#获取当前目录的完整路径
homedir = os.getcwd()

#把git pull的冲突文件导出到conflict.txt中
os.system("git pull > conflict.txt 2>&1;")

#判断conflict.txt是否存在,如果存在,变量赋值,否则为空
if os.path.exists("conflict.txt"):
conflict_file = open('conflict.txt','r')
else:
conflict_file = None
print ('there is no conflict.txt file')

#删除指定目录文件函数
def removeFileInFile(dir,targetFile):
if os.path.exists(dir + '/' + targetFile):
os.remove(dir + '/' + targetFile)
else:
print ('no such file:%s' % dir + targetFile)

#while循环,从conflict.txt中按行读取,如果这一行是一个文件,则删除它,如果不是执行下一个
#当读到行尾后,跳出while循环
while 1:
if conflict_file != None:
target_line = conflict_file.readline()
target_line = target_line.strip()
if not target_line:
break
#print target_line
#print homedir + target_line
if os.path.isfile(target_line):
removeFileInFile(homedir , target_line)
else:
continue

if os.path.exists("conflict.txt"):
os.remove("conflict.txt")

os.system("git pull")

跟我一起写python--模块(15)

发表于 2016-05-07   |   分类于 跟我一起写python   |  

编程语言中模块是指数据说明、可执行语句等程序元素的集合,它是指单独命名的可通过名字来访问的过程、函数、子程序或宏调用。

我们通常在C\C++\JAVA中如果需要用到某个特定功能,需要调用对应模块的接口,需要使用include或者import相关头文件或者包。在python中我们同样的,先实现一个.py文件,在另一个文件中需要import时,将事先写好的.py文件拷贝 到当前目录,或者是在sys.path中增加事先写好的.py文件所在的目录,然后再import我们的模块名称。

即调用方法为:

模块名.函数名

1. 模块导入–import

在Python中用关键字import来引入某个模块,比如要引用模块sys,就可以在文件最开始的地方用import sys来引入。

例:

1
2
3
#!/usr/bin/python
import sys
print ("sys\'s path is", sys.path)

输出:

sys’s path is [‘/home/tianger/python’, ‘/usr/lib/python2.7’, ‘/usr/lib/python2.7/plat-x86_64-linux-gnu’, ‘/usr/lib/python2.7/lib-tk’, ‘/usr/lib/python2.7/lib-old’, ‘/usr/lib/python2.7/lib-dynload’, ‘/usr/local/lib/python2.7/dist-packages’, ‘/usr/lib/python2.7/dist-packages’, ‘/usr/lib/python2.7/dist-packages/PILcompat’, ‘/usr/lib/python2.7/dist-packages/gtk-2.0’, ‘/usr/lib/pymodules/python2.7’, ‘/usr/lib/python2.7/dist-packages/ubuntu-sso-client’]

说明:

1、import sys 引入 python 标准库中的 sys.py 模块;这是引入某一模块的方法。
2、sys.path 包含了一个 Python 解释器自动查找所需模块的路径的列表。

2.自定义模块

第一节我们用了系统的模块sys,但是通常情况我们需要自己写接口给自己调用,这就需要我们来实现自定义模块

我们先创建一下自定义模块hello.py,内容如下:

1
2
3
4
5
6
7
#!usr/bin/python

def say():
print ('hello world!')

def plus(a,b)
return a+b

我们再创建一个test.py,来调用hello.py中的say()和plus()函数实现,test.py内容如下:

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/python
import hello

print ('call hello\'s say function as follow:')

hello.say()

sum = hello.plus(3,5)

print ('call hello\'s plus function is', sum)

输出:

call hello’s say function as follow:
hello world!
call hello’s plus function is 8

说明:

import hello –加载我们之前写的hello模块
hello.say() –调用hello模块中的say函数,会自动打印出hello world!
sum = hello.plus(3,5) –向hello的plus函数中,传入两个值3、5,计算得到的结果赋值给sum

通过上面两个简单的例子,我们可以编写自己的模块了,让自己的工程进行模块化,不但可以使代码变得简洁,同时也增加了代码的复用性

跟我一起写python—函数 (14)

发表于 2016-05-02   |   分类于 跟我一起写python   |  

函数,名称出自数学家李善兰的著作《代数学》—“凡此变数中函彼变数者,则此为彼之函数”。即函数指一个量随着另一个量的变化而变化,或者说一个量中包含另一个量。

我们简单理解为,一个输入,对应一个唯一的输出关系。

编程语言中,函数即指一个语句块,语句块有自己的名字,他可以有输入,也可以有输出。

之前我们已经用过python的内建函数,例如print、len等,下面我们来讲函数的相关使用.

1.函数的定义

1)函数通过关键字def定义
2)def关键字后跟一个函数的标识符名称,再跟一对圆括号
3)圆括号中包括一些变量名、传入的参数,以冒号结尾
4)函数内容以冒号起始,并且缩进
5) 返回值用return +返回值进行上报,如果没有返回值,直接写一个return,相当于上报None

例:
1
2
3
4
5
6
#!/usr/bin/python

def test():
print (“This is a function test code!")

test()

输出:
This is a function test code!

2.函数形参

形参,即你传给函数的参数,函数取得这个值,并用这个值去做一些事情。
若果有多个形参,则需要用逗号进行分隔。
一个形参,例如:

1
2
3
4
5
6
7
8
9
#!/usr/bin/python
# -*- coding :UTF -8 -*-

def test(a):
if a>0:
return a

x = test(2)
print(x)

输出:2

两个形参:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/python
# -*- coding :UTF -8 -*-

def test(a,b):
if a > b:
print ("a is max num")
return a
else:
print ("b is max num")
return b

x = test(2,3)
print(x)

输出:
b is max num
3

3.局部变量

局部变量是在函数体内声明的变量,即使他们与函数外部具有相同的名称,但是他们没有任何关系。
例:

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/python
# -*- coding :UTF -8 -*-

def test(a):
print ("a is ",a)
a = 10
print ("a modify to ",a)

a = 2
test(a)
print ("a is still",a)

输出:
a is  2
a modify to  10
a is still

第一次把a赋值为2,传给test函数,这里的a为形参,test函数中a赋值为10,这里a为局部变量,其实跟我传进来的值没有任何关系,所以当跳出函数体后,a仍然为2

4.全局变量global

如果想上面例子中的a,我们想在test函数中真正能修改他的值,我们就需要把a作为一个全局变量,用global进行定义
例:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/python
# -*- coding :UTF -8 -*-

def test():
global a
print ("a is ",a)
a = 10
print ("a modify to ",a)

a = 2
test()
print ("a is still",a)

输出:
a is  2
a modify to  10
a is still 10
这里的a为一个全局变量
1234…6
Tianger Ge

Tianger Ge

3G/LTE protocol | C

53 日志
8 分类
14 标签
GitHub 微博
© 2015 - 2016 Tianger Ge
由 Hexo 强力驱动
主题 - NexT.Pisces