【C/C++】 学习笔记

C

知识点回顾

c语言是一种中级语言,即可以使用英文编程,又能直接对硬件进行交互。

数据类型

数据类型(datatype)代表变量存入内存的一种存储管理。这里我们举int型变量的例子——声明int num后,编译器分配2Bytes(0~255)大小的内存空间,然后编译器为内存空间取名num

左值和右值:左值必须为有数据类型的变量。

C中的数据类型分为2类:

  • 基本数据类型(primary):char(1B)、int(2B)、float(4B)、double(8B)、void

  • 中级数据类型(secondary):array、pointer、structure、union、enumeration

    其中void较为特殊:
    void类型的指针可以记录一个地址,在之后的使用中可以转换任意其它类型的指针。
    将void作为函数参数名,代表函数不接受任何参数。

    void *p;
    p = &x;
    
    int fn(void){ statements; }
    

C中的修饰符分为2类:

  • 长度型修饰符:short、long(long为short的2倍)
  • 符号型修饰夫:sighed、unsigned

<limits.h>头文件中,我们可以查询当前系统环境下各个变量的最大最小值

#include <stdio.h>
#include <limits.h>
int main()
{
    printf("%d, ", INT_MAX);
    printf("%d, ", INT_MIN);
    printf("%ld, ", LONG_MAX);
    printf("%ld, ", LONG_MIN);
    printf("%d, ", SHRT_MAX);
    printf("%d", SHRT_MIN);
    return 0;
}

结果可见,当前64位系统环境下,intlong int等效

2147483647, -2147483648, 2147483647, -2147483648, 32767, -32768

C中的限定符共有2个

  • const:变量值不能修改;数据类型强制转换
  • volatile: 代表该值是可被外部进程改变的(编译器不会对变量进行优化,在执行期程序将监视变量值的变化)
#define PI 3.1415926

const float PI = 3.1415926

从效果来看,两者都定义了一个不可修改的PI,但是#define表述不占用内存,且可以用于数组的定义

C中的操作符太多了:一元操作符三元操作符逻辑操作符关系操作符位操作符混合操作符

在这里插入图片描述
重点:

  • ++的位置:在变量值传递或打印时,前置递增和后置递增的结果不一致。
  • ? ::条件语句
  • 逻辑操作符:&& || !(python和matlab中为& | ~
  • 位操作符:& | ~ (善用位操作符,可直接提升程序运行速度)
  • 地址操作符:& *

位操作符示例

#include<stdio.h>
int main(){
    int x, y;
    x= 10;
    y = 12;

    printf("\n~x= %d", (~x));
    printf("\nx&y= %d", (x&y));
    printf("\nx|y= %d", (x|y));
    printf("\nx^y= %d", (x^y));
    printf("\nx<<2= %d", (x<<2));
    printf("\nx>>2= %d", (x>>2));

    return 0;

}

结果

~x= -11
x&y= 8
x|y= 14
x^y= 6
x<<2= 40
x>>2= 2
#include<stdio.h>
int main(){
    int x = 1;

    printf("DataType = %f", x);
    printf("DataType = %f", float(x));
    printf("DataType = %f", (float)x);
    printf("DataType = %f", (const float)x);
    printf("DataType = %f", x);

    x = (float)x;
    printf("DataType = %f", x);
    return 0;

}

结果,可见既定int型的x在转换类型后,将x = (float)x之后x的数据类型依然是int

DataType = 0.000000
DataType = 1.000000
DataType = 1.000000
DataType = 1.000000
DataType = 0.000000

DataType = 0.000000
补码、反码

0000 0000 :+0
0111 1111 :+127
1000 0000 + 1:-128
1111 1111 + 1:-0
0000 0001 :+1
1111 1110 + 1:-1

数组
  1. 为什么数组下标从0开始
    答:数组在底层实现中被编译为arr + i的形式,arr变量名事实代表一个基址,通过* arr我们可以取出数组的第一个地址。

  2. 转义符
    %[^\n]:可同时输入多个单词

    #include <stdio.h>
    #include <stdlib.h> //使用system函数时必须要添加
    #include <windows.h>
    int main()
    {
    	char name[15];
    	scanf("%s", name);
    	printf("\nYour name is %s\n", name);
        system("pause");
    
        fflush(stdin);
        
        scanf("%[^\n]", name);
    	printf("\nYour name is %s\n", name);
        system("pause");
        return 0;
    }
    

    结果

     shao yu ning
     
     Your name is shao
     请按任意键继续. . .
     shao yu ning
     
     Your name is shao yu ning
     请按任意键继续. . .
    
  3. 解决输入问题
    在键盘进行多个数据输入时,容易出现Enter占用一个字符位,因此需要清除输入设备的缓存区

     fflush(stdin);
    
函数

参数的声明和定义
(int, int, int):只有函数声明可以使用;
(int x, int y, int z):函数定义必须这么写;也可以用作函数声明;

字符与字符串

显示ASCII码及对应字符

#include <stdio.h>
#include <windows.h>
int main()
{
    int c;

    for (c=0; c<=255; c++)
        printf("\nASCII= %d\t Char= %c", c, c);
    system("pause");
    return 0;
}

字符检测函数

#include <ctype.h>
isalpha(c)    /*判断是否为英文字符*/
iscntrl(c)     /*判断是否为控制字符*/
isdigit(c)     /*判断是否为阿拉伯数字0到9*/
isgraph(c)   /*判断是否为可打印字符,若所对应的ASCII码可打印,且非空格字符则返回TRUE*/
islower(c)    /*判断是否为小写英文字母*/
ispunct(c)   /*判断是否为标点符号或特殊符号。返回TRUE也就是代表为非空格、非数字和非英文字母*/
isspace(c)   /*判断是否为空格字符,也就是判断是否为空格(' ')、水平定位字符('\t')、归位键('\r')、换行('\n')、垂直定位字符('\v')或翻页('\f')的情况*/
isupper(c)   /*判断是否为大写英文字母*/
isalnum(c)   /*判断是否为字母或数字*/
isxdigit(c)   /*判断是否为16进制数字*/

【C语言学习笔记】输出函数puts()

字符串处理函数

C语言字符串操作函数总结

作用域

动态和静态

静态变量:多次函数调用中唯一且存在
静态函数:可被当前程序文件中的代码重复使用

存储类型

对变量存储位置、初始化操作、作用域、生命周期进行划分,c语言中变量的存储类型分为4种:

  • auto:内存、垃圾数据(针对字符和字符串,但使用最新gcc编译后也设置成了默认值,所以垃圾数据的特性也算是淹没于历史之中)、局部、区域块执行完立即将其移出内存(块外部不可用);
  • register:CPU寄存器、其他三项同auto;(快速访问,用作循环计数器)
  • static:内存、默认值、全局、块外部可访问,其他源文件不可用,程序结束;
  • extern:内存、默认值、全局、块外部可访问,程序结束;
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
int main()
{
    auto char na;
    register char nr;
    auto char naa[10];
    // register char autora[10];
    static char ns;
    static char nsa[10];
    printf("na is %c\n", na);
    printf("nr is %c\n", nr);
    printf("naa are %s\n", naa);
    // printf("%s\n", nra);
    printf("nsa are %s\n", nsa);
    system("pause");
    return 0;
}
控制语句

return语句:终止当前函数。
exit语句:终止程序进程。exit(0)为正常终止程序,exit(1)为异常退出程序。
switch-case特性:case具有一定的包容性,从自身case自动执行到下一条case,直至遇到break或者结束。

指针

未使用指针时,编译器对变量的操作:分配内存地址->命名内存空间->保存数据;
使用指针时,编译器对变量的操作:分配内存地址->命名内存空间->保存数据地址->使用取值操作符或间接引用符号*访问数据;

指针可以进行的操作

  • 自加减、两个指针间相减、加减常数、比较大小(以定义的数据类型决定单位大小:int为4B,char为1B)
  • 修改指向变量
指针作用
  1. 按值传参和按引用传参:相比于普通变量局限于块内,指针的改变会修改指向数据。
  2. 从生命周期来看,指针在运行时可以修改,而数组和变量在编译期已确定,不能修改。
  3. 可以使用堆内存,防止爆栈。
  4. 编写底层,如设备驱动、杀毒软件。
  5. 创建各种数据结构。

JAVA、python不提供指针,不过,扩展了数据类型以解决C语言数据类型的简略。

二维数组指针的保存

*(*(arr+0)+0)	//arr[0][0]
*(*(arr+0)+1)	//arr[0][1]
*(*(arr+2)+2)	//arr[2][2]
内存分配

低地址内存段被操作系统占用;只有高地址内存段才可被程序代码访问;

  • code segement:text segment,由于加载代码

  • data segment:分为initialized data和uninitialized data,后者中的全局变量和静态变量保存在BSS段(block started by symbol),亦称静态分配内存static memory。loader加载.exe文件后会使用BSS段,将内存块全部清0

  • heap segment:堆段,亦称动态内存dynamic memory。只能通过指针访问高位扩展。常用相关函数为

    malloc()
    calloc()
    realloc()
    free()
    
  • stack segment:栈段,保存局部变量、临时变量LIFO
    栈帧stack frame和pull stack/pop stack:当函数调用会创建一个栈帧,当函数返回时会从栈空间删除栈帧。

Q: 全局变量和静态变量保存在哪个段?
A: data segment 的BSS段。

在这里插入图片描述
静态分配与动态分配

动态分配注意点:

  1. 配对使用,有一个malloc,就应该有一个free。(C++中对应为new和delete)
  2. 尽量在同一层上使用,不要像上面那种,malloc在函数中,而free在函数外。最好在同一调用层上使用这两个函数。
  3. malloc分配的内存一定要初始化。free后的指针一定要设置为NULL。

静态分配:
储存变量、指针的地址值,适合快速存取的场景。

动态分配:
构造栈、队列、链表等数据结构,但访问速度稍慢于静态分配

内存段是如何划分的
c语言动态与静态分配

指针类型
  • dangling指针:指向无效对象

  • null指针:不指向任何对象(指向栈区的基地址)

  • 野指针:垃圾数据,未被初始化

  • near指针(2B)、far指针(4B)、huge指针(4B):区别于访问位置,near只能访问数据段(64KB),far和huge可以访问所有内存段。

  • void指针:

    #include <stdio.h>
    #include <stdlib.h>	
    int main(void){
        char ch = 'A';
        int i = 100;
    
        void *ptr;
        ptr = &ch;
        printf("%c", *(char *)ptr);
    
        ptr = &i;
        printf("%d", *(int *)ptr);
    
        system("pause");
        return 0;
    }
    

    在这里插入图片描述
    指针常量和常量指针

指针常量侧重指针的值为常量,常量指针侧重指针指向的值是常量。

//指针常量,只能指向固定的地址
int *const ptr1;
//常量指针
int const *ptr2;

指向指针的指针

int *p1;
int **p2;

p2 = &p1;
内存分配实践

基本函数的使用

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    //定义指针
    int *p;
    //首先,定义一个int型的动态内存空间
    //然后,将此空间的地址值赋给指针变量P
    p = (int *)malloc(sizeof(int));
    //在动态内存使用完成后一定要记得free
    free(p);

    char *ch; 
    ch = (char *)malloc(20 * sizeof(char));
    

    //当需要更改内存块大小时
    ch = (char *)realloc(ch, 25);

    free(ch);

    //当需要分配若干指定字节数的内存块
    int *ptr;
    ptr = (int *)calloc(10, 20 * sizeof(int));

    free(ptr);

    system("pause");
    return 0;
}

解决dangling指针——null指针

#include <stdio.h>
#include <stdlib.h>
#define MAX_BUF_SIZE 100
int main(void)
{
    /*内存释放标志*/
    int flag = 0;
    char * p = (char *)malloc(MAX_BUF_SIZE);
    if (p == NULL)
    {
        /*...*/
    }
    if (flag == 0)
    {
        free(p);
        p = NULL;
    }
    if (p != NULL)
    {
        free(p);
        p = NULL;
    }

    system("pause");
    return 0;
}

free(p)释放的是指针变量 p 所指向的内存,而不是指针变量 p 本身。指针变量 p 并没有被释放,仍然指向原来的存储空间。在程序进程中,由于free指针后导致这个指针指向不合法的地址,我们称这个指针为dangling指针,又称野指针 。因此在抛弃指针后,一定要打free + NULL的组合拳。

消除dangling指针后的指针使用

#include <stdio.h>
#include <stdlib.h>
#define MAX_BUF_SIZE 100
int main(void)
{
    char * p = NULL;
    /*内存申请*/
    p = (char *)malloc(MAX_BUF_SIZE);
    if (p == NULL)
    {
        /*...*/
    }
    /*内存初始化*/
    memset(p, '\0', MAX_BUF_SIZE);
    strcpy(p, "hello");
    /*释放内存*/
    if (p != NULL)
    {
        free(p);
        /*在free之后给指针存储一个新值*/
        p = NULL;
    }
    if (p != NULL)
    {
        strcpy(p, "world");
    }
    return 0;
}

memset 函数使用

观察下面两个程序,分析其结果不同的原因。

#include <iostream>
#include <cstring>
using namespace std;
int main()
{
    char a[5];
    memset(a,'1',5;
    for(int i=0;i<5;i++)
        cout<<a[i]<<"";
    system("pause");
    return 0;
}
#include <iostream>
#include <cstring>
#include <windows.h>
using namespace std;
int main()
{
    int a[5];
    memset(a,1,20;
    for(int i=0;i<5;i++)
        cout<<a[i]<<"";
    system("pause");
    return 0;
}

char 和int 在单位大小上不同,memset函数以字节为单位进行赋值,因此不能用它将int数组初始化为0和-1之外的其他值

  • 在对指针赋值前,一定要确保没有内存位置会变为孤立的。
  • 每当释放结构化的元素(而该元素又包含指向动态分配的内存位置的指针)时,都应先遍历子内存位置并从那里开始释放,然后再遍历回父节点。先释放子内存再释放父内存
  • 始终正确处理返回动态分配的内存引用的函数返回值。return结果一定要处理

结构体清0

memset(&x,0,sizeof(struct_name));

C语言malloc和free使用详解
memset

字符指针、字符数组常量、字符数组变量

字符串称为字符数组常量,而由于字符串占用内存空间,因此函数调用字符串时可以使用字符串的字符指针,字符串可以初始化字符数组变量
前面说到,字符串常量在内存中将占用空间,并且能够取得它的地址并输出。下面用实验进行简单的证明:

#include <stdio.h>
int main()
{
    char *p;
    p = "Hello World!";
    printf("%p\n%s\n", "Hello World!", "Hello World!");
    printf("%p\n%s\n", p, p);
    return 0;
}
/*
输出如下

012A5858
Hello World!
012A5858
Hello World!
*/

可见字符串本身的取值就是一个指针,而字符串的数据则保存在指针所指向的内存空间。
另外可以发现,对于完全相同的字符串常量,在内存中只会记录一次,而不会重复占用空间

But,计算机的神奇之处就在于当你以为你懂了,它又会跳出某些新的问题。

#include<stdio.h>
int main(){
    char ch;
    ch = getchar();
    putchar(ch);
    
    char str[20];
    
    printf("\n");
    gets(str);
    puts(str);

    return 0;
}

输入

asdddddddddddddddddssssssssssssssssaaaaaaaaaaaaaa

输出

a
sdddddddddddddddddssssssssssssssssaaaaaaaaaaaaaa

str[20]已定义为长度为20的字符数组变量,但是在puts时全部输出,这里的推测是,str作为一个定义好[20]长度的数组空间,存储了20个字符,但是gets函数在存储时,并没有直接停止,而是在内存中的数组空间后面的空间继续存储,保持了物理存储的连续性,因此在puts时我们通过str指针可以完整读出所有的超出部分的字符。(作者的猜测,请大家斧正!

指针应用

指针函数
声明了指向数据类型的指针的数组

char *name[50];
int *arr[50];

函数的指针传参

char * fn(char *str);
int * fn(int *num);

指针函数和回调函数

void (*fp)(int x, int y);
void sum(int x, int y);

fp = sum;

通过传递函数地址的方式调用一个函数叫做回调函数机制。需要使用函数指针来向函数传递函数地址。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

float ap_tax(float salesamt){
    return salesamt * 0.25;
}

float ka_tax(float salesamt){
    return salesamt * 0.20;
}

float calc_tax(float salesamt, float(*fp)(float)){
    return (*fp)(salesamt);
}

int main(void){

    float salesamt = 10000.00;
    printf("tax = %.2f\n", calc_tax(salesamt, ap_tax));

    printf("tax = %.2f\n", calc_tax(salesamt, ka_tax));

    system("pause");
    return 0;
}

比起它令人迷惑的运作方式,我们更应关注函数指针的应用场景。函数指针一般用于常驻内存的程序,比如病毒、杀毒程序、状态机。参考文章的理解是在C语言中实现C++中的函数重载、函数模板功能。

函数指针的巧妙应用

动态分配内存的实践

#include <stdio.h>
#include <stdlib.h>

int *get(int n){
    int i;
    int *x;
    x = (int *)malloc(n * sizeof(int));
    printf("\nEnter elements: \n");
    for (i=0; i<n; i++)
        scanf("%d", x + i);

    return x;
}

void display(int *x, int n){
    int i;
    printf("\n1D array elements: \n");
    for (i = 0; i < n; i++)
        printf("%d\t", *(x + i));
}

int main(void)
{
    int *a;
    int n;
    
    printf("\nHow mant elements? ");
    scanf("%d", &n);

    a = get(n);
    display(a, n);
    free(a);
    system("pause");
    return 0;
}

结构体和联合体

结构体创建、声明成员变量、成员变量初始化。
结构体指针:->引用结构体实例的成员。
结构体数组:存储多个相同结构体的实例。
结构体指针数组:用指针引用的多个相同结构体的实例。(2021-02-03记:今天用C写数据结构,尤其是链表,用结构体数组指针用到吐,所以指针还是要好好看看的)
fprintf函数:stdout显示屏、stdprn打印机、fptr文件。
拷贝结构体:直接用=,当然结构体中有数组另当别论。
嵌套结构体:套中套,不解释。

文件操作

文件从存储形式上分为文本文件二进制文件。文本文件分为无格式文本文件、有格式文本文件(记录字符和数字);二进制文件使用二进制串记录文件。

基本函数
#include<stdio.h>
File *fp

fp = fopen()
fclose(fp)

//从文件中读取or存储数据
putc(ch, fp)
char = getc(fp)

//从终端显示or存储字符
putchar(ch)
getchar(ch)

//从文件读取or存储字符串
str = fgets(fp)
fputs(str, fp)

//从终端显示or存储字符串
puts(str)	//存储
gets(str)	//读取

//存储格式化数据(可多变量多类型)
fprintf(fp, "%d", num)	//向fp输入数据流
fscanf(fp, "%d", &num)  //从fp读取数据流

//在终端显示格式化数据
printf()	//向stdout输入数据流
scanf()		//从stdin读取数据流

向文本文件中存储字符

#include <stdio.h>
#include <stdlib.h>
// #include <string.h>


int main(void){

	//创建文件指针
	FILE *fp;
    char ch;

    //打开文件
    fp = fopen("myfile.txt", "w");
    
    //判别文件是否打开
    if (fp == NULL){
        printf("\nFile not opened");
        return;
    }
    
	//当检测输入不为ctrl+z时,一直使用putc输入
	while((ch = getchar()) != EOF)
		putc(ch, fp);
	
	// //从文本文件中读取数据
	// while((ch = getc(fp)) != EOF)
	// 	putchar(ch);

    system("pause");

    fclose(fp);

    return 0;
}

fopen()函数的第二个参数为打开模式
文本文件打开方式有六种:w / w+ / r / r+ / a / a+ ,分别表示:清空写入、清空写入+创建新文件、读取、读取+检测文件是否存在、追加、追加+创建新文件。函返回一个文件指针(指针指向文件的起始区域地址)
二进制文件打开方式有六种:wb / w+b / rb / r+b / ab / a+b ,分别表示:清空写入、清空写入+创建新文件、读取、读取+检测文件是否存在、追加、追加+创建新文件。函返回一个文件指针(指针指向文件的起始区域地址)

向文本文件中存储字符串

do{
	gets(str);
	strcat(str, "\n");
	fputs(str, fp);
}
while(*str != '\n');
#include<stdio.h>
#include<string.h>

int main(void){
	char str[80];
	char fname[15];
	FILE *fp;
	
	gets(fname);
	
	fp = fopen(fname, "w");
	//持续输入
	do{
	gets(str);
	strcat(str, "\n");
	fputs(str, fp);
    }
	while(*str != '\n');
	
	//持续读入
    while((fgets(str, 80, fp)) != NULL)
        puts(str);

    return 0;
}

文本文件中操作格式化数据

scanf()
fprintf(fp, "%d\n", num)

fscanf(fp, "%d", &num)
printf("%d", num)

相信聪明的读者已经发现了规律:

  • 一个终端操作,一个文件操作
  • 先读后写,持续读写需要注意特殊语句

注意fprintffscanf两个函数的第一个参数,当我们输入fp文件指针时,它就对文件进行操作;当我们输入stdprn时,它就会连接到打印机进行打印;当我们输入stdout时,它就等效于printf

高级函数
//重定向至文件开头
rewind(fp);

fseek(fp);
//从文件头开始移动
fseek(fp, 10, SEEK_SET);
//从当前位置开始移动
fseek(fp, 50, SEEK_CUR);
//从文件末尾开始移动,跳到文件末尾
fseek(fp, -10, SEEK_END);

//返回文件指针所在处
long int position = ftell(fp);
fp = fopen();

{fwrite();}
//重定位文件指针到文件开始之处
rewind(fp);

{fread();}

fclose(fp);

二进制文件读写

#define SPACE(k) for(j=1; j<=k; j++) fprintf(stdout, " ");
#define LINE for(j=1; j<=80; j++) fprintf(stdout, "-");

struct customer cust;

fwrite(&cust, sizeof(cust), 1, fp);

while(!feof(fp))
	fread(&cust, sizeof(cust), 1, fp);

文件转储

#include<stdio.h>
struct customer{
    int accno;
    char name[20];
    float balance;
};

int main(void){
    int j, k, i=1;
    struct customer cust;
    FILE *fp, *fp1;
    
    fp = fopen("bank.dat", "rb");
    
    if (fp == NULL){
        printf("\nFile not opened");
        return;
    }
    
    fp1 = fopen("report.txt", "w");
    
    //从bank.dat读取记录
    fread(&cust, sizeof(cust), 1, fp);
    
    while(!feof(fp))
    //将记录存储进report.txt中
    //fwrite(&cust, sizeof(cust), 1, fp1);
    {
    	fprintf(fp1, "%d", i);
        fprintf(fp1, "%d", cust.accno);
        fprintf(fp1, "%s", cust.name);
        fprintf(fp1, "%f", cust.balance);
        fprintf(fp1, "\n");
    	
    	i++;
    	fread(&cust, sizeof(cust), 1, fp);
    }    
    
    fclose(fp);
    fclose(fp1);
    return 0;
}
文件操作模板

返回文件大小

//返回文件大小
fseek(fp, 0, SEEK_END);
int filesize = ftell(fp);

更新记录

//更新记录
int tempno;
scanf("%d", &tempno);
recno = tempno - baseno;

recsize = sizeof(cust);
position = (recno - 1)*recsize;
//重定向文件指针进行读取
fseek(fp, position, SEEK_SET);
fread(&cust, sizeof(cust), 1, fp);

{//修改操作}
//重定向文件指针进行修改
fseek(fp, -recsize, SEEK_CUR);
fwrite(&cust, sizeof(cust), 1, fp);
    
fclose(fp);

删除记录
由于C中并没有删除数据的函数,因此我们删除指定记录的思路为创建一个新的文件,将指定记录以外的数据录入其中,再删除原来的文件,重命名即可。

FILE *fp, *fp1;
int tempno;

fp = fopen("bank.dat", "rb");
fp1 = fopen("temp.dat", "wb");

scanf("%d", &tempno);
fread(&cust, sizeof(cust), 1, fp);

while(!feof(fp))
//将记录存储进temp.dat中
{
	if(tempno != cust.accno)
		fwrite(&cust, sizeof(cust), 1, fp1);
		fread(&cust, sizeof(cust), 1, fp);
}    

fclose(fp);
fclose(fp1);

//对文件进行操作
remove("bank.dat");
rename("temp.dat", "bank.dat");

获取目录信息

#include<dir.h>
int ret_code;
struct ffblk file;

//第三个参数为设置参数
ret_code = findfirst("*.*", &file, 0);
while(ret_code == 0)
{
	ret_code = findnext(&file);
	printf("\n%s", file.ff_name);
	ret_code = findnext(&file);
	
}

findclose(ret_code);

文件查找(c语言 findfirst函数,findnext函数)

命令行参数

基本参数
void main()
void main(void)
void main(int agrc, char *argv[])
void main(int agrc, char *argv[] ,char *envp[])

执行程序首先执行main函数,同样,作为函数就可以有参数的输入和输出,main函数的常见输出共四种。无参、void无参、二参、三参。
void无参通知编译器不需接收任何参数;无参的适用范围更广,可输入可不输入;
接下来我们来研究三参数的main函数。

  • argc:参数的个数
  • argv:字符指针数组,argv[0]表示程序名,而后参数依次对应
  • envp:字符指针数组,表示运行环境的各种环境变量
显示环境变量 text.exe
#include<stdio.h>
#include<stdlib.h>

int main(int argc, char *argv[], char *envp[]){

    int i;
    for (i = 0; i < envp[i] != NULL; i++)
        printf("%s\n", envp[i]);

    system("pause");
    return 0;
}

将程序输出重定向至文件中

D:\Users\lenovo\VScode-C> test>result.txt
D:\Users\lenovo\VScode-C> test>>result.txt
D:\Users\lenovo\VScode-C> test<result.txt
main函数结束后执行代码
#include<stdlib.h>
void close_all(){}
void message(){}

void main()
{
	/*
	函数声明区
	*/
	atexit(close_all);
	atexit(message);
	statements;
}

需注意atexit()函数按LIFO后进先出注册这些函数并调用,因此我们将close_all()函数写在上方。

main函数中调用其他程序
#include<process.h>
#include<stdio.h>
#include<stdlib.h>

system("pause");
system("dir");
system("text");

.exe文件作为可执行文件,可被system语句进行直接调用(读者可以观察自己mingw的文件夹下包含着大量.exe文件),这种方式可以很方便的进行功能的叠加和使用。

宏与枚举

在这里插入图片描述

define部分

#define作用于编译器预处理程序时,对程序中的对应部分进行修改,拿空间换时间。(挺神奇的,感兴趣可以去读一读《编译原理》

#define PI 3.1415926
#define MAX(a, b) (a>b)?a:b

//通过\连接多行宏定义,但第二行开始就不是宏的组成部分(需要加入;)
#define LINE for(i=0;i<80;i++) \
			printf("-");


//条件编译
//条件编译检查预处理器的宏定义情况进行语句调用
#define LAPTOP

#ifdef LAPTOP
	code0;
#ifndef LAPTOP
	code1;
#elif X == 2
	code2;
#else
	code3;
#endif
	code4;
enum部分
enum week {Sunday, Monday, Tuesday, Wednesday, Thursday };
enum week day;
for(day=Sunday; day<=Thursday; day++)
	printf("\n%d", day);
include部分

双引号:“用户自定义.h”,不支持函数重载
尖括号:<C标准库的内置头文件.h>,支持函数重载
头文件可嵌套包含

C语言轶事

GNU、MinGW\Cygwin、GCC、gcc\g++\gdb

  1. GNU是什么
    1990年,GNU计划已经开发出的软件包括了一个功能强大的文字编辑器Emacs 。GCC(GNU Compiler Collection,GNU编译器集合),是一套由 GNU 开发的编程语言编译器。以及大部分UNIX系统的的程序库和工具。唯一依然没有完成的重要组件就是操作系统的内核(称为HURD)。1992年Linux与其他GNU软件结合,完全自由的操作系统正式诞生。

    GNU 包含3个协议条款:

    1. GPL:GNU通用公共许可证(GNU General Public License)
      GPL v3的官方说明:http://www.gnu.org/licenses/gpl-3.0.html,GPL要求软件以源代码的形式发布,并规定任何用户能够以源代码的形式将软件复制或发布给别的用户, 如果用户的软件使用了受 GPL 保护的任何软件的一部分,那么该软件就继承了 GPL 软件,并因此而成为 GPL 软件,也就是说必须随应用程序一起发布源代码, GPL 并不排斥对自由软件进行商业性质的包装和发行,也不限制在自由软件的基础上打包发行其他非自由软件(没明白!)。

    2. LGPL:GNU较宽松公共许可证 (GNU Lesser General Public License )
      由于GPL很难被商业软件所应用,它要求调用它的库的代码也得GPL,全部开放,并且一同发布,不能直接连接。所以后来GNU推出了LGPL许可证。在GPL与LGPL许可证保护下发布源代码的结果很相似,对旧代码所做的任何修改,对于想知道这些代码的人必须是公开的,唯一真正的不同之处在于私人版权代码是否可以与开放源代码相互连接,LGPL允许实体连接私人代码到开放源代码,并可以在任何形式下发布这些合成的二进制代码。只要这些代码是动态连接的就没有限制(???)。(使用动态链接时,即使是程序在运行中调用函数库中的函数时,应用程序本身和函数库也是不同的实体)

    3. GFDL : GNU自由文档许可证(GNU Free Documentation License )的缩写形式。

  2. MinGW、cygwin是什么

    MinGW(Minimalist GNU For Windows)是个精简的Windows平台下的 C/C++、ADA及Fortran编译器,相比Cygwin (Cygwin是一个在windows平台上运行的类UNIX模拟环境,对于学习UNIX/Linux操作环境,或者从UNIX到Windows的应用程序移植,或者进行某些特殊的开发工作,尤其是使用GNU工具集在Windows上进行嵌入式系统开发,非常有用) 而言,MinGW体积要小很多,使用较为方便

    MinGW最大的特点就是编译出来的可执行文件能够独立在Windows上运行。
    MinGW是一个外壳(VISUAL IDE),编译工具是GCC.
    其实MinGW只是把gcc(g++ gdb等)封装一下,便于使用而已.
    综上来说,MinGW具有gcc的所有功能,并且支持几乎所有非类UNIX特性的库函数.

    cygwin/gcc和MinGW都是gcc在windows下的编译环境,但是它们有什么区别,在实际工作中如何选择这两种编译器。
    cygwin/gcc完全可以和在linux下的gcc做等号,这个可以从boost库的划分中可以看出来端倪,cygwin下的gcc和linux下的gcc完全使用的是相同的Toolsets。
    所以完全可以和linux一起同步更新gcc版本,而不用担心问题,并且在cygwin/gcc做的东西(不用win32的)可以无缝的用在linux下,没有任何问题。是在windows下开发linux程序的一个很好的选择。但是在cygwin/gcc下编译出来的程序,在windows执行必须依赖cygwin1.dll,并且速度有些慢,如果不想依赖这个东西的化,必须在gcc的编译选项中加入-mno-cygwin。加入这个选项其实gcc编译器就会自动的选择在安装cygwin/gcc时安上的mingw,这个mingw就是gcc的一个交叉编译。
    对于mingw作为gcc在windows上的一个实现,不像cygwin的gcc在一个模拟linux上运行,同时相当一部分linux的工具不能够使用。

    根据以上的分析,如果在windows开发linux程序,cygwin是很好的选择。如果你开发的程序不介意有一个cygwin1.dll的话,也是可以选择cygwin的。如果你是想开发windows下的程序,还要必须用gcc的话,mingw是很好的一个选择

gcc和MinGW的异同
MinGW ,GNU 是什么

  1. gcc和g++的区别

    gdb 是调试工具

    gcc与g++编译器的程序文件分别为:/usr/bin/g++和/usr/bin/gcc。

    gcc 和 GCC 是两个不同的东西,GCC:GNU Compiler Collection(GUN 编译器集合),它可以编译C、C++、JAV、Fortran、Pascal、Object-C、Ada等语言。gcc是GCC中的GUN C Compiler(C 编译器);g++是GCC中的GUN C++ Compiler(C++编译器)。

    首先需要强调一点,gcc与g++都可以编译C和C++源程序,对于.c文件gcc当做c语言处理,g++当做c++处理;对于.cpp文件gcc和g++均当做c++处理。C++是C的超集,但是两者对语法的要求标准是不一样的,C++对语法的要求更加严格一些,更加规则。

    • 预处理阶段,gcc与g++对.c和.cpp产生的效果是一样的,即该阶段两者都可以正常进行。

    • 编译阶段,g++实质上是调用的是gcc,因此在编译阶段两者是一样的,但是gcc程序不能自动将c++程序使用的库文件进行链接。在此阶段虽然不需要对库文件进行链接,但是同样需要识别C++使用的库文件,从而对库文件进行申明,因此gcc在编译阶段直接编译C++程序会报错。而g++可以成功编译生成汇编语言代码

    • 汇编阶段,都是利用as程序将汇编语言代码.s译为机器代码.o,因此也完全一样。

    • 链接阶段gcc无法将C++程序生成的.o文件转化为可执行程序,因为gcc程序不能自动将c++程序使用的库文件进行链接,而g++可以正常进行。

    因此,gcc与g++在执行C程序的过程中所做工作是一样的(g++调用了gcc来完成各项工作),都可以正常运行。gcc和g++的区别主要是在对cpp文件的编译和链接过程中,因为cpp和c文件中库文件的命名方式不同,g++既可以编译C又可以编译C++是因为g++在内部做了处理,默认编译C++程序,但如果遇到C程序,它会直接调用gcc去编译。

    extern "C"对于gcc和g++的效果是一样的。extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般之包括函数名。

     但是为了能够是gcc能正常完成C++的编译,需要在编译和链接阶段采用
     lstdc++参数,其余操作不变。g++的使用完全同gcc,只是不需要加- lstdc++参数,
     如: g++ -E hello.c -o hello.i    g++ hello.cpp -o hello       
     gcc -S -lstdc++ hello.ii -o hello.s(hello.ii是hello.cpp经过预处理产生的)     
     gcc -lstdc++ hello.cpp -o hello  
    

gcc编译器与g++编译器的区别

VScode-C的问题

程序可在DEV-C++调试,但不能在VC调试

#include<stdio.h>
#include<time.h>

int main(){
	time_t t;        //声明time_t类型变量
    struct tm *start_time, *end_time;  //tm结构指针
    
	time (&t);//获取时间戳。  
    start_time = localtime (&t);//转为时间结构。  
    printf ( "%d\n", start_time);
//    printf ( "%d/%d/%d %d:%d:%d\n",lt->tm_year+1900, lt->tm_mon, lt->tm_mday, lt->tm_hour, lt->tm_min, lt->tm_sec);//输出结果  	

	int length = 100000;
	int evens[length/2];
	int j = 0, i;

	for(i=0;i<=length;i++){
		if (i % 2 == 0){
			evens[j] = i;
			j += 1;
		}
	}
	
	time (&t);
	end_time = localtime (&t);
	printf ( "%d\n", end_time);
	
	// for(i=0;i<=length/2;i++){
	// 	printf("%d ", evens[i]);
	// 	if(i % 10 == 0){
	// 		printf("\n");
	// 	}	
	// }
	
	printf("%d", end_time - start_time);
		
	return 0;
} 

问题描述
开始调试,终端显示以下警告

=thread-group-added,id="i1"
GNU gdb (GDB) 8.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-w64-mingw32".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
Warning: Debuggee TargetArchitecture not detected, assuming x86_64.
=cmd-param-changed,param="pagination",value="off"

进一步调试遇到以下错误

无法打开“cygwin.S”: 无法读取文件'c:\mingw810\src\gcc-8.1.0\libgcc\config\i386\cygwin.S' 
(Error: 无法解析不存在的文件"c:\mingw810\src\gcc-8.1.0\libgcc\config\i386\cygwin.S")。

经过查询,得知自己的数据结构有问题,由于有总规模大于10w的数组,程序爆栈(stack)了

解决方法
开全局变量(用堆(heap))

C++ 栈内存与堆内存小探究
VSCODE调试时在cygwin.S中发生段错误
vscode报错:无法打开“cygwin.S”:

表达式必须含有常量值变量 – 变量 “length” (已声明 所在行数:13) 的值不可用作常量

DEV-C++使用的编译器是GCC,它允许使用变量作为数组的长度定义数组。
VC的编译器不是GCC,它不允许你这样做。

方法1:使用动态内存分配,new和delete操作符

int num;
cin >> num;
int* a = new int[num];
…
delete[] a;

方法2:使用vector容器

int num;
cin >> num;
vector a(num);

注意:在为数组分配内存失败的时候,以上两种方法均会抛出异常bad_alloc

c++中使用关键字new来进行动态内存申请,它是基于类型进行的,使用关键字delete进行内存释放

1.变量申请

Type *p=new Type;
...
delete p;
//也可以进行初始化
//例如
int *p=new int(2);//动态分配一个int的空间并初始化为3

2.一维数组申请

Type *p=new Type[n];
...
delete[] p;
//例如
 
int *p=new int[5];
...
delete[] p;

3.二维数组申请

Type **p=new Type*[m];
//数组p[m][n];
for(int i=0;i<m;++i)
{
    p[i]=new Type[n];
}

之前一直这样创建二维数组,然后使用memset将数组初始化为0,但是总是报错

int **edge = new int*[frame.rows];
for (int k = 0; k < frame.rows; ++k)
{
	edge[k] = new int[frame.cols];
}
memset(edge, 0, sizeof(edge));

做了修改后,这样写就不报错了,暂时还没有搞懂为啥,仅供参考吧,弄清楚之后再更新

int **edge = new int*[frame.rows];
for (int k = 0; k < frame.rows; ++k)
{
	edge[k] = new int[frame.cols];
	memset(edge[k], 0, sizeof(int)*frame.cols);

run code后,length会影响程序的运行

int length = 1036225; 时
程序可以执行完毕
int length > 1036225; 时
程序只能显示start_time,然后自动结束

why???

C++

C++ 知识点回顾

面向对象编程

C++ 高级技巧

变量命名

【C++】关于以下划线开头的变量名

写程序千万不要_开头
系统头文件、宏名、变量名、内部函数大多数都是_开头
当变量是私有的时候,用_xxx 来表示变量是很好的习惯
Python_ 开始的成员变量叫做保护变量,意思是只有类对象和子类对象自己能访问到这些变量;而 __ 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据:

  • 以单下划线开头(_foo)的代表不能直接访问的类属性,需通过类提供的接口进行访问,不能用from xxx import *而导入;

  • 以双下划线开头的(__foo)代表类的私有成员

  • 以双下划线开头和结尾的(__foo__)代表python里特殊方法专用的标识,如 __init__()代表类的构造函数

  • from numpy import *不能导入以单下划线开头的保护属性和以双下划线开头的私有属性

  • import numpy可以

C

  • 以单下划线(_)表明是标准库的变量
  • 双下划线(__) 开头表明是编译器的变量
class InitParamSetting
{

	public:
		InitParamSetting();
		~InitParamSetting();
	
	public:	
		void ReadRegInfo();
		void WriteRegInfo();
		
		CString _localRecPath;//本地录像存放路径
		int _maxLocalRecTime;//最大录像时间(以分钟为单位)
		CString _capPicPath;//抓图存放路径
		CString _vedioWndLabel;//视频窗口标签
 
 };

类的深入

函数重载(overload)和函数重写(override)的基本规则

函数重载(overload)和函数重写(override)的基本规则

函数重载

需求点:
因为在一个程序中,会出现很多很多,完成的函数功能完全相同,而仅仅是函数的参数略有不同的情形。这时如果没有函数重载这个概念,那么开发人员恐怕就要为如何为功能完全相同的函数起不同的名而头疼了。

特点:

  • 同名不同参,即不同的参数类型,不同的参数个数,或者不同的参数顺序。
  • 函数返回值可以相同,也可以不相同。

应用场景:
类的构造函数、类的成员函数。

函数重写

需求点:
函数重写,亦称覆盖,是指子类重新定义父类中有相同名称和参数的虚函数,主要在继承关系中出现。

特点:

  • 重写函数和被重写的函数必须为virtual函数,函数必须完全一致。
  • 返回值相同,或者返回指针或引用,并且派生类虚函数返回的指针或引用的类型是基类中被替换的虚函数返回的指针或引用的类型的字类型。

应用场景:
为了水一下这篇博客的字数。

C++程序的性能调优

C++程序的性能调优

windows:Intel’s VTune , xperf(WINDOWS7 VC2008 SDK ), amd code analysis, Compuware’s Devpartner Performance Analysis Community Edition, GlowCode

WINDOWS下免费的性能分析工具:
Very Sleepy : http://www.codersnotes.com/sleepy/sleepy
Luke Stackwalker :http://lukestackwalker.sourceforge.net/
Shiny http://sourceforge.net/projects/shinyprofiler/
VSPTree: http://code.google.com/p/vsptree/
Timing:http://ravenspoint.wordpress.com/2010/06/16/timing/

C++程序的性能调优
c++ 性能分析工具
Rational的检测工作——Purify
Purify使用指南

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值