首先声明,C语言没有类,类是C++里的。

C++世界,一切现实的事物都是对象,那类就是近似的对象的虚拟称呼,比如人“类”,类只是一种声明,不占用内存,比如“恐龙”这个类,没有实例,不占自然资源。

所有的类都有通用的属性,哺乳类会有身高,体重,都有通用的方法,如哺乳。

类的声明:类使用Class关键词声明,是自定义类型,如:

Class Person{

public:

char *name;

int height;

void say(){

printf_s(“HELLO,大家好,我身高%d”,height);

}

private:

int weight;//体重是秘密

}

其中private是私有属性,外部不可以访问,属性可以私有,方法也可以私有,声明在private下面的都为私有属性方法,声明在public都是公开的属性方法,外部可以访问。

类是创建对象的模板,创建对象的过程是类的实例化,每个对象都是一个实例,拥有类的属性和方法,对象是实在的数据,需要占用内存,如小明,是人类的一个实例,需要占用自然资源,如:

Person man;

man.name = “小明”;

man.height = 180;

man.say();

 

面向对象编程的设计思想

程序中,不单有数据,数据间存在一定的关系,而且,还存在着大量的在这种关系上的“运算”。
如果把数据及其上的运算,“封装”起来,就形成基本的“对象”的概念。
因此,对象是由数据成员和运算成员(函数成员)封装而成的。
数据成员又称为对象的“属性”;
函数成员又称为对象的“方法”。
将具有相同属性和方法的多个对象中,用“抽象”的方法,提取出其中的属性和方法,就构成了“类”,其实就是“类型”的意思。
将类实例化(计算机化、存储实现)就成为“对象”(实例)。
因此,对象的基本概念中的第一个“对象”其实是面向世界的;
“类”是面向逻辑的;

我个人理解面向对象就是面向职能模块,每一个类都专注于做一件事情,允许每个实例有个体化的特征,比如所有前端工程师的核心方法是页面重构,都有工具属性,性别属性等,但是工具,性别又会有所不同。

其它有待扩展

c-fseek

C语言中fseek函数掌握了文件流的进度和位置,可以实现文件中读写数据的跳跃。

int ret = fseek(文件指针,偏移值,相对位置)返回值为0||-1,当0时seek成功,-1时失败

每个被fopen的文件对象,其数据结构里都有一个位置指示器指向当前文件的读写位置,fseek就是让这个指示器从当前位置发生偏移从而实现数据的跳跃,正整数往后,负整数往前,如下:

const char* filename = “e/aaa.txt”;

File* fp = fopen(filename,”rb”);//此时指示器的值为0

unsigned char buf[128];

int ret = fseek(fp,32,SEEK_SET);//此时返回值为0,seek成功

int n = fread(buf,1,4,fp);//此时buf的值为32开始的4个字节

有了fseek以后,文件的读取可以动态起来了,跳跃式的读取可以略过中间不需要的数据直接读取到目标,但是方式还是略微死板,而且seek不能多用,因为seek时需要移动物理“磁头”,在硬盘、U盘等外部存储器上频繁的读写,速度比较慢,还会影响设备的寿命。期待其它的读写器。

C-文件-1

C语言可以编写各种后缀名的文件,尴尬,后缀名为mp3,xyz,都毫无压力,所有的文件类型都可以用来存储信息,主要看如何读取了。当然,这么高端的方法现阶段我还没有学到,现在只会读写数据。

C语言读写文件很简单,告诉程序文件的位置,打开它的方式,然后读写,然后关闭。主要是四个函数:fopen,fwrite,fread,fclose.

FILE* fp = fopen(文件路径指针,打开方法);返回值为文件指针,我使用的是fopen_s,安全性提高,返回值是一样的。例如下:

FILE* file;
errno_t fp;
fp = fopen_s(&file,”E:\\a.txt”,”r”);
if (fp != 0) {
printf_s(“文件打不开啊啊啊\n”);
return -1;
}

此处留疑问一个:fopen打开文件后,此时文件是在内存中吗?似乎不太可信,留疑。

Int n = fwrite(目标字符,写入字节大小(默认为1),写入次数(一般设置为sizeof(目标字符)),目标文件指针);返回值是写入的字节数,例如下:

for (int i = 0; i < 256;i++) {
char text[] = “helld,”;
n = fwrite(text, 3, strlen(text)/3,  file);
}

一般写入字节用1避免目标不能整除,多写了溢出字符,产生乱码。案例为错误示意。

Int m = fread(目标存储区域,读取字节大写,读取次数,目标文件指针);返回值是读取次数

char buf[128];
n = fread(buf, 12, 128, file);

Fclose(目标文件指针),关闭文件;无返回值

适合和fwrite一起使用的函数有sprintf(目标存储区域,输出格式(“%d”或者“%.2lf”),目标字符串):可以把数组格式化为有分隔符的字符串。返回值为格式化的字符大小,我使用的是sprintf_s,没区别,例如下

for (int i = 0; i < arr_len;i++) {
char text[128];
int s = sprintf_s(text,”%d,”,arr[i]);
n = fwrite(text, 1, strlen(text),  file);
}

读取未知大小文件,有函数feof(file)判断文件是否读取结束,返回值为布尔类型,为true时文件读取结束,下例以每次读4*1字节的方式循环读取文件数据,读完为止。

char buf[4];
int i = 1;
while (!feof(file))
{
int n = fread(buf, 1, 4, file);
if (n>0) {
printf_s(“read %d bytes,time %d,file is end:%d \n”, n,i, feof(file));
}
i++;
}

C-深入字符串

之前有一篇字符串留疑了,为什么字符串一定要用0结束,这次深入学习了字符串之后我又有了新的了解。

字符串的声明和数组的声明一样都是用char来声明的,这不是巧合,因为字符串其实就是数组,是文字的集合,数组的结束也是以0结束的。

字符串删除有两种情况,一是删除指定位置的字符,一是删除多个不相邻的特定字符。

删除指定位置的字符使用的是挪动方法,循环后把目标后面的所有字符移动n位即可。

删除多个不相邻的特定字符使用的是复制法,遇到特定字符时不复制并跳过。

字符串的增加和删除的操作一样,指定位置使用循环挪动法,增加使用复制法,避免多次挪动后面的字符,增加字符需要另外注意越界的问题,不能超过声明的字符串最大长度。

学习字符串的分割需要了解字符串的头和尾,字符串的头是首字母的地址,尾是0,所以当我们把字符串str的头往后位移n位时,可以获得从后往前数长度为len-n的新字符串str_new,而如果我们把字符串str的第n位赋值为0时,我们会得到从前往后数长度为n-1的新字符串str_new。

分割有分隔符的字符串就可以使用以上思路,循环字符串,判断字符是否为分隔符,如果是分隔符,就令分隔符等于0,并记录分隔符的地址作为下一个字符串的首地址。

字符串的深度操作其思路和操作数组的操作一样又不太一样,主要还是着重在对字符串的理解上,需要理解字符串头尾的概念,首地址的概念。

附:C-字符串  C-指针

C-动态分配内存

终于学到了C的动态操作了,之前的学习内容大部分都是需要指定长度的操作,实际应用中是绝对不能行的,毕竟总不能随时覆盖吧。

动态分配内存

1.mollloc

2.free

使用mollloc申请内存,可以把变量作为参数传递给size大小,也可以接收用户传递的size尺寸,这是它的优势。Mollloc申请内存,返回的是void* 类型指针,参数是int,单位是byte,申请成功后,返回该内存的首地址,当申请的内存大于目前剩余内存块(整块),则内存申请会失败,返回null。

申请内存使用过后,需要释放,否则会产生内存使用完毕的情况出现,释放内存的函数为free。申请一次内存,就需要释放一次内存,否则程序会发生错误。

释放内存后,指针需要重置为null,否则该指针则变为野指针,会出现错误。

使用如下:

char* p = (char *)malloc(8);//这里的8还可以使用变量,意味着用户可以输入自己想要申请的数字。

if(p){

for (char i = 0; i < 8;i++) {

p[i] = i + 1;

}

}

free(p);

p = null;//清理指针,避免出现if(p)的情况。

链表

链表是使用指针把若干对象串联在一起形成链状的数据结构,其中最重要的是串联使用的指针,当前对象的指针指向了下一个对象地址,如下:

代码设置链表如下:

struct Student

{

int id;

char name[16];

Student* next;

};

Student ss[4] =

{

{20170414,”a”,0},

{20170415,”b”,0},

{20170416,”c”,0},

{20170417,”d”,0}

};

ss[0].next = &ss[1];

ss[1].next = &ss[2];

ss[2].next = &ss[3];

ss[3].next = 0;

Student* p = &ss[0];

 

打开内存调试窗口,查看链表头,能很明显的看到“链”,如下:

链表头是指链表的第一个对象,我们通常使用链表头来代表整个链表。

链表尾是值链表的最后一个对象,它的next必需为NULL;

1.有头链表的构造

由于有链表头就可以定义为一个链表,那么我们只需要一个链表头,不需要里面具体的链表对象,也可以表达链表,实现了链表的动态化,在添加删除链表时都会更加的灵活。

2.插入和删除节点

节点可以插入和删除,是改变next指针的指向,如下:

void add(Student* target,Student* obj)

{

obj->next = target->next;

target->next = obj;

};

void del(Student* target, Student* obj)

{

target->next = obj->next;

obj = 0;

};

链表实际应用中可以当做指针型的没有长度单位的数组,突破了C语言数组长度的限定,在增删操作中也大量的节省了内存,数组增删时,节点后面所有的元素都要被操作,而链表不需要,它只需要改变节点的指向即可,相比较而言,效率会高更多。

C-结构体

C语言的结构体,名字听着让人惴惴然不安,但是学习了之后发现,这又有另个JS概念对应,那就是对象。好后悔没有早点开始这一章的学习!

结构体是用户自定义的类型,和Int char差不多,但是结构体有所不同,它可以包含多种类型,它是一个多种类型的结合,简称结构体。定义如下:

Stuct Contact//定义名称为Contact的结构体

{

//里面包含的成员变量

int id;

char name[16];

char phone[16];

}

使用如下:

Contact a = {12138,”wenling”,”136****0646″};

这样一个新的结构体实例就完成了。

访问实例使用.符号访问即可:

prinft_s(“id is : %d”,a.id);

结构体赋值和其它系统类型赋值不同,结构体赋值后的结果是===,内存一样,每个字节都是相同的。

结构体也可以用指针访问,一般使用->符号,而不是.符号,->访问如下:

Contact* p = &a;

printf_s(“id is : %d”,p->id);

结构体可以作为函数的参数,也可以作为函数的返回值,作为函数参数时,还是遵照我们地址访问的原则,使用指针访问,减小传递的数据大小和cpu内存。

结构体也可以作为结构体的成员,访问方式也是.符号访问。

结构体大小有时候会比成员之和要大,因为CPU或者说编译器会有字节对齐的需要,比如前两个成员是char类型,第三个成员是int类型,编译器会自动补齐第三第四字节,直到第五字节才开始int类型,所以会比成员之和要大。

期待后面的学习,期待概念相同。

C-指针2

指针是十分强大的操作,它可以读写内存,强大的操作经常会有一些限制方法,减少出错的几率及代价。

const定义指针,只能读不能写。定义方法:const int* p = &a;

杜绝野指针,让指针要么有处可指,要么为0,就是不能不定义,暂时不用的指针这样定义成空指针:int* p = 0;空指针也会报错,但是它是可以判断的,野指针是完全没有办法判断的,错了都不知道呢。JS里经常var m , n.这样初始化变量,不需要类型以及初始化,完全不适应C。

严防数组越界,指针加减不要超过数组的长度,超过了跟野指针也差不多了,谁知道你指哪去了。

最后一个需要注意的是指针所指向的变量生命周期,局部函数的变量周期很短暂,当需要由指针指向这个变量时,需要关注该变量的生命周期,变量已经结束没有了,指针却还指着,也是错误的。

总之,越是强大的方法越需要注意安全,谨慎使用。

C-指针1

C语言是一门很强大的语言,他可以直接操作内存,指针就是他可以操作内存的一种方法。

指针是指对象对应的内存地址,读写指针就等于读写内存。

&操作符,又称为取址操作符,将&放在对象名前面,可以获取该对象的内存地址。

数组事实上就是一个指针,他具有长度属性,事实上,随便的一个数的指针也是数组,长度为1而已。指向数组的指针做加减法时,+i等于指针后移i个元素,-i等于前移i个元素,若是超出了数组的长度,称为出界,会导致未知错误,因为被出界的指针很有可能指向的是一个有什么用的变量,不知不觉给人家改了,当然有可能出问题了。有时候,运行良好的程序不一定就没有毛病,只是没爆发而已,养成良好的代码习惯,可以少犯奇奇怪怪的错误。

指针作为普通函数参数使用时,不需要长度,而作为数组参数使用时,需要定义长度。使用指针作为参数与传递给函数,效率会更高,而且不用受限于函数只能return一个结果,可以使用指针传递好几个结果出来。

写一个求数组中最大值最小值的函数如下:

void _max_min(int *p, int len, int *pmax, int *pmin) {
int _max = p[0], _min = p[0];
for(int i = 1; i<len; i++) {
if(p[i]>_max) {
_max = p[i];
}
if(p[i]<_min) {
_min = p[i];
}
}
*pmax = _max;
*pmin = _min;
}

int main()
{
int arr[4] = { 1,2,3,4 };
int _arr_max = 0,_arr_min = 0;
_max_min(arr, 4, &_arr_max, &_arr_min); //此处结果借由指针参数传递出来,得到两个结果
printf_s(“max is %d \n min is %d \n”,_arr_max,_arr_min);
return 0;
}

指针可以传递多个参数出来,是因为指针传递的是地址,不是值,函数运行时如果是传值,他会复制一份在进行运算,而传递指针就不需要复制,直接访问了指针所指向的地址,所以在值比较占内存的情况下,指针作为参数相对相率更高,而且不存在变量无法变更的情况。