阅读时,从内循环入手。
计算机的工作是建立在抽象的基础上。
机器语言和汇编语言是对机器硬件的抽象
高级语言是对汇编语言和机器语言的抽象
传递一个数组为什么需要两个参数?
如何实现一个简单病毒
while(1){int *p=new int[10000000];}
换行(\n)就是光标下移一行却不会移到这一行的开头,回车(\r)就是回到当前行的开头却不向下移一行。
按Enter键后会执行“\n\r”就是一般意义上的回车了。
用自己的话描述逐步细化的过程。
逐步细化就是将一个大问题分成若干个小问题,小问题分解成小小问题,直到一个问题可以用一段小程序实现为止。每个问题的解决过程是一个函数,实现小问题的函数通过调用解决小小问题的函数来实现。解决大问题的函数通过调用解决小问题的函数来实现。在解决一个较大的问题时,只需要知道有哪些可供调用的解决小问题的函数,而不必关心这些解决小问题的函数是如何实现的。这样可以在一个更高的抽象层次上解决大问题。
为什么库的实现文件要包含自己的头文件?
保证实现文件中的原型和提供给用户程序员用的函数原型完全一致。
1 库文件包含,为可能需要调用的库函数或类定义包含库文件。
2 类型定义,包括结构体类型、类类型;
3 函数声明;
4 其他文件中的全局变量的extern声明;
5 变量定义:为一些在程序编写时值未知的数据预约他们的存放处 ;
6 输入阶段 :获取执行时才能确定的用户数据。输入过程一般包括两步 :
6.1 显示提示信息;
6.2 读取数据;
7 计算阶段 :由输入推导出输出的过程。通常通过各种计算得到。
8 输出阶段:显示程序执行的结果。
分而治之法
分:分成较小的可以递归解决的问题
治:从子问题的解形成原始问题的解
分而治之算法通常都是高效的递归算法
在分而治之法中,递归是“分”,额外的开销是“治”
当程序变得更长的时候,要在一个单独的源文件中处理如此众多的函数会变得困难。
把程序再分成几个小的源文件。每个源文件都包含一组相关的函数。一个源文件被称为一个模块。
模块划分标准:块内联系尽可能大,块间联系尽可能小。
库的接口表现为一个头文件。
俄罗斯方块,将游戏区域划分为m行*n列的棋盘,定义一个布尔型的二维数组变量,以便判断棋盘上的方格中是否有方块。
方块图形都是4个小块,以其中的某个小块(一般用方块图形角上的小块)为坐标原点(0,0),用一个二维数组来表示一个方块。
索引存储方式是另外多开一块存储空间,专门通过索引表来存储数据元素之间的关系。
哈希存储方式通常用来存储数据元素之间没有关联的集合类数据。
字符串的结束标志:空字符('\0')。
文件的结束标志:EOF。
程序设计和程序执行的过程的过程可以和产品生产设计和实际制造的过程进行类比。
堆、栈的概念有数据结构、内存存储两个方面,完全不同的概念。
堆栈常见应用:递归程序的调用和返回、算术表达式的转换与求值、调用子程序和返回处理等;
关键字分为:1 数据类型关键字16个;2 存储类型关键字4个;3 控制类型关键字;
让电脑蜂鸣器发出声音:printf("\7"));
c语言中{}可以用于数据的初始化;
断点是指在程序编译时暂停到某一点。
异常处理:健壮、友好、异常捕捉后的代码可以继续执行。除了捕捉系统抛出的异常,还可以捕捉自定义的throw抛出的异常。
链表结点:结点对应一个地址,当new一个节点后,会返回一个指针指向这个节点,而node本身也是包含一个指针域,指向另外一个节点。
#pragma c9x on
让编译器支持C9X
设计一种数据类型包括设计如何储存该数据类型和设计一系列操作该数据的函数。
编辑思维:机械思维+灵活变通;
面向过程:思考解决问题的整个过程,用函数分解;
面向对象:思考解决问题的参与对象,数据与行为结合为对象;
标识符是一段连续内存地址的命名,基本数据类型有编译器规定的长度,所以知道需要访问多长的一段内存地址。数组呢,有长度与类型一起确定内存的长度,字符串呢?以null字符\0来标识一段内存的结束位置。
在字符串中,以\n\r来标识换行,所以,一个包含\n\r的字符串就可以对话一个文件。
名字限定的作用域包括结构体、名字空间、类,分别使用.->::等运算符。
main函数执行之前,主要就是初始化系统相关资源:
1 设置栈指针
2 初始化static静态和global全局变量,即data段的内容
3 将未初始化部分的全局变量赋初值:数值型short,int,long等为0,bool为false,指针为NULL,等等,即.bss段的内容
4 运行全局构造器,估计是C++中构造函数之类的吧
5 将main函数的参数,argc,argv等传递给main函数,然后才真正运行main函数
5.1 全局对象的析构函数会在main函数之后执行;
5.2 可以用_onexit 注册一个函数,它会在main 之后执行;
如果你需要加入一段在main退出后执行的代码,可以使用atexit()函数,注册一个函数。
char *month[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
条件语句中的表达式一般为逻辑表达式或关系表达式
结构或类访问成员使用点号.,点号前面的是这一大块内存的首地址,点号后面的名称(数据成员名称)是这一大块中每一小块的的首地址。
#pragma pack(1) //C编译器按n字节对齐。
<assert.h>不一个assert宏,它可用于验证程序做出的假设,并在假设为假时输出诊断消息。
<ctype.h>测试和映射字符,参数int c,包括11个is函数,2个转换函数;
<errno.h>表示不同错误代码的宏、整数变量 errno、EDOM Domain Error、ERANGE Range Error
<float.h>一组与浮点值相关的依赖于平台的常量
<limits.h>一组限制各种变量类型(比如 char、int 和 long)的值的宏
<locale.h>定义了特定地域的设置,比如日期格式和货币符号。以及一个重要的结构 struct lconv
<math.h>各种数学函数和一个宏。都带有一个double类型的参数,且都返回double类型的结果。
<setjmp.h>宏setjmp()、函数longjmp和变量jmp_buf,该变量会绕过正常的函数调用和返回规则。
<signal.h>一个变量sig_atomic_t、两个函数和一些宏来处理程序执行期间报告的不同信号。
<stdarg.h>变量va_list和三个可用于在参数个数可变时获取函数中的参数的宏。参数末尾用...
<stddef.h>定义了各种变量类型和宏。如size_t、wchar_t、NULL
<stdio.h>三个变量类型、一些宏和各种函数来执行输入和输出(包括文件)。
<stdlib.h>四个变量类型、一些宏和各种通用工具函数。转换、绝对值、随机数、终止、system、动态内存管理、查找、排序函数
<string.h>一个变量类型、一个宏和各种操作字符数组的函数。字符串不是变量,字符串通过定义为一个有终止符\0的字符数组或用一个字符指针来初始化以及一些字符串操作函数来完成字符串操作
<time.h>四个变量类型、两个宏和各种操作日期和时间的函数。
使用extern关键字只声明而不定义变量。
在程序中使用自定义类型的好处是能够提高程序的移植性。同一种数据类型,在不同的机器上或不同的操作系统上,其长度或者性质可能是不同的。如果程序中统一使用了自定义类型,在修改程序时,只需要修改自定义类型的基类型就可以了,代码的其他地方不需要改动。
对于自增自减运算符来说,不仅可以应用于数值型变量,还可以应用于指针变量。在开发程序时,通常使用自增运算符来实现指针遍历数组。
常用来操作循环变量和指针偏移。
variable is a named location with unique name,used to stored data according to the requirement.
Windows API是在C++出现之前开发的,因此经常用来在Windows和应用程序之间传递数据的是结构体而不是类.
相对内说,memcpy()的速度比较快;
unsigned int imax=0;
imax = ~imax;
cout<<imax; //4294967295
cout<<hex<<imax; //ffffffff
int imax2 = -1;
cout<<dec<<(unsigned int)imax2; //4294967295
编译完成后,变量的名字会被实际或虚拟的内存地址所代替。如果在运行期间给你一个内存地址。你也可以用一个变量把他存储起来。链式存储也是如此。函数作为数据处理的机器也是如此,我不是直接处理物理,而是按你提供的地址到指定的地点去处理。这些需求都是需要用一个变量来存储地址,也不是实际的数据值,这种存储地址的变量在C++叫指针变量。
约定了名字和数据类型的存储空间称作一个变量(variable)。这个名字就叫变量,而数据类型则称为变量的数据类型,简称变量类型。
变量=变量名+数据类型
字符串字面量在内存的数据段保存为字符数组,为只读。因此,可以用一个字符指针指向它。
关键字volatile有什么含意?
提示编译器对象的值可能在编译器未监测到的情况下改变。
引用可以返回左值;
指针可以从函数中返回动态分配的对象或内存;
double power(double x, int n) { double val=1.0; while (n--) val *= x; return(val); }
#define(宏函数)执行查找和替换操作。
分开编写,分开编译,然后链接;
内存块函数,以mem开头,如memcpy(),也包含在
m%n,n就相当于是一个模数,其结果都不会超过这个模数,当想生成一定范围内的随机数时,可以使用%操作符。
正则表达式可以辅助执行词法分析(lexical analysis),即智能地把输入字符串分解成片段的任务,以及诸如把一个文本文件格式转换为另一种格式的任务。
标准的C和C++都不支持正则表达式,但有正则表达式的函数库提供这功能。
C语言本身不具备RE特性,但是有很多库,在Linux下你可以很方便的使用regex.h提供的库。
C++11的有regex库
仿布尔型:
#define true 1 #define false 0 enum bool{ false, true };
标识符其实质就是对应一个内存地址,如变量名、常量名、指针变量名、函数名、数组名、对象名;当然类名、结构体名等自定义类型的类名需要具体定义为变量后才对应一个内存地址。
这些地址都可以用指针变量指向。
读文件是否结束:
if(feof(fp)==EOF)
运算符优先级:算术→关系→逻辑→赋值→逗号;
作用域:块、函数原型、函数、类、文件;
\ddd ddd表示1到3位八进制数字
\xhh hh表示1到2位十六进制数字
有些系统(如Turbo C)将字符变量定义为signed char型。其存储单元中的最高位作为符号位,它的取值范围是-128~127。如果在字符变量中存放一个ASCII码为0~127间的字符,由于字节中最高位为0,因此用%d输出字符变量时,输出的是一个正整数。如果在字符变量中存放一个ASCII码为128~255间的字符,由于在字节中最高位为1,用%d格式符输出时,就会得到一个负整数。
混合运算:整型(包括int,short,long)、浮点型(包括float,double)可以混合运算。在进行运算时,不同类型的数据要先转换成同一类型,然后进行运算.
pointer_1=&a;&* pointer_1的含义是什么?“&”和“*”两个运算符的优先级别相同,但按自右而左方向结合。因此,&* pointer_1与&a相同,即变量a的地址。
sizeof(f00());对函数地址操作返回的是函数返回值类型的内存字节数,所以不能对返回void类型的函数进行sizeof操作。
自增自减运算符的前置或后置单独使用时是没有区别的,但用做右值时是有区别的。(因为其表达式是有副作用的)
长行代码分两行书写:在一行的末尾加一反斜线符号可将此行和下一行当作同一行处理。注意反斜线符号必须是该行的尾字符——不允许有注释或空格符。同样,后继行行首的任何空格和制表符都是字符串字面值的一部分。正因如此,长字符串字面值的后继行才不会有正常的缩进。在define中,也可以通过使用反斜线符号使用多行。
至于编译器自动对齐的原因,是为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。(最终的目的还是为了实现指针在内存中的有规律跳动,以实现更高的效率。)(union也有内存对齐或整型提升。)
union un{ char ch; float f; }; // sizeof(un) == 4
强制类型转换就是程序员明确提出的、需要通过特定格式的代码来指明的一种类型转换。
移位运算符是将数据看成二进制数,对其进行向左或向右移动若干位的运算。移位运算符分为左移和右移两种,均为二元运算符。
在进行C语言某些算法编程的时候,需要使几种不同类型的变量存放到同一段内存单元中。也就是使用覆盖技术,几个变量互相覆盖。这种几个不同的变量共同占用一段内存的结构,在C语言中,被称作“共用体”类型结构,简称共用体。
随机函数rand()用于产生从0开始到32767之间的随机数,它的使用语法如下:
RAND_MAX
int r;
r=rand();
表示生成一个随机数并赋予变量r。
#include<time.h>
srand((unsigned)time(NULL));
并不是任何地方出现的逗号都是作为逗号运算符。例如函数参数也是用逗号来间隔的。
二维数组中的元素在内存中的排列顺序是:按行存放,即先顺序存放第一行的元素,再存放第二行的元素……(按行首尾相连)
对静态局部变量是在编译时赋初值的,即只赋初值一次,在程序运行时它已有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的值。
在预编译时将宏名替换成字符串的过程称为“宏展开”。
数组名作函数的参数,主调函数和被调函数共用一段存储单元。
编译器会根据函数的返回值类型和参数表来区分函数的不同重载形式。
声明一个volatile变量会要求编译器在每次使用该变量时都会获取它的一个新的副本。而不用通过该变量的值保存在一个寄存器中并复用它来优化程序。
一次非对齐的内存访问的时间相当于这些字节在同一个字中时的两倍,因为需要读取两个字。
计算机并不一定会一次读取一个字节,会通过获得更大块的数据来补偿缓慢的内存速度,如1字节,64字节,一个字等;
如今所有处理器都可以在一个内部时钟周期中执行一次位移或是加法操作。
x*4,x<<2更高效;
x*9=(x<<3)+x
内存管理器是C++运行时系统中监视动态变量的内存分配情况的一组函数和数据结构。
函数的指针还可以是表达式、函数调用;
共用体:同一段内存空间可以有多个类型形式;
continue放弃当前循环,继续下一次循环
从函数返回对象时,程序对该变量进行复制,然后将副本返回;
/*C89:*/ int i; for(i = 0;i < 10;++i) /*...*/ ...
/*C99*/ for(int i = 0;i < 10;++i) /*...*/
这只是其中的一种差别,但是C99需要人为手动的开启,但是很多人有疑问,为什么有时候没有配置什么也能使用后面的语法?这是因为你用了C++的文件进行C语言的开发,就像挂羊皮卖狗肉的道理。
char* point_to_me = me; point_to_me++; 的语法糖,对于++,在非必要的情况下,请使用前缀递增,而非后缀递增,原因是消耗问题,仔细想想这两种递增的区别在何处? 前缀递增总是在原数上进行递增操作,然而后缀递增呢?它首先拷贝一份原数放于别处,并且递增这份拷贝,在原数进行的操作完毕后,将这份拷贝再拷贝进原数取代它,此中的操作涉及的更多,所以在非必要的情况下,请使用前缀递增而不是后缀递增(递减也是同样的道理)
volatile 这个关键字常常被C语言教材所忽略,它很神秘。实际上确实如此,他的作用的确很神秘:一旦使用了,就是告诉编译器,即使这个变量没有被使用或修改其他内存单元,它的值也可能发生变化。通俗的说就是,告诉编译器,不要把你的那一套优化策略用在我身上。
一定要放一个 default 在最后,即使它永远不会用到。
inline int func_0(int arg_1, int arg_2) { return arg_1 + arg_2; } inline int func_1(int arg_1, int arg_2) { return arg_1 - arg_2; } inline int func_2(int arg_1, int arg_2) { return arg_1 * arg_2; } inline int func_3(int arg_1, int arg_2) { return arg_1 / arg_2; } #define CALL(x, arg1, arg2) func_##x(arg1, arg2) ... printf("func_%d return %d\n",0 ,CALL(0, 2, 10)); printf("func_%d return %d\n",1 ,CALL(1, 2, 10)); printf("func_%d return %d\n",2 ,CALL(2, 2, 10)); printf("func_%d return %d\n",3 ,CALL(3, 2, 10));
打个不太贴切的比喻,假设计算机是一个家,CPU是一个人,想象一下这个家中的所有物品都是井然有序的,这个人想要工作必然会需要工作物品,所以他需要从某些地方拿来,用完以后再放回去,这些地方就是存储器,但是过了一段时间发现这么做太浪费时间,有时候某些东西太远了,所以,人想把它把它放在离自己更进的地方,这样自己的效率就高很多,如果这个东西一段时间内不再用,则把它放回原处,留出位置给更需要的工作物品,于是形成了越常使用的物品离人越近的现象。这便是计算机存储器的分层结构的意义。
而对于一个有良好局部性的程序而言,我们总能在离自己最近的地方找到我们所需要的数据,回到计算机:我们知道计算机的存储器是分层结构的,即每一层对应着不同的读写速度等级(CPU寄存器 > 高速缓存 > 主存 > 硬盘),而我们的程序总是按照从左至右的顺序依次查找,每次找到一个所需要数据,不出意外,总是将其移动到上一层次的存储器中存储,以便下次更高速的访问,我们称这种行为叫做命中 。越好的程序,越能将当时所需的数据放在越靠近左边的地方。这便是局部性的意义所在。
当然,存储器如此分层也是出于无奈,在处理器的速度和存储器的速度实在差距的情况下只有如此做才能让处理器更加充分的利用,而不至于等待存储器读写而空闲,也许某一天,当内存的位价和普通硬盘不相上下或者差距不多的时候,也许内存就是硬盘了。而当今也有人使用某些特殊的软件在实现这个功能,凭着自己计算机上大容量的内存,分割出来当作硬盘使用,存取速度让硬盘望尘莫及。
volatile int i = 0;
首先它的现象本质就是,确保每次读取 i 的时候,是从它的内存位置读取,每次对它操作完毕后,将结果写回它的内存位置,而不是将其优化保存在寄存器内。
{}除了用做语句块,还可以用做初始化;
下载编译好的二进制包,预处理好(某些操作,不多说,网上有教程,记得用谷歌,或者到博客园里找类似的,但是版本比较老可能和我用的有一些出路,但可以依着葫芦画瓢)以后,将路径配置到工程里:
创建一个Win32程序,并且在属性管理器(左侧栏下部寻找)中创建属性表(Debug和Release各创建一个,设置都相同即可)
打开新建的属性表
通用属性->VC++目录->包含目录->编辑 添加下载下来的文件中的glib\glib2.28\include目录,不放心的还可以再添加一个glib\glib2.28\lib\glib-2.0\include目录
通用属性->VC++目录->库目录->编辑 添加glib\glib2.28\lib目录
通用属性->链接器->输入->附加依赖项 添加glib\glib2.28\lib目录下的所有.lib文件,即将这些文件的名字都手动输入进去,如果使用我的这个版本的话那就是
gio-2.0.lib glib-2.0.lib gthread-2.0.lib gmodule-2.0.lib gobject-2.0.lib
通用属性->C/C++->代码生成->运行库开启多线程/MT
Okay!成了
A union is a struct in which all members are allocated at the same address so that the union occupies only as much space as its largest member. Naturally, a union can hold a value for only one member at a time.
The C Standard strncpy() function is frequently recommended as an alternative to the strcpy() function. Unfortunately, strncpy() is prone to null-termination errors and other problems and consequently is not considered to be a secure alternative to strcpy().
程序库:头文件(库函数原型,由编译器来做类型检查)+库文件(函数实现,由链接器链接,将代码复制到可执行文件中)。
VC++提供了一个名为comment的指令,它可以搭配lib选项给链接器发送一个特定信息,以链接特定的程序库。因此头文件中的代码:
#pragma comment(lib, "mylib")
将告知链接器链接程序库mylib。一般来说,最好使用项目管理工具,比如nmake或者MSBuild,用于确保将正确的程序库链接到项目中。
大部分C运行时库是以如下方式实现的,在一个静态库或者动态链接库中编译函数,然后在头文件中声明函数原型。开发人员在链接器命令行中提供了程序库,通常还将为该程序库引用头文件,以便编译器能够访问函数原型。只要链接器能够识别该程序库,就可以在项目代码中输入其函数原型(将它定义为外部链接,以便告知编译器函数是在其他地方定义的)。这样可以省去将某些大型文件引入到源代码中的麻烦,因为这些文件中很有可能包含大量不会用到的函数原型。
cout << "long最大值:" << (numeric_limits<long>::max)(); //#include <limits>通过指针访问字符串常量:
char* str="hellow";
Generally, system programmers aren’t willing to pay overhead for programming convenience, so C adheres to the zero-overhead principle: what you don’t use, you don’t pay for. The strong type system is a prime example of a zero-overhead abstraction. It’s used only at compile time to check for program correctness. After compile time, the types will have disappeared, and the emitted assembly code will show no trace of the type system.
Operators are functions that perform computations on operands. Operands are simply objects.
const wchar_t* ws = L"中文abc";
大写的L是告诉编译器:这是宽字符串。所以,这时候是需要编译器根据locale来进行翻译的。
左值一般在内存,右值一般在寄存器。
左值:可寻址的非只读表达式;
数列求和一般会有两个迭代表达式
有1、2、3、4三个数字,能组成多少个互不相同且无重复数字的三位数?分别是多少?
可以采用三重循环组成所有的排列,而后再用if语句选择满足条件的排列即可。
斐波拉契数列
f1 f2 f1 f2
f1 = f1+f2
f2 = f1+f2
求字符串长度:
while (a[la++]);
复合语句由一对花括号将一组语句序列括起来形成一个程序段,经常出现在选择或循环语句中。当选择语句的分支和循环语句的循环体由多条语句组成时,就必须用花括号括起来形成完整的复合语句,起到层次划分的作用。甚至可以将若干条基本语句用花括号括起来,组成一个局部范围内的程序段,对变量的作用范围产生影响。
int n = a+rand()%(b-a+1);
int m = rand()%11;
wchar_t是一个typedef而不是一个内部类型,_t就是用来区分标准类型与typedef;
不使用realloc( )。
memcpy与memmove的目的都是将N个字节的源内存地址的内容拷贝到目标内存地址中。
但当源内存和目标内存存在重叠时,memcpy会出现错误,而memmove能正确地实施拷贝,但这也增加了一点点开销。
全局数据出场顺序不分先后。
union{ unsigned int i; char ch[4]; }endian; endian.i = 0x12345678; for(int j=0; j<4; j++) { cout<<hex<<(int)endian.ch[j];// 78563412 }
Prefix (unary):Right-to-left
可以运算的地址叫指针,不可以运算的地址叫普通变量。
// 5 个函数指针的数组,函数返回指向 3 个 int 的数组的指针
int (*(*callbacks[5])(void))[3]
// 与 typedef 相同
typedef int arr_t[3]; // arr_t 是 3 个 int 的数组
typedef arr_t* (*fp)(void); // 指针指向的函数返回 arr_t*
fp callbacks[5];
#include <stdio.h> int foo(int *a, int *b) { *a = 5; *b = 6; return *a + *b; } int rfoo(int *restrict a, int *restrict b) //C //int rfoo(int *__restrict__ a, int *__restrict__ b) //C++ { *a = 5; *b = 6; return *a + *b; } int main() { int i =0; int *a = &i; int *b = &i; printf("%d ",foo(a,b)); // 12 printf("%d ", rfoo(a,b)); // 12 }
void convert(int x) // 十进制用二进制输出 { if((x/2)==0) cout<<x; else { convert(x/2); cout<<x%2; } }
int sprintf(char* string, char* format[, arguments,…]):将字符串string的内容重新写为格式化的字符串。
int scanftf(char* string, char* format[, arguments,…]):将字符串string分别对各个参数进行赋值。
一、命名空间缺失
1、两个文件中如果有同一名称的全局变量后,链接会报错。
2、include了两个包含相同宏名的头文件时,预处理时会报错。
3、当两个.lib库中有相同名称的变量时,链接的时候会选择第一个lib库中的变量。
二、宏
我们知道预处理阶段会替换宏,所以预处理器非常简单,根本无法检查宏函数的有效性,所以很多语言中淡化了宏或者直接取消了宏功能。
三、内存
C语言对内存的访问很直接,程序中很多bug都是出在内存问题上,不是程序猿很菜,是语言设计上就没有太多考虑内存的管理操作问题。
四、对平台抽象不够
1、数据长度问题:16位平台上 int是16bit,但32平台上是32bit,这样程序移植上就有问题。
2、数据大小端问题。
五、包管理器
c语言生态没有一个特别好用的包管理器,可以源码和二进制级别管理代码,c语言库的依赖问题,这是个让每个程序猿都非常头疼的问题。
共用体是指不同的数据类型共用一段内存空间,由共用体定义的变量指向首地址。注意共用体对于小端和大端数据的访问有些区别。
union-共联体,是C语言常用得关键字。从字面上的意思就是共同联合在一起的意思,union所有的成员共同维护一段能够内存空间,其内存的大小取决于所有成员中占用空间最大的成员。
位域可能对于初学者用得比较少,不过对于大部分参加工作的工程师应该屡见不鲜了,确实它也是我们省内存的神器。
结构体对齐问题可能大部分人关注的不是很多,可能在通讯领域进行内存的copy时候接触得比较多。结构体对齐问题也是与平台相关,CPU为了提高访问内存的效率,一次性可能读取2个字节,4个字节,8个字节等,所以编译器会自动对结构体内存进行对齐。
算法优化其实主要是我们通过修改一些算法的实现一种效率与内存使用的一个平衡,我们都知道我们的算法都存在着复杂度的问题,我们大部分高效率的算法都是通过使用内存来换效率,也就是一种用空间换时间的概念。那么当我们内存使用有限的时候我们可以适当的用时间来换空间的方法,腾出更多的空间来实现更多的功能。还有就是尽量使用局部变量来减少全局变量的使用!
C语言又称为面向函数的语言!
一维数组作为参数,可以不限定元素个数。
多维数组作为参数,第一维可以不限定,其余各维必须限定。
*和&是互逆的两个操作符,可相互抵消。
匿名联合通常被定义在一个结构或类类型的内部,联合中的成员直接作为所在结构或类中的成员使用,但任一时刻只有一个有效。
副本机制:函数传值的参数传递,函数返回时,对于基本数据类型,会保存到寄存器,对于复合数据类型,被调函数会临时开辟一片空间用于保存返回值(内存地址也保存在寄存器中)。
一、表达式的副作用
1、在表达式的求值过程中不但要提取变量的值,还可能改变变量的值。
如:k=m++
2、表达式能产生副作用的原因:引入了具有副作用的操作。
表达式作为语句使用时,它的功能通过副作用来体现。因此把没有副作用的表达式作为语句使用是无意义的。
如:x+=5;(有意义)
k+1;(无意义)
循环初始化和循环参数调整都应当是具有副作用的表达式,其中循环参数调整应当能够影响循环条件。
数组没有副本机制;
对于传址,只有一个指针(地址)大小的副本。
作为语句的函数调用的功能由函数的副作用体现。
数值表达式副作用的有关结论,对于指针表达式同样适用。
作用于变量的操作只能施加于变量对象。
当若干个作用于变量的操作施加于同一变量时,除了最后一个外,不得有后增1或后减1操作。
表达式的副作用
1、在表达式的求值过程中不但要提取变量的值,还可能改变变量的值。
如:k=m++
2、表达式能产生副作用的原因:引入了具有副作用的操作。
函数的传参和返回都存在隐式类型转换,可能会抛出异常,从而改变流程走向。
程序中使用内存单元存放数据。程序可以对存储单元以标识符命名。
对内存的读、写操作称为访问。
既能读又能写的内存对象,称为变量;若一旦初始化后不能修改的对象则称为常量。
对于数据单元,名访问就是操作对象的内容。
C++允许通过名或地址(并根据类型)访问对象 。
能够存放对象地址的变量,简称“指针变量” 。
间址访问:读出指针变量的地址值,查找该地址的存储单元,用关联类型解释并读出数据。
a=b;//a←b,将b的值写入a
pa=pb;//pa→pb,pb的地址赋给pa,pa指向pb
线性表是重要的数据结构。动态数据结构中,数据元素类型定义必须包含表示数据关系的指针。
动态链表可以在程序运行时创建或撤销数据元素。
#define definition existed
typedef existed definition;
1 圆括号(函数参数的括号通常写在后面,除外),做为核心,其它部分是修饰;如char* (*pf) (int i); 核心是指针,其它是修饰,所以是函数指针。
2 并列部分核心落在最后,前面的是修饰,如char* arr[12]; 核心是数组,指针修饰数组,所以是指针数组。int (*p)[10]就是一个数组指针。
3 char* (*pfa[3]) (int i); 核心是*pfa[3],然后是[3],是一个数组,一个指针数组,一个函数指针数组。
(*(void(*)())pVoid)()是做什么的?
函数指针调用函数: (*函数指针名)(参数表);
强制转换语法:(目标类型)变量
指针的类型: 去掉变量名剩下的就是指针的类型
而对函数print的函数指针是 :void (*p)() ,其实就是用(*p)替换函数名,就是该函数的函数指针,所以去掉变量名剩下的就是指针的类型,即:void (*)() ,然后pVoid 调用函数的方式: (*pVoid)(); 然后把类型拿过来强制转换即可得到:(*(void(*)())pVoid)(),明白了吧!就这么简单。
C显式描述程序细节。
C也可以实现OOP和GP的特性,函数指针和宏。
位字段结构成员类型:unsigned int、bool。
Pure declarations are typically of types used as function parameters or return types.
atoi():ascii.
结构允许单个的变量组合在一个共同的变量名称下。结构中的每个变量通过它的结构名称,后面跟随一个句号和变量名称进行访问。
结构作为数组的元素特别有用。使用这种方式,每个结构变成记录列表中的一条记录。
联合的定义创建一个存储覆盖区域,每个联合成员使用相同的存储空间位置。因此,一次只有一个联合的成员可能是起作用的。
Poiters allow you to refer to a large data structure in a compact way.
Pointers make it possible to reserve new memory during program execution.
Pointers can be used to record relationships among data items.
把main()函数写在了最后,这通常是一种不良的风格。因为在阅读一段程序时,我们通常首先要阅读的是main()函数,它描述了程序总体的思路。把main()函数放在源程序的最后,给人一种头重脚轻的感觉,就如同把一本书的书名、简介、目录放在书的末尾一样。
对“&&”运算通俗一点的描述就是,首先求左面运算对象的值,若该值为0,则表达式的值为0;否则求右面运算对象的值,若该值为0,则表达式的值为0;否则表达式的值为1。
“全局变量”这个词描述C 语言中的“外部变量”是不准确的,它掩盖了前面所提到的外部变量这个概念的精确含义,是一个“名实相怨”的概念,很容易使人尤其是初学者产生不必要的误解,造成不必要的困难和麻烦。所以应该予以废止,使用“外部变量”这个精确的概念。
一般来说,共用体的长度不仅包括其最大成员所需要的空间,还包括其尾部为了满足对齐要求而存在的必要填充(这部分不被使用)。因此只能说共用体所占内存长度不小于最大成员的长度,而不能断言共用体的长度等于最长成员的长度。在了解对齐规则的前提下,可以确定共用体的长度,因为共用体的起始地址和结束地址都要求有同样的对齐系数。
stdin 是一个指向FILE 类型对象的指针类型的表达式。
在很多实现中,它其实是一个“常量”表达式。例如在Visual C++中,stdin 是这样定义的:
#define stdin (&_iob[0])
Visual C++ 6.0并不支持C99的_Bool 数据类型,.c改成CPP就可以运行,因为使用的是c++编译器了。
正确的写代码顺序:先写最基本的部分:“printf("");”,在每次写这条函数调用语句时首先写出其最基本的必要部分,然后再向其中添加内容,最后完成“printf("你好!");”。
写语句也是一样,先把语句写完整:
switch () { }
然后再向其中的各个部分添加内容。
有数组int[8],当a 作为左值时其数据类型为“int [8]”,而对于这种数据类型,C 语言根本就没规定有赋值运算。
在优先级拿不准的情况下,最好不要主观臆测。保险的办法是在表达式中增加“()”。 “()”把( x & y )变成了一个完整的整体——基本表达式(primary expression),因此不会产生“x & (y != 0)”这样的错误解析。
#include <stdio.h> int main( void ) { union { char c[3] ; short s; } u ; printf( "%u\n" , sizeof u ); // 4 return 0; }
会用到一些有名字却不知道在哪儿的对象,比如printf,那么为了整个程序最后能执行,C 编译器就需要去查找哪儿有这个printf ,它按照这个名字去一些预定的地方,以及你提供给它的地方去找各种做好的“目标文件”,然后在这些目标文件中查找有没有一个东西叫做printf,一旦找到了,系统中的“链接器”就会用这个目标文件中导出的 printf 的接口在你自己的目标文件里面替换,最后把所有的目标文件合起来做成一个大文件,这个文件就是可执行程序了。
.c 文件约定俗成地是作为一个独立翻译单元存在的。它的功能就是真正的生成一系列对象,让链接器“有米可炊”。
.h 文件是用于向其他模块导出,告诉别人“我这里有这么一个对象,快来用吧”的。它存在的目的是告诉编译器“这个就是米”,而至于“米在哪儿”的问题,则是链接器的工作,由链接器自行寻找,我们只是偶尔提供一些启示,告诉它在哪儿寻找而已。
宏的实参如果是一个带有副效应的表达式,在实际编程中是一种绝对的禁忌。
事实上,二维数组可以看成是一个特殊的一维数组,而在内存中二维数组也是按行首尾 相接线性排列的。