C语言概念笔记

0. 预定义宏
1. 关键字extern
2. 关键字typedef&结构体
3. 指针&二维数组寻址
4. 文件读写
5. 变长数组
6. 链表(先入后出)
7. 队列(FIFO)
8. 位域结构&位操作 (可做寄存器位操作)
9. 函数指针(被调函数)
10. extern "C"编译(按C语言规则编译)



数据结构动画:	https://totuma.cn/
在线C编译器:	https://www.onlinegdb.com/#
C语言基础: 	https://www.runoob.com/cprogramming/c-tutorial.html
C语言快速参考:	https://quickref.me/zh-CN/docs/c.html#%E5%85%A5%E9%97%A8




0. 预定义宏
	__LINE__源代码文件的行号(整数)
	__FILE__文件名称(字符串)
	__DATE__编译文件时的日期(字符串)
	__TIME__编译时的时间(字符串)
	__FUNCTION__   __func__返回程序名(字符串)
// 常用于调试信息
printf("[Debug]DATE=%s TIME=%s in FUN=%s LINE=%d :Var=%d\n", __DATE__,__TIME__,__func__,__LINE__, var);//var = 12
printf("%-9d %-9d %-9d\n", a1, a2, a3);
//%-9d 中,d 表示以 10 进制输出,9 表示最少占 9 个字符的宽度,宽度不足以空格补齐,- 表示左对齐

1.关键字extern
1.1.作用变量
extern int global_var;	//声明一个全局变量,告诉编译器该变量在其他文件中定义
1.2.作用函数
extern void global_func();	//声明一个全局函数,告诉编译器该函数在其他文件中定义

2. 关键字typedef&结构体
	typedef 的功能来声明一个已有的数据类型的新名字
	//typedef int Length使得Length成为int类型的别名
2.1 .1 结构体原生定义
struct point{ 	//结构体类型:定义平面上的点
    int x;
    int y;
};
struct point POINT = { 1,1 } ; 	//结构体变量定义和初始化:

struct rectangle{	//结构体类型:嵌套定义平面上的矩形
    struct point leftUp;
    struct point rightDown;
};//已知斜对角两点,可定义一个矩形
struct point RECT = { {1,1} ,{100,100} } ; 	//结构体变量定义和初始化:
2.1 .2 结构体类型重定义(推荐)
typedef struct { 	//结构体类型:定义平面上的点
    int x;
    int y;
} strtPoint ;
strtPoint  POINT = { 1,1 } ; 	//结构体变量定义和初始化:

typedef struct {	//结构体类型:嵌套定义平面上的矩形
    strtPoint leftUp;
    strtPoint rightDown;
} strtRect ;//已知斜对角两点,可定义一个矩形
strtRect RECT = { {1,1} ,{100,100} } ; 	//结构体变量定义和初始化:

2.2 结构体间复制
// 使用memcpy()函数将s1的值复制给s2 
memcpy(&RECT2, &RECT1, sizeof(strtRect));


3.指针
//------------------------
3.1. 数组名就是指针(地址)
 若 int *p = a[10];
  a == a[] == p == &(a[10]) ==0xYYYYZZZZ
 指向指针的指针 char **p (=指向[数组首地址]的指针)
 

3.2. 指针增减
 若 int *p = a[10];
p+1== 0xYYYYZZZZ+0x4 :指针加1,是加1个数据类型int的长度,而非一个字节 
   == p+sizeof(p)

3.3. 指针内容读取
 若 int *p = a[10],int *p2 = b[10][8];
*p=p[0]=a[0] :指针==数组名,可按数组索引方式取值 
*(p+1)=p[1]=a[1]				//--- 指针索引一维数组
*(*(p2+i)+j)=p[i][j]=b[i][j]	//--- 指针索引二维数组

3.4 指针访问结构体成员
-> 运算符:语法为 pointer->member,等价于 (*pointer).member

3.5 函数指针
函数指针的声明类似于函数的声明,只不过将函数名变成了 (*指针名)
例如:int (*fp)(int a);
这里就定义了一个指向函数(这个函数参数仅仅为一个 int 类型,函数返回值是 int 类型)的指针 fp。

3.6 指针的指针(指针数组)
int	**p   :指针的指针 = 一个指针指向元素-->元素均为指向int类型的指针
int	*p[]  :指针数组   = 一个指针指向数组-->数组元素均为指向int类型的指针
int	p[][] :二维数组名 = p为指针指向行数组p[]-->数组元素p[i]为指向一行int类型的指针
*(*(p2+i)+j)=p[i][j]=b[i][j]	//--- 指针索引二维数组

4. 文件读写
计算机里文件都是按二进制存储的,可按两种格式解析
(a) 二进制文件: 每个字节理解为纯数字
(b)文本文件: 	每个字节理解为纯数字对应的文本(ASCII码)

fopen()	打开新文件或现有文件
	[r/w/a]: 读/写/追加,把每个字节按文本(ASCII码)解析
 	[b]: 二进制文件,把每个字节按纯数字解析
	[+]
fputc()	将一个字符文件写入
fgetc()	从文件中一个字符读取
fseek()	将文件指针设置到给定位置
ftell()	返回当前位置
rewind()	将文件指针设置为文件的开头

  /*------  Part0: 打开文件  ---------*/  
	FILE *fp = fopen("test56.txt", "w+");
  	//--- ftell() 当前文件内光标地址 
   	int loc = ftell(fp);
    	printf("loc=%d\n", loc);//loc=0
    
   /*------  Part1: 读写文本流  ---------*/
	//--- fprintf() 将数据按指定模板格式,写成文本流 
    	fprintf(fp,"%s %d\n","abcde",18);
	//--- fscanf() 按指定模板读取文本流,并按格式解析 
	char str[100]; 
	int num;
	fseek(fp, 0, SEEK_SET);	// 定位文件的开头/
	fscanf(fp,"%s %d\n", str, &num);
    	printf("str=%s num=%d\n", str, num);  

    /*------  Part2: 读写文本块  ---------*/   
	//--- fwrite() 向文件写文本块             
	char c[] = "This is runoob";
	char buffer[30]; 
	fseek(fp, 0, SEEK_SET);	// 定位文件的开头/
   	fwrite(c, strlen(c) + 1, 1, fp);
	//--- fread() 向文件读文本块  
   	fseek(fp, 0, SEEK_SET);  // 定位文件的开头/
   	fread(buffer, strlen(c)+1, 1, fp);
   	printf("%s\n", buffer);
   	
    /*------  Part3: 关闭文件  ---------*/  
   	 fclose(fp);


5. 变长数组
//定义数组结构 
typedef struct {
	int* array;	//数组指针 
	int size;	//数组大小 
} Array;

//可变数组的创建
Array array_creat(int init_size) {
	Array a ;
	a.size = init_size;
	a.array = (int*)malloc(sizeof(int) * a.size);
	return a;
}

//可变数组扩容
Array* array_inflate(Array* a, int more_size) {
	int size = a->size + more_size;
	int* p = (int*)malloc(sizeof(int) * (size)); 
	for (int i = 0; i < a->size; i++) {
		p[i] = a->array[i];
	}
	//memcpy(p, a->array, a->size);
	free(a->array);
	a->array = p;
	a->size = size;
} 

//返回可变数组下标处的地址
int* array_index(Array* a, int index) {
	return &(a->array[index]);
}
//可变数组元素的赋值 
//*(array_index(&a, 0)) = 10;

//可变数组的析构
void array_free(Array* a) {
	free(a->array);
	a->size = 0;
	a->array = NULL;
} 
#include <stdio.h>

int main() {
	
	int i;
	int old_size = 5;
	Array Tst = array_creat(old_size);	//chu 
	for(i=0;i<Tst.size;i++) {
		*(array_index(&Tst, i)) = i;	
	}
	for(i=0;i<Tst.size;i++) {
		printf("Tst.array[%d]=%d\n",i,Tst.array[i]);
	}	
	
	int more_size = 7;
	Array* pTstN = array_inflate(&Tst, more_size);
	for(i=0;i<pTstN->size;i++) {
		*(array_index(pTstN, i)) = i+7;	
	}
	for(i=0;i<pTstN->size;i++) {
		printf("pTstN->array[%d]=%d\n",i,pTstN->array[i]);
	}	
	
	printf("&Tst=0x%x, Tst.array=0x%x,, Tst.size=0x%x\n",&Tst,Tst.array,&(Tst.size));
	printf("pTstN=0x%x, pTstN->array=0x%x,, pTstN->size=0x%x\n",pTstN,pTstN->array,&(pTstN->size));
	array_free(&Tst);
	array_free(pTstN);
	

}

6. 链表(先入后出)
	
// 全站内容仅供学习,禁止以原文或修改形式后的任何企业使用,请准守“一般著作权”协议
// 来源:totuma.cn
#include <stdio.h>
#include<stdlib.h>
using namespace std;

// 定义双链表结构
typedef struct DNode {
  int data; // 数据
  struct DNode *prior, *next; // 前驱和后继指针
} DNode, *DLinkList;

// 初始化链表
bool DList_Init(DLinkList &pHead) {
  pHead = NULL; // 不再创建头结点
  return true;
}

// 判断双链表是否为空(不带头节点)
bool DList_Empty(DLinkList pHead) {
  return pHead == NULL;
}

// 获取链表有效数据节点个数
int DList_Length(DLinkList pHead) {
  int count = 0;
  DNode *p = pHead;
  while (p != NULL) { // 当指针p不为空时,即链表不为空时
    count++;
    p = p->next; // 将指针p移动到下一个节点
  }
  return count;
}


// totuma.cn
// 创建双链表,头插结果为倒序(不带头节点)(Last data is head)= HEAD->55->...->11->Null 
DLinkList DList_Create(DLinkList &pHead) {
  pHead = NULL; // 初始化链表头指针为空,表示链表为空
  int x;
  scanf("%d", &x);
  DNode *pTemp; // 创建一个临时节点指针pTemp
  while (x != 999) {
    pTemp = (DNode *)malloc(sizeof(DNode));
    pTemp->data = x; // 将新节点的数据域设置为x
    pTemp->prior = NULL; // 新节点的prior为空,因为是头插法,头始终为空
    pTemp->next = pHead; // 新节点的next指向链表当前头节点
    if (pHead != NULL) {
      pHead->prior = pTemp; // 设置原头节点的prior指向新节点
    }
    pHead = pTemp; // 更新链表头指针为新节点
    scanf("%d", &x);
  }
  return pHead; // 返回创建好的双链表的头指针
}


// totuma.cn
// 按位序插入,i=1插在表头,i=length+1插在表尾
bool DList_Insert(DLinkList &pHead, int i, int e) {
  // 检查插入位置的有效性
  if (i < 1 || i > DList_Length(pHead) + 1) return false;
  // 创建一个新节点pTemp并分配内存
  DNode *pTemp = (DNode *)malloc(sizeof(DNode));
  pTemp->data = e;

  // 如果插入位置是链表头
  if (i == 1) {
    pTemp->next = pHead; // 新节点的next指向当前头节点
    pTemp->prior = NULL; // 新节点的prior为空,因为是头插法
    if (pHead != NULL) { // 如果链表不为空
      pHead->prior = pTemp; // 设置原头节点的prior指向新节点
    }
    pHead = pTemp; // 更新链表头指针为新节点
  } else { // 插入位置不在表头
    int j = 1;
    DNode *p = pHead; // 创建一个指针p,指向链表头节点
    while (j < i - 1) { // 循环找到要插入位置的前驱结点
      p = p->next;
      j++;
    }
    pTemp->next = p->next; // 新节点的next指向前驱结点的下一个节点
    pTemp->prior = p;  // 新节点的prior指向前驱结点
    if (p->next != NULL) { // 如果前驱结点的下一个节点存在
      p->next->prior = pTemp; // 设置前驱结点的下一个节点的prior指向新节点
    }
    p->next = pTemp; // 设置前驱结点的next指向新节点
  }
  return true;
}


// totuma.cn
// 通过值获取结点和其位序
DNode* DList_Get_Elem(DLinkList pHead, int e, int &i) {
  i = 1;
  DNode *p = pHead; // 创建一个指针p,指向链表头节点
  while (p != NULL && p->data != e) { // 循环查找值为e的节点或到达链表尾部
    p = p->next; // 移动到下一个节点
    i++;
  }
  return p;// 返回找到的节点,如果没有找到则返回NULL
}


// totuma.cn
// 按位序删除,i=1删表头,i=length删头尾
bool DList_Del(DLinkList &pHead, int i) {
   // 检查删除位置的有效性
  if (i < 1 || i > DList_Length(pHead)) return false;

  if (i == 1) { // 如果删除位置是链表头
    DNode *q = pHead; // 创建一个指针q,指向链表头节点
    pHead = pHead->next; // 更新链表头指针为下一个节点
    if (pHead != NULL) { // 如果链表不为空
      pHead->prior = NULL; // 设置新头节点的prior为空
    }
    free(q); // 释放原头节点的内存
  } else { // 删除位置不在表头
    int j = 1;
    DNode *p = pHead; // 创建一个指针p,指向链表头节点
    while (j < i - 1) { // 循环找到要删除位置的前驱结点
      p = p->next; // 移动到下一个节点
      j++; // 增加位置计数器
    }
    DNode *q = p->next; // 创建一个指针q,指向待删除的节点
    p->next = q->next; // 前驱结点的next指向待删除节点的下一个节点
    if (q->next != NULL) { // 如果待删除节点的下一个节点存在
      q->next->prior = p; // 设置待删除节点的下一个节点的prior指向前驱结点
    }
    free(q); // 释放待删除节点的内存
  }
  return true;
}


// totuma.cn
// 打印链表所有值
void DList_Show(DLinkList pHead) {
  DNode *node = pHead;
  printf("链表值(从头遍历):");
  while (node != NULL) {
    printf("%d、", node->data);
    node = node->next;
  }
  printf("\n");
}

// 打印链表所有值,从尾到头
// 双链表是可以走回头路的,所以可以从最后一个结点往前遍历
void DList_Show_Back(DLinkList pHead) {
  DNode *node = pHead;
  while (node->next != NULL) {
    node = node->next;
  }
  printf("链表值(从尾遍历):");
  while (node != NULL) { // 从最后一个节点开始循环,一直到链表头
    printf("%d、", node->data);
    node = node->prior;
  }
  printf("\n");
}

int main() {
  DLinkList pHead;
  DList_Init(pHead);
  printf("链表判空:%s\n", DList_Empty(pHead) ? "空" : "非空");

  DList_Create(pHead);
  DList_Show(pHead);
  DList_Show_Back(pHead);
  printf("链表长度:%d\n\n", DList_Length(pHead));

  printf("位序为1(头插),插入:0\n");
  DList_Insert(pHead, 1, 0);
  DList_Show(pHead);
  printf("链表长度:%d\n\n", DList_Length(pHead));

  printf("位序为%d(尾插),插入:99\n", DList_Length(pHead) + 1);
  DList_Insert(pHead, DList_Length(pHead) + 1, 99);
  DList_Show(pHead);
  printf("链表长度:%d\n\n", DList_Length(pHead));

  printf("删除位序为1(头删)\n");
  DList_Del(pHead, 1);
  DList_Show(pHead);
  DList_Show_Back(pHead);
  printf("链表长度:%d\n\n", DList_Length(pHead));

  printf("删除位序为%d(尾删)\n", DList_Length(pHead));
  DList_Del(pHead, DList_Length(pHead));
  DList_Show(pHead);
  DList_Show_Back(pHead);
  printf("链表长度:%d\n\n", DList_Length(pHead));

  printf("获取值为:5的结点位序\n");
  int i = -1;
  DNode *p5 = DList_Get_Elem(pHead, 5, i);
  printf("值为5的结点位序为:%d\n\n", p5 != NULL ? i : -1);

  printf("链表判空:%s\n", DList_Empty(pHead) ? "空" : "非空");
  return 0;
}



7. 队列(FIFO)


// 全站内容仅供学习,禁止以原文或修改形式后的任何企业使用,请准守“一般著作权”协议
// 来源:totuma.cn
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef struct LNode {
  int data;
  struct LNode* next;
} LNode;

typedef struct {
  LNode* front; // 队头 (读指针) 
  LNode* rear;  // 队尾 (写指针) 
  int len;    // 当前队列的成员数
} LinkQueue;

// 初始化队列(不带头结点)
bool Queue_Init(LinkQueue* Q) {
  Q->front = Q->rear = NULL;
  Q->len = 0;
  return true;
}

// 判断队列是否为空
bool Queue_Empty(LinkQueue* Q) {
  return Q->front == NULL;
}

// 获取队列有效数据节点个数
int Queue_Length(LinkQueue* Q) {
  return Q->len;
}

// 创建队列
void Queue_Create(LinkQueue* Q) {
  int x;
  scanf("%d", &x);
  while (x != 999) {
    LNode* pTemp = (LNode*)malloc(sizeof(LNode));
    pTemp->data = x;
    pTemp->next = NULL;
    if (Q->rear == NULL) {
      Q->front = Q->rear = pTemp;
    } else {
      Q->rear->next = pTemp;
      Q->rear = pTemp;
    }
    Q->len += 1;
    scanf("%d", &x);
  }
}


// totuma.cn
// 新元素入队
bool Queue_En(LinkQueue* Q, int e) {
  LNode* pTemp = (LNode*)malloc(sizeof(LNode));
  pTemp->data = e;
  pTemp->next = NULL;
  if (Q->front == NULL) {
    Q->front = pTemp;
    Q->rear = pTemp;
  } else {
    Q->rear->next = pTemp;
    Q->rear = pTemp;
  }
  Q->len += 1;
  return true;
}


// totuma.cn
// 出队
bool Queue_De(LinkQueue* Q, int* e) {
  if (Q->front == NULL) return false;
  LNode* p = Q->front;
  Q->front = p->next;
  *e = p->data;
  Q->len -= 1;
  if (Q->front == NULL) {
    Q->rear = NULL;
  }
  free(p);
  return true;
}


// totuma.cn
// 打印队列所有值
void Queue_Show(LinkQueue* Q) {
  LNode* node = Q->front;
  printf("队列值:");
  while (node != NULL) {
    printf("%d、", node->data);
    node = node->next;
  }
  printf("\n");
}


// totuma.cn
int main() {
  int i = -1;
  LinkQueue Q;
  Queue_Init(&Q);
  printf("队列判空:%s\n", Queue_Empty(&Q) ? "空" : "非空");


  Queue_En(&Q, 10);
  Queue_En(&Q, 20);
  Queue_En(&Q, 30);
  Queue_En(&Q, 40);
  Queue_En(&Q, 50);
  Queue_Show(&Q);
  printf("队列长度:%d\n\n", Queue_Length(&Q));

  Queue_De(&Q, &i);
  printf("出队列值:%d\n", i);
  Queue_Show(&Q);
  printf("队列长度:%d\n\n", Queue_Length(&Q));

  Queue_De(&Q, &i);
  printf("出队列值:%d\n", i);
  Queue_Show(&Q);
  printf("队列长度:%d\n\n", Queue_Length(&Q));

  printf("队列判空:%s\n", Queue_Empty(&Q) ? "空" : "非空");
  return 0;
}

8. 位域结构&位操作
#include <stdio.h>
#include<stdlib.h>

/*--- C语言中的位域结构 
	C 语言又提供了一种数据结构,称为"位域"或"位段"(把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。)
struct [位域变量名] 
{
	type [member_name] : width ;  //位域元素列表: width=占用bit位 ( <type长度)
	...
};
	位域变量名.位域名
	位域变量名->位域名
-------------------*/
//基于位域,联合体,结构体,可以实现另一种位操作,这对于封装底层硬件寄存器具有重要意义,实践如下:

typedef unsigned char uint8_t;

// type1 : 联合体+位域结构,封装可位操作寄存器 
typedef union reg{     
    struct{              //--- 位域结构 --- head 
    	uint8_t bit0:1;         // 位域元素 bit0 --- 1bit宽 
    	uint8_t bit1:1;         // 位域元素 bit0 --- 1bit宽       
    	uint8_t bit2_6:5;       // 位域元素 bit0 --- 1bit宽        
   		uint8_t bit7:1;         // 位域元素 bit0 --- 1bit宽    
  	};     			 //--- 位域结构 --- end 
  	uint8_t value;
}REG,*pREG;
// type2 : 位域结构 ,封装可位操作寄存器 
typedef  struct {          
    uint8_t bit0:1;         
    uint8_t bit1:1;         
    uint8_t bit2_6:5;         
    uint8_t bit7:1;     
}HWReg;
 
int main(void)
{  
 // type1 : 联合体+位域结构,封装可位操作寄存器   
  REG RegData;     
  RegData.value = 0;      
  RegData.bit0 = 1;     
  RegData.bit7 = 1;     
  printf("0x%x\n", RegData.value);      
  RegData.bit2_6 = 0x3;     
  printf("0x%x\n", RegData.value);
  
  	pREG pRegData = &RegData ;
  	printf("RegData.value = 0X%X\n"	, pRegData->value);
	printf("RegData.bit0 = 0X%X\n"	, pRegData->bit0);
	printf("RegData.bit2_6 = 0X%X\n", pRegData->bit2_6);
	printf("RegData.bit7 = 0X%X\n"	, pRegData->bit7);
  
 // type2 : 位域结构 ,封装可位操作寄存器  
  HWReg R10 ;
  //R10 = {0,0,0,0};   
  printf("0x%x\n", sizeof(R10));  
  printf("0x%x\n", R10); 
  //R10 = {0};        
  R10.bit0 = 1;     
  R10.bit7 = 1; 
  R10.bit2_6 = 3;
  printf("0x%x\n", R10.bit2_6);  
  printf("0x%x\n", R10);     
}
/*
0x81
0x8d
*/
 

9. 函数指针
/*-------------
函数指针是指向函数的指针变量。可以像一般函数一样,用于调用函数、传递参数(其它函数执行时调用被调函数)。

函数指针类型的声明:
typedef int (*fun_ptr)(param type); // 声明一个指向同样参数、返回值的函数指针类型1
--------------*/
#include <stdlib.h>  
#include <stdio.h>
// 主调函数 
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))  //函数指针做参数 
{
    for (size_t i=0; i<arraySize; i++)
        array[i] = getNextValue();         // 调用函数运行 
}
 
// 被调函数:获取随机值
int getNextRandomValue(void)
{
    return rand();
}
 
int main(void)
{
    int myarray[10];
    printf("name of func = 0x%x\n", getNextRandomValue); 
    printf("addr of func = 0x%x\n", &getNextRandomValue);  //函数名即为函数地址 

    /* getNextRandomValue 不能加括号,否则无法编译,因为加上括号之后相当于传入此参数时传入了 int , 而不是函数指针*/
    populate_array(myarray, 10, getNextRandomValue);
    for(int i = 0; i < 10; i++) {
        printf("%d ", myarray[i]);
    }
    printf("\n");
    return 0;
}

10. extern "C"编译(按C语言规则编译)
	_cplusplus是C++编译器中自定义的宏,extern "C"是告诉C++编译器括号中的程序代码是按照C语言的文件格式进行编译。
	C++支持函数重载,即在编译时会将函数名与参数联合起来生成一个新的中间函数名称;而C语言不支持函数重载,这就导致在C++环境下使用C函数会出现链接时找不到对应函数的情况。
	这时就需要使用extern "C"进行链接指定,告知编译器此时采用的是C语言定义的函数,需要使用C语言 的命名规则来处理函数,不要生成用于链接的中间函数名。

#ifdef__cplusplus
   extern "C"{
#endif
   //函数声明
#ifdef_Cplusplus
     }
#endif

11. 
程序流跳转至标签后,会一直执行,直到遇到break 语句。在所有case 都不匹配时,跳转至
default标签处。应该在switch 语句最后一个标签的末尾放置break语句。
switch  (n) {
     case 0 : cout << "A";
			  cout << "B";   break; // 0,执行到此
     case 2 : cout << "C";
     case 5 : cout << "D";   break; // 2或5,均执行到此
     case 6 :
     case 7 : cout << "E";   break; // 6或7,均执行到此
     default: cout << "F";   break;
    }

12. 	
1) 数组a 的元素个数可以通过sizeof( a) / sizeof(a[0] ) 求得;
2) new 运算符用来动态创建对象,delete 运算符用来动态销毁对象,delete[] 运算符用来
销毁数组。在动态创建数组时,可以在运行时确定元素个数;
3) 命名空间用来从逻辑上控制标识符的通用范围,
命名空间定义的形式如下所示。标识符是命名空间的名称,属于该命名空间的变量和函数的定义或声明放置在{}中。
namespace 标识符 {
    //  定义或声明
}
4) 构造函数(constructor )的名称与类名相同,没有返回值,它的作用是明确且恰当地初始化对象
	原则上,不能在头文件中放置using指令
5) 析构函数的名称由类名和类名前的波浪符构成。它是一个成员函数,在该类的对象的生命周期
将要结束时自动被调用,与构造函数的作用恰好形成对照。
	与构造函数一样,析构函数也不具有返回值。而与构造函数不同的是,析构函数是被自动调用
的,不接收参数。
6) 正常情况下,C++ 的运算符( +、-、*、/ 等)只能用于对基本类型的常量或变量进行运算,而不能用于类对象之间的运算。运算符重载的目的是使得 C++ 中的运算符也能够用来操作对象。
7) 对于无符号化为有符号的位数运算,采取 N-2^n 的计算方法,n 取决于定义的数据类型 int、short、char、long int 等等,N 为无符号数的数值,例如文中的 N=50000,short 为 16 位,计算方法为 50000-2^16 得到 -15536
8) static 存储类指示编译器[在程序的生命周期内]保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以[在函数调用之间]保持局部变量的值。
	静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下一次调用,直到下次赋新值。
	
	
9) [this]: 通过引用捕获当前对象(对象本身)

[*this]: 通过传值捕获当前对象(对象拷贝,且只是一个地址值,可以说没有意义)
	
12.C++中函数参数传递:值传递 Vs 引用传递 Vs 指针传递
当调用函数时,有三种向函数传递参数的方式:
传值调用:该方法把参数的实际值赋值给函数的形式参数。在这种情况下,修改函数内的形式参数对实际参数没有影响。
指针调用:该方法把参数的地址赋值给形式参数。在函数内,该地址用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。
引用调用:该方法把参数的引用赋值给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。
// 值传递C&C++__调用max1(10,5)
int max1(int a, int b){
	return a > b ? a = b ;
}
// 引用传递C++__调用max2(10,5)
int max2(int& a, int& b){
	return a > b ? a = b ;
}

// 指针传递C&C++__调用max3(&10,&5)
int max3(int* a, int* b){
	return *a > *b ? *a = *b ;
}
引用变量是一个别名(第二标签)


13. Lambda 函数与表达式
C++11 提供了对匿名函数的支持,称为 Lambda 函数(也叫 Lambda 表达式)。
Lambda 表达式把函数看作对象。Lambda 表达式可以像对象一样使用,比如可以将它们赋给变量和作为参数传递,还可以像函数一样对其求值。

Lambda 表达式本质上与函数声明非常类似。Lambda 表达式具体形式如下:
[capture](parameters)->return-type{body}
例如:
[](int x, int y) -> int { int z = x + y; return z + x; }

C++变量传递有传值和传引用的区别。可以通过前面的[]来指定:
[]      // 沒有定义任何变量。使用未定义变量会引发错误。
[x, &y] // x以传值方式传入(默认),y以引用方式传入。
[&]     // 任何被使用到的外部变量都隐式地以引用方式加以引用。
[=]     // 任何被使用到的外部变量都隐式地以传值方式加以引用。
[&, x]  // x显式地以传值方式加以引用。其余变量以引用方式加以引用。
[=, &z] // z显式地以引用方式加以引用。其余变量以传值方式加以引用。

14. 基类 & 派生类
类派生列表以一个或多个基类命名,形式如下:
	class 派生类: [public/protected/private] 基类
如:class Rectangle: public Shape
派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private

多继承即一个子类可以有多个父类,它继承了多个父类的特性。
C++ 类可以从多个类继承成员,语法如下:
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
	<派生类类体>
};

15. 重载
C++ 中的函数重载
在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。
C++ 中的运算符重载
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。
如:Box operator+(const Box&);
声明加法运算符用于把两个 Box 对象相加,返回最终的 Box 对象。

16. 命名空间
全局变量 a 表达为 ::a

嵌套的命名空间
命名空间可以嵌套,您可以在一个命名空间中定义另一个命名空间,如下所示:
namespace namespace_name1 {
	// 代码声明
	namespace namespace_name2 {
		// 代码声明
	}
}
您可以通过使用 :: 运算符来访问嵌套的命名空间中的成员:
// 访问 namespace_name2 中的成员
using namespace namespace_name1::namespace_name2; 
// 访问 namespace_name1 中的成员
using namespace namespace_name1;

17. C++ 模板
模板是创建泛型类或函数的蓝图或公式。

17.1 函数模板
模板函数定义的一般形式如下所示:
template <typename type> ret-type func-name(parameter list)
{
   // 函数的主体
}
//---定义
template <typename T>
inline T const& Max (T const& a, T const& b) 
{ 
    return a < b ? b:a; 
}
//---例化

17.2 类模板
泛型类声明的一般形式如下所示:
template <class type> class class-name {
	// 类的主体
}
//---定义
template <class T>
class Stack { 
  private: 
    vector<T> elems;     // 元素 
 
  public: 
    void push(T const&);  // 入栈
    void pop();               // 出栈
    T top() const;            // 返回栈顶元素
    bool empty() const{       // 如果为空则返回真。
        return elems.empty(); 
    } 
}; 
//---例化
	Stack<int>		intStack	;  // int 类型的栈 
	Stack<string>	stringStack	; 
	
18. 变量Vs内存地址
   全局变量和静态变量对应的内存地址是全局唯一的,不会随程序的运行而变化; 函数内部的临时/局部变量(栈变量)的内存地址是相对飘忽的。
	CPU眼里没有变量,只有内存地址;变量名是内存地址的别名,增加可读性
	
19. this指针
   当类的对象调用其成员函数时,会把对象的内存地址(this指针)通过CPU寄存器传递给成员函数!

20. volatile异变化性质变量
   volatile时针对编译器优化的:防止编译器把变量当作常量,以此精简不必要的CPU指令,换取效率优化!
   volatile就是防止这种优化,让CPU老老实实的读、写变量
   
21. 虚拟内存&MMU&物理内存
21.1    OS自动为每个Exe/进程划分[一段虚拟内存]+[一张页面映射表],每个进程的映射表各不相同!
21.2 进程中地址的是虚拟地址,需要MMU解析配对的页面映射表,才能转化为物理内存地址
21.3	不同进程的可以由相同的虚拟地址,但物理地址一定是隔离不同的!(虚拟地址只在进程内有意义,进程间没意义)
21.4	线程是进程的子集,线程没有独立的映射表,它们共享同一个进程的页表。线程间,天生就可以内存共享!

22. 多线程
  多线程竞争,需要通过“锁”或“原子操作”来解决;
  
23. Gcc Vs Vcc
mingw,是Minimalist GNU on Windows 的缩写。它实际上是将经典的开源 C语言 编译器 GCC 移植到了Windows 下,并且包含了 WindowsAPI ,因此可以将源代码编译生成 Windows下的可执行程序。MSVC是微软提供的编译器。cypress提供的官方库在QT中需要使用MSVC的编译器,静态库是以.lib或者以.a结尾的文件,.lib结尾的是MSVC编译器使用的,.a结束的是minGW编译器使用的。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值