0%

边界检查

尽管列表没有固定的大小,但是Python仍然不允许引用不存在的元素,超出列表末尾之外的索引总是会导致错误,对列表末尾范围之外的赋值也是如此。

为了让一个列表增大,我们可以调用append这样的列表方法。

Python 命名规则

Python有自己的编码规则和命名规则,一般如下:

  • 变量名、包名、模块名用小谢
  • 类名首字母大写,或者全部大写,对象名小写
  • 函数名小写

Python 常量和变量

常量

常量是一旦初始化后就不能改变的变量,在C++中使用const指定常量。

变量

计算机内存中的一块区域,变量可以存储任何事,而且可以改变。

局部变量

1
2
3
4
5
6
7
8
9
10
11
a = 1
b = 2

def add():
a = 10
b = 5
return a + b

print (add())
print (a)
print (b)

全局变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
_a = 1
_b = 2

def add():
global _a
global _b
_a = 10
_b = 5
return _a + _b

def sub():
global _a
global _b
_a = 10
_b = 5
return _a - _b

print (add())
print (sub())
print(_a)
print(_b)

这里有一个global的关键字,如果没有这个关键字是不对的,结果可能对,但是实际的运行不是你认为的那样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
_a = 1
_b = 2

def add():
_a = 10
_b = 5
return _a + _b

def sub():
_a = 10
_b = 5
return _a - _b

print (add())
print (sub())
print(_a)
print(_b)

区别

编译器处理方式不同

  

  • define宏是在预处理阶段展开。
  • const常量是编译运行阶段使用。

类型和安全检查不同

  • define宏没有类型,不做任何类型检查,仅仅是展开。
  • const常量有具体的类型,在编译阶段会执行类型检查。

存储方式不同

  • define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。
  • const常量会在内存中分配(可以是堆中也可以是栈中)。

const 可以节省空间,避免不必要的内存分配。

const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而 #define定义的常量在内存中有若干个拷贝。

提高效率

编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

联系

作为常量,两者都有很多的应用,在一个程序里面看不到constdefine的可能性不大。

总结

C++ 语言可以用const来定义常量,也可以用#define来定义常量。但是前者比后者有更多的优点:比较重要的是const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应)。

得到随机数的代码:

1
2
3
srand(time(NULL)); //use clock value as starting seed。这个保证每次出来的随机数都不同
int number = 0;
number = rand();

在做for循环的时候,循环控制变量也可以是一个浮点类型的变量。例如:

for(double x = 1.0; x < 11; x += 1.0),但是由于浮点类型在计算机内部的表示形式,决定了分数值通常没有浮点数形式的精确表示,所以不应该把相等判断作为结束循环的条件,比如x != 2.0,就可能一直打不到要求。

  • goto语句可以用在有多层循环时,想跳出循环的时候设置一个label就可以了,没有必要逐层break。
  • do-while循环与for循环和while循环的不同之处在于,这个循环语句或语句块至少会执行一次。
  • continue:不结束循环,但是要跳过目前的迭代,继续执行下一个迭代。

三种循环

  • for循环:一般用于计算循环的次数,在该循环中,控制变量的值在每次迭代时递增或递减指定的值,直到到达某个最终值为止;
  • while循环:只要给定的条件为true就继续执行。如果循环条件一开始就是false,循环语句块就根本不执行;
  • do-while循环类似于while循环,但其循环条件在循环语句块执行后检查。

编写程序的规则和建议

  • 开始编写程序前,先规划好过程和计算的逻辑,将它写下来,最好采用流程图的形式;
  • 理解运算符的优先级,以正确计算复杂的表达式,如果不能确定运算符的优先级,就应使用括号。确保表达式完成预期的操作,使用括号更便于理解复杂的表达式;
  • 给程序加上注释,全面解释它们的操作和使用。要假设这些注释为为了方便别人阅读这个程序,并加以扩展与修改,声明变量时应说明它们的作用;
  • 程序的可读性是最重要的;
  • 在复杂的逻辑表达式中尽量避免使用!运算符;
  • 使用缩进格式,可视化地表达出程序的结构;

基本输入和输出操作

与大多数现代编程语言一样,C语言也没有输入输出的能力,所有这类操作都由标准库中的函数提供。

stderr流只是将来自C库的错误信息传送出去,也可以将自己的错误信息传送给stderr

stderrstdout之间的主要差别是,输出到stdout的流在内存上缓存,所以写入stdout的数据不会马上送到设备上,而stderr不缓存,所以写入到stderr的数据会立刻传送到设备上。
对于缓存的流,程序会在内存中传入或传出缓存区域的数据,在物理设备上传入或传出数据可以异步进行。这使输入输出比较高效,而为错误信息使用不缓存的流,其优点是可以确保错误信息显示出来,但输出操作是低效的。缓存的流比较高效,但是如果程序因某种原因失败,缓存的流就不会刷新,所以输出可能永远不会显示出来。

scanf会忽略空白字符

scanfprintf中有个格式控制符%n:表示输出有效字符的数量。

N多的scanf参数也可以保证,你可以用许多方式得到自己希望得到的数据。

但是有一点需要注意,就是scanf对输入格式很挑剔,稍微错一点就会导致整个读取输入出错。

scanf的格式控制符%s只能读取不含空格的字符串,但是%[]可以读取包含空格的字符串,比如I love you,就可以全部读取。

scanf的陷阱

  • 变元必须是指针,最常犯的错误是将变量指定为scanf的变元时,忘记在变量名的前面加上&符号,不过使用printf时不需要这个&字符,此外,如果变元时数组名或指针变量,也不需要&符号;
  • 在读字符串时,要确保有足够的空间存放读入的字符串,这个字符串需包含终止字符’\0’,否则,会覆盖内存中的饿数据,甚至是程序代码。

对于字符串输入,使用gets或fgets通常是首选方式,除非要控制字符串的内容。

C 集锦 一分钟看完C运算符

发了6篇关于C运算符的小知识,总结一下,而且真的,关于运算符的几个总结,1分钟确实读完。

其实对于C语言而言,入门其实相对而言还是比较简单的,因为他的短小精悍,可有可无的坚决不增加,掌握了基础知识就可以说是入门了,但是做到精通就比较困难了,这就需要在学习之外的实战了。

对于所有程序里面的运算符而言,熟练掌握这些就没有问题了。

C语言的数据运算符

这一节接触的都是新东西,hold住。

数据运算符顾名思义就是对数据进行操作的运算符,这里设计到几个新概念,如果刚开始不了解,也没有关系,毕竟是进阶的东西,先大概看一下有个基础概念,因为这一次我们接触到了C语言中最难懂也是最优雅的指针,此处有掌声。

​ 数据运算符有下面几个:

数据运算符 含义
sizeof() 获取xx的大小,返回字节长度
[] 数组
& 取地址
* 定义指针或者解指针
-> 结构体解应用
. 结构体应用

详细说说这几个的具体含义:

  • sizeof(xx):这个比较简单,就是计算xx的字节长度;
  • []:数组的定义方式,比如int a[10]就是定义了一个一维的整数数组,这个数组长度为10,但是有个问题要注意了,数组的长度从0开始,所以如果访问数组a,只能是a[0]到a[9],如果下标是10就越界报错了,其实ANSI也曾尝试着把这个下标从1开始矫正过来,但是几十年的传统还有兼容性,此事只能作罢;
  • &:取地址是取一个变量的地址,比如int a=0;我们顶一个了一个整数a,它的值是0,那么它位于哪里呢,这个就类似于你叫张三,你家的地址在桃花坞里桃花庵,桃花庵里第一间,另注意这个符号在C++里面是引用的含义,不要混淆;
  • *:这个星号就大有来头了,前后左右的含义不一样,古往今来也很很多人栽进去了,就是因为这个隐晦又高效的指针,所以有了一本书叫做《C和指针》,专门来讲讲这个东西,后面我们也会 有系列小篇,争取能让大家越过这个坎,那就算是C语言里面的高手了;
  • ->和.:这两个的区别主要在于结构体,如果定义的结构体是指针用->,如果不是用.,这个我也迷惑过。

看个例子来show一下:

这个例子又新增了一些东西,除了上面描述的运算符,我们增加了一个头文件string.h,这个用来后面调用strcpy函数,另外还使用了结构体,下一节来聊聊必须要使用的结构体。

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
/*advance/operator/operator3.c*/
#include <stdio.h>
#include <string.h>

int main()
{
int a;
printf("sizeof(a) is %d \n", sizeof(a));

int arr[100];
arr[0] = 0;
arr[2] = 2;

printf("The value of arr[0] is %d\n", arr[0]);
printf("The value of arr[1] is %d\n", arr[1]);
printf("The value of arr[2] is %d\n", arr[2]);

printf("The address of a is %X\n", &a);
printf("The address of arr is %X\n", arr);

int *pa = &a;
printf("The address of a is %X\n", pa);
int *parr = arr;
printf("The address of arr is %X\n", parr);

struct person
{
char name[20];
int age;
};

struct person lilei = {"Li Lei", 18};
printf("Person 1 : Name: %s\t Age : %d\n", lilei.name, lilei.age);

struct person hanmeimei;
struct person *phanmeimei = NULL;
phanmeimei = &hanmeimei;
strcpy(phanmeimei->name, "Han Meimei");
phanmeimei->age = 16;
printf("Person 2 : Name: %s\t Age : %d\n", phanmeimei->name, phanmeimei->age);
return 0;
}

相应地Makefile如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#advance/operator/Makefile

ALL : operator1 operator2 operator3

operator1: operator1.c
gcc -o operator1 operator1.c

operator2: operator2.c
gcc -o operator2 operator2.c

operator3: operator3.c
gcc -o operator3 operator3.c

.PHONY : clean

clean:
rm -f operator1 operator2 operator3

输入make,然后./operator3输出为:

1
2
3
4
5
6
7
8
9
10
sizeof(a) is 4
The value of arr[0] is 0
The value of arr[1] is 0
The value of arr[2] is 2
The address of a is EDFAD6D8
The address of arr is EDFAD710
The address of a is EDFAD6D8
The address of arr is EDFAD710
Person 1 : Name: Li Lei Age : 18
Person 2 : Name: Han Meimei Age : 16

Q&A :上上节的答案,为什么a没有赋值却有这么大的值,这个涉及到后面会说到的全局变量和局部变量的区别,局部变量在未初始化之前,有可能是任何值,所以为防意外,变量定义最好都要初始化。

Ubuntu 安装GTK

我利用此方法成功在UBUNTU 10.04下安装GTK 2.20.1。

安装

1、安装gcc/g++/gdb/make 等基本编程工具

1
$sudo apt-get install build-essential

2、安装 libgtk2.0-dev libglib2.0-dev 等开发相关的库文件

1
$sudo apt-get install gnome-core-devel

3、用于在编译GTK程序时自动找出头文件及库文件位置  

1
$sudo apt-get install pkg-config

4、安装 devhelp GTK文档查看程序

1
$sudo apt-get install devhelp

5、安装 gtk/glib 的API参考手册及其它帮助文档

1
$sudo apt-get install libglib2.0-doc libgtk2.0-doc

6、安装基于GTK的界面GTK是开发Gnome窗口的c/c++语言图形库

1
$sudo apt-get install glade libglade2-dev

或者

1
$sudo apt-get install glade-gnome glade-common glade-doc

7、安装gtk2.0 或者 将gtk+2.0所需的所有文件统通下载安装完毕

1
$sudo apt-get install libgtk2.0-dev

或者

1
$sudo apt-get install libgtk2.0*

  

查看GTK库版本

1、查看1.2.x版本

1
$pkg-config –modversion gtk+

2、查看 2.x 版本

1
$pkg-config –modversion gtk+-2.0

3、查看pkg-config的版本

1
$pkg-config –version

4、查看是否安装了gtk

1
$pkg-configlist-all grep gtk

  

测试程序

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
//Helloworld.c

#include <gtk/gtk.h>

int main(int argc,char *argv[])
{
GtkWidget *window;
GtkWidget *label;

gtk_init(&argc,&argv);

/* create the main, top level, window */
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

/* give it the title */
gtk_window_set_title(GTK_WINDOW(window),“Hello World”);

/* connect the destroy signal of the window to gtk_main_quit
* when the window is about to be destroyed we get a notification and
* stop the main GTK+ loop
*/
g_signal_connect(window,“destroy”,G_CALLBACK(gtk_main_quit),NULL);

/* create the “Hello, World” label */
label = gtk_label_new(“Hello, World”);

/* and insert it into the main window */
gtk_container_add(GTK_CONTAINER(window),label);

/* make sure that everything, window and label, are visible */
gtk_widget_show_all(window);

/* start the main loop, and let it rest until the application is closed */
gtk_main();

return 0;
}

  

编译运行

1、编译

1
$gcc -o Helloworld Helloworld.c `pkg-config –cflags –libs gtk+-2.0`

2、运行

1
$./Helloworld

C语言的赋值运算符

​ 赋值运算符就是把表达式或者值赋给变量,比如极易混淆的=为赋值,等于是==

​ 其实赋值运算符可以和很多前面说的算术运算符、逻辑运算符和按位运算符合并使用。

​ 如下如下:

赋值运算符 含义
= 赋值,等于
+= 加后赋值
-= 减后赋值
*= 乘后赋值
/= 除后赋值
%= 取模后赋值
&= 按位与后赋值
|= 按位或后赋值
^= 按位异或后赋值
<<= 按位左移后赋值
>>= 按位右移后赋值

举个例子来show一下:

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
/*advance/operator/operator2.c*/
#include <stdio.h>

int main()
{
int a;
printf("a is %d\n", a);
a = 5; //0b101
printf("a is %d\n", a);

printf("%d += 2 -> %d \n", a, a += 2);
a = 5; //0b101
printf("%d -= 2 -> %d \n", a, a -= 2);
a = 5; //0b101
printf("%d *= 2 -> %d \n", a, a *= 2);
a = 5; //0b101
printf("%d /= 2 -> %d \n", a, a /= 2);
a = 5; //0b101
printf("%d %%= 2 -> %d \n", a, a %= 2);
a = 5; //0b101
printf("%d &= 2 -> %d \n", a, a &= 2);
a = 5; //0b101
printf("%d |= 2 -> %d \n", a, a |= 2);
a = 5; //0b101
printf("%d ^= 2 -> %d \n", a, a ^= 2);
a = 5; //0b101
printf("%d <<= 2 -> %d \n", a, a <<= 2);
a = 5; //0b101
printf("%d >>= 2 -> %d \n", a, a >>= 2);

return 0;
}

相应地Makefile如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#advance/operator/Makefile

ALL : operator1 operator2

operator1: operator1.c
gcc -o operator1 operator1.c

operator2: operator2.c
gcc -o operator2 operator2.c

.PHONY : clean

clean:
rm -f operator1 operator2

输入make,然后./operator2输出为:

1
2
3
4
5
6
7
8
9
10
11
12
a is 107106770
a is 5
5 += 2 -> 7
5 -= 2 -> 3
5 *= 2 -> 10
5 /= 2 -> 2
5 %= 2 -> 1
5 &= 2 -> 0
5 |= 2 -> 7
5 ^= 2 -> 7
5 <<= 2 -> 20
5 >>= 2 -> 1

可以看到结果就是对每一位进行的操作

那么问题来了,你知道为什么a刚开始的值是107106770吗,为什么每次都给a赋值吗?