C与C++学习笔记(入门到上坟)

本文详细介绍了C与C++的基础内容,包括两者区别、命名空间、类型转换、属性关键字等。阐述了常量、变量与内存布局,编译过程及gcc选项。还涉及数组、指针、引用、函数等知识,以及结构控制语句,为学习C与C++提供了全面的基础指导。

01-C与C++基础

C与C++区别

  1. 都属于编译型语言
  2. 都属于强类型语言
  3. C++对C完全兼容,对其做了其他优化并提供更多的特性
    • 语言风格更加简化
    • 类型检查更加严格
    • 支持面向对象编程
    • 支持操作符重载
    • 支持异常处理
    • 支持泛型编程

命名空间

float

  • float变量不要直接与0值比较,需要精确定义
float f = 0.0;
if((f >= -0.00000001) && (f <= 0.00000001))
// 当f与0相减后的绝对值在某个极小的值范围内,就可以认为f值为0

typeid.name()用法

  1. 获取变量或对象的数据类型
    cout << typeid(a).name();    // 输出 a 的数据类型
    cout << typeid(1+1.5).name();// 输出 double
    cout << typeid("hi").name(); // 输出 const [3]
    

typedef详解

  1. typedef概念
    • typedef用来定义类型名,且是对已存在数据类型定义别名,并没胡创建新类型
    • typedef语句是在(预处理、编译、汇编、链接)编译过程中处理的

typedef常见用法

  1. 定义数组类型
    • C语言中常规的定义数组的方法:int arr[N];
    • 用typedef定义数组:typedef int arr[N];:arr是一个包含N个int类型元素的数组类型
    • 用arr定义数组:arr myA;等价于:int myA[N];
  2. 给某数据类型起别名:typedef struct node myNode;
    • 定义指针类型:typedef char* myP;
    • 定义函数类型:ypedef void(myFunType)(char*,int));
typdef struct t_node
{
	int data;
	struct t_node* next;
}myNode;
// 为结构体struct t_node起一个别名:myNode
// 就可以用myNode来代替这个结构体自定义类型,更加方便

隐式类型转换

  1. 隐式类型转换定义:
  2. 发生隐式类型转换的场景
    • 给无符号整型赋予一个超出其范围的初始值时,就会发生隐式类型转换

C++中的强制类型转换:static_cast

  1. 用于内置类型转换,不可用于内置类型的指针的转换

    int a = 10;
    int* pi = &a;
    char* pc = static_cast<char*>(pi);	// 错误
    
  2. 可用于相关类型的转换,如整型与实型之间的转换

    void test()
    {
        float f = 1.2;
        int i = static_cast<int>(f);
    }
    
  3. 还可用于void*与其他类型指针之间(包括非内置类型指针)的转换

    void test()
    {
        int* p = static_cast<int*>(malloc(sizeof(int)));
        cout << p << endl;
    }
    
  4. 还可用于子类类型转换为父类类型,反之不可

    Parent p=static_cast<Parent>(c);
    

C++中的强制类型转换:dynamic_cast

  1. 主要用于类层次之间的转换:比如,父类与子类之间,还适用于类之间的交叉转换
  2. 具有类型检查功能,比static_cast安全

C++中的强制类型转换:const_cast

  1. 用于移除x的const属性,这里的x必须是指针、引用、或指向某个对象类型成员的指针

  2. 通常不是为了修饰对象,而是为了给函数传参时,函数可以接受该实参

    const int& r1 = 10; 
    int& r2 = const_cast<int&>(r1); 
    r2 = 20;  // 变成可修改的
    void show(int* p)
    {
        *p = 10;
    }
    
    void test()
    {
        const int a = 5;
        int* pa = const_cast<int*>(&a);
        show(pa);
        cout << "a=" << a << endl;        // 输出值为5,没有改变
        cout << "*pa=" << *pa << endl;    // 输出值为10
        cout << "&a=" << &a << endl;
        cout << "pa=" << pa << endl;
        // 明明&a与pa都表示同一地址,为什么2者指向的空间的数值却不一样
        // const_cast并没有真正改变const常量的值。
        // 猜测原因:*pa只是一个过渡值,并不是真正的a的值,暂存在register中,并未真正写入内存
    
    
        int& b = const_cast<int&>(a);
        b = 3;
        cout << a << endl;  // 输出值为5,没有改变
        cout << b << endl;  // 输出值为3
    }
    
    

C++中的强制类型转换:reinterpre_cast

  1. 用在何处
    • 用于无关类型之间的转换,将内容重新解释为另一种不同的类型
    • 用于指针类型之间的转换、整数和指针类型的转换
  2. 原理
    • 原理是直接从二进制位进行复制,但是是一种极其不安全的转换
    • 随便怎么转,编译器都不会报错,所以极其危险

属性关键字

  • C语言中的变量可以有自己的属性
  • 在定义变量时,可加上属性关键字
  • 属性关键字,指了变量的特有意义

auto

  • auto是C语言中局部变量的默认属性,编译器默认所有的局部变量都是auto的

static

  • static含义:指明变量的静态特性,同时具有 “作用域限定符的意义
  • static修饰变量的特点
    • 修饰的局部量存储在静态区
    • 修饰的全局变量、函数的作用域只在声明的文件中,即使加了extern也不在被外部引用
    • 修饰的局部变量,作用域虽然只在局部,但生命周期却在程序的整个运行期间存在着
  • static的意义
    • 标识某变量的作用域仅在本文件内

register

  • 含义:指明将变量存储于寄存器中,只是请求寄存器变量,但不一定成功
  • 特点:
    • retister变量必须是CPU接受的值
    • 不能用&运算符获取regoster将军号的地址
    • register变量必须是CPU寄存器可以接受的值

void

  1. void的意义:C语言没有定义void究竟是多大的内存
  2. 函数中的void:修饰返回值或函数参数,只是为了表示无
    • 若函数没有返回值,应该将其声明为void型
    • 若函数没有参数,应该将其参数声明为void
  3. 指针类型void*
    • C语言规定,只有相同类型的指针才可以相互赋值
    • void*指针作为左值时,可接收任意类型指针
    • void*指针作为右值时,必须对其进行强制类型转换,好让它能被其他类型指针接收
    // void*指针的应用:自定义memset函数
    void* myMenset(void* src, char x, int xSize)
    {
        void* ret = src;
        if (NULL == src || xSize < 1)
        {
            return ret;
        }
    
        char* dest = (char*)src;
        for (int i = 0; i < xSize; ++i)
        {
            dest[i] = x;
        }
        return ret;
    }
    // 测试int型数组,将其内存空间每个字节的值都设置为0
    void test1()
    {
    	int arr[] = { 1,2,3,4,5 };
    	for (int i = 0; i < 5; i++)
    	{
    		printf("%d ", arr[i]);
    	}
    	printf("\n");
    	myMenset(arr, 0, sizeof(arr));
    	for (int i = 0; i < 5; i++)
    	{
    		printf("%d ", arr[i]);
    	}
    	printf("\n");
    }
    // 测试long型数据,将该数据每个字节值都设置为0
    void test()
    {
        long data = 10000;
        printf("memset前:data=%d\n", data);
    
        myMenset(&data, 0, sizeof(data));
        printf("memset后:data=%d\n", data);
    }
    

extern

  1. 作用
    • 用于声明文件外部定义的变量和函数,使其在本文件可以使用
    • 用于告诉编译器用C方式编译
// 混合编程的基本实现
1..cpp文件中编译C语言,将C语言代码放在 extern "C" { code }2. 只要放在该区域的代码,都会按照C的方式进行调用
    extern "C"
    {
        void show(int a, int b)
        {
            printf("C\n");
        }
    }
    void show(int a, long y)
    {

            cout << "1" << endl;
    }
    void show(long y, int a)
    {

            cout << "2" << endl;
    }
    void test()
    {
        int a = 10;
        long y = 20;
        show(a,a);
        show(a,y);
        show(y,a);
    }
0000000000000000 T show        // 用c方式编译的,不会发生函数重载命名改编
00000000000000ce t _Z41__static_initialization_and_destruction_0ii
000000000000001a T _Z4showil   // 用C++方式编译,发生了函数重载命名改编
0000000000000047 T _Z4showli

// 混合编译的智能实现
1. extern "C" {code}这段代码,在C中,是不需要extern的,只需要code即可,但在c++中却需要
2. 宏:__cplusplus只在c++编译器中存在,c编译器中是不存在的,故可用条件编译来实现联合编译
    #ifndef __HEAD_H__
    #define __HEAD_H__

    #ifdef __cplusplus
    extern "C"
    {
    #endif 

        // code代码

    #ifdef __cplusplus
    }
    #endif 

    #endif 

volatile

  1. volatile的概念
    • 可以理解为编译器警告指示字
    • 用于告诉编译器必须每次去内存取变量
    • 主要修饰可能被多个线程访问的变量
    • 也可以修饰可能被未知因数更改的变量

运算符、位运算

sizeof

  1. 用法
    • sizeof是编译器的内置指示符,不是函数
    • sizeof用于计算相应实体所占的内存大小
    • sizeof的值在编译期就已经确定

常量、变量与内存布局

宏常量详解

  • 宏常量与const常量的区别
    1. 发生时机不同:前者在预处理阶段,后者在编译阶段
    2. 类型检查:宏定义没有类型检查、语法检测,只是在预处理阶段做了简单的文本替换,虽然也有编译阶段,但是在编译阶段不报错,将报错的时机延迟到了。const常量是有类型检查、语法检测的,更安全

带参宏函数

// define.c
#define max(a, b) ((a) > (b) ? (a) : (b))
#define MAX(a, b) ({ int A = a; int B = b; ((A) > (B) ? (A) : (B));})
void test1()
{
  int i = 5;
  int j = 3;
  printf("i = %d, j = %d\n", i, j);
  printf("max = %d\n", max(i++, j++));
  printf("i = %d, j = %d\n", i, j);
}
// 输出结果
i = 5, j = 3
max = 6
i = 7, j = 4  // 较大者i,自增了2次

void test2()
{
  int i = 5;
  int j = 3;
  printf("i = %d, j = %d\n", i, j);
  printf("MAX = %d\n", MAX(i++, j++));
  printf("i = %d, j = %d\n", i, j);
}
// 输出结果
i = 5, j = 3
MAX = 5
i = 6, j = 4  // 结果正确
int main()
{
        test1();
        test2();
        return 0;
}

// gcc -E define.c,查看预处理后的源文件
void test1()
{

 int i = 5;
 int j = 3;
 printf("i = %d, j = %d\n", i, j);
 printf("max = %d\n", ((i++) > (j++) ? (i++) : (j++)));
 printf("i = %d, j = %d\n", i, j);
}

void test2()
{

 int i = 5;
 int j = 3;
 printf("i = %d, j = %d\n", i, j);
 printf("MAX = %d\n", ({ int A = i++; int B = j++; ((A) > (B) ? (A) : (B));}));
 printf("i = %d, j = %d\n", i, j);
}

// 宏定义#define:是预处理阶段,占用编译时间,一改全改
// 不检查语法,只是单纯的完成宏体与宏名的替换
// 宏是标识常量,不可做左传
// 函数:编译阶段,占用运行时间

常量详解

  • 常量相关概念
    1. 常量:在程序运行期间其值不发生改变的量
    2. 常量表达式:在编译期间可直接求值的表达式
    3. 常量表达式可以用于指定数组长度,用于switch中的case N
      #define N 5       // 常量表达式
      const int N = 5;  // 常量,不是常量表达式
      

C语言中的const

  • C语言中const常量存储位置
    1. const修饰的全局变量保存在常量区,不可通过任何方式改变其值
    2. const修饰的全局变量链接属性为外部链接属性
    3. const修饰的局部变量保存在栈区,不可通过变量名直接其值,却可通过其地址间接修改其值
  • C语言中const常量的特性
    1. const变量只是只读变量,不算真正的常量
    2. const变量有它自己的内存空间

C++中的const

  • C++中const常量的存储位置
    1. const修饰的全局变量保存在常量区,不可通过任何方式修改其值
    2. const修饰的全局变量默认为内部链接属性
    3. const修饰的局部变量保存在符号表,且无法取得符号表地址
  • C++中const常量的特性
    1. 取const修饰的局部变量的地址时,编译器会产生一个临时变量保存该地址值
    2. const对象一理创建后其值便不能再修改,故const对象必须在创建时初始化,只能在const对象上对其执行不改变其值的操作

符号表

  • 符号表相关概念
    1. 符号表产生:符号表是编译器在编译过程中产生的关于源程序语法符号的数据结构;变量名、变量名表、数组名、数组名表等都与之类似
    2. 符号表使用权限:符号表是编译器内部自用的数据结构,程序员不可用
    3. 符号表的位置:符号表不会进入最终的待执行文件中,它只与编译器有关
  • 进入符号表的量有
    1. 只有用字面值初始化的const常量才会进入符号表
    2. 对const常量引用会导致编译器为其分配存储空间
    3. volatile修饰的const常量只会退化为只读常量,不会进入符号表

C编译器对const常量的处理方式

const int x=10;
1. x = 100;		// A语句
2. int y = x;	// B语句
3. int* p = &x;	// C语句
// 出现A语句,报错,不可用作左值
// 若程序只出现B这种语句,则编译器直接从符号表取出值1赋给y
// 若程序一旦出现C语句,则编译器会从x所在的内存空间聚会赋给p

C语言中const修饰数组

  1. C语言中,const修饰的数组是只读的
  2. C语言中,const修饰的数组空间不可被改变

const修饰函数

  1. const修饰的函数参数:表示在函数体内部,不允许改变该参数的值
  2. const修饰函数返回值:表示返回值不可改变,多用于返回指针的情况const myNode* xxx_f();

编译

#define宏详解

  • 不带参数的宏定义#define SIZE 20
    • 在字符串内部,即使某些字符串与宏名相同,也不会将同名的字符串替换为宏值
  • 带宏的参数定义:#define S(r) (r)*(r)
    • 宏参数没有类型之说,只能将其看作符号,展开时代入指定字符
    • 宏替换只占用编译时间,不占用运行时间,而函数调用占用运行时间
      • 宏的参数没有类型这个说法,只能将其看作符号,宏展开时代入指定字符

include文件包含详解

  • #include
  • #include是一个编译预处理指令
  • iostream是一个纯文件文件,它的内容是符合C++语法的
  • 在iostream文件里声明了某些用户想要使用的功能,比如输入输出功能
  • 当用户想用例cout时,不需要理会cout是如何声明与实现的,只需在文件中包含iostream即可
  • 编译器在开始做语法检查、开始编译某段程序前,首先要按照include指令到约定目录下找到相应的iostream文件,再文件全部内容复制粘贴到include语句所在文件的当前位置上

编译过程

  1. 预编译:gcc -E hello.c -o hello.i
    • 把include包含的文件内容替换到hello.c文件中,即展开头文件,此过程并不检查语法是否正确
    • 该过程的操作是进行宏定义的替换,将宏名替换为宏值
    • 预编译后hello.i仍是源文件,也可叫预处理文件
  2. 编译:gcc -S hello.i -o hello.s
    • 将C程序翻译成汇编指令后,生成汇编文件
    • 编译器的核心任务是把C程序翻译成机器的汇编语言
    • 编译文件过程中,主要的操作是逐行做语法检查和句法分析,此过程最耗时
    • 符合规则后,将源代码翻译成等价的中间代码或汇编代码
  3. 汇编:gcc -c hello.s -o hello.o
    • 将汇编指令翻译成对应的二进制编吧,该编码是人们看不懂
    • 将编译阶段生成hello.s文件转成二进制目标代码,即二进制文件
  4. 链接:gcc hello.o -o hello.exe
    • 把目标文件、启动代码、库文件,链接成可执行目标文件的过程
    • 数据合并
    • 数据段地址回填
    • 库引入
    • 此文件可被加载或拷贝到存储器中

gcc常用选项详解

  1. gcc编译常用选项:

  2. cc编译优化选项:

  3. gcc警告与错误提示选项:

结构控制

if分支语句

  1. 用法
    • if语句用于根据条件选择执行语句
    • else不能独立存在,且总是与距离它自己最近的if匹配
    • else语句后还可以连接其他的if语句

switch

// switch语句单个条件对应多个分值
switch (表达式) // switch语句中表达式计算出来的值,只能是整型或枚举类型,用来对应case的常量
{
case 常量1:	
    代码块1	   // 每个case分支必须要有break,否则会导致分支多次重复执行
case 常量2:
    代码块2
default:	  // default可加可不加,加上时可用来处理特殊情况,一般是要加的
    代码块3
}
  1. switch与if
    • if语句实用于需要按"片“进行判断的情形
    • switch语句实用于需要对各个离散值进行分别判断的情形
    • if语句从功能上完全代替switch,但switch无法代替if
    • switch语句对于多分支判断的情形更加简洁

循环语句:do,while,for

  1. 循环语句的基本工作方式
    • 通过条件表达式的值来判定是否执行循环体
    • 条件表达式遵循if语句表达式的原则
  2. 区别
    • do语句先执行后判断,循环体至少执行一次
    • while语句先判断后执行,循环体可能不执行
    • for语句先判断后执行,垂循环体可能不执行,功能等同于while,比while更简洁直观
do	// 此do语句只执行一次循环体,实际上起到的是代码的作用
{
    if (exp1) break;
    if (exp2) break;   
    // 其他的code
}while(0)

continue与break

  1. 区别
    • break表示终止循环,即跳出某个块
    • continue表示终止本次循环,进行下次循环
  2. switch为什么不能使用continued,而可以用break
    • break作用是跳出某个块,而switch某个case分支就是一个块
    • continue的作用是跳出某次循环,而switch根本没有循环

goto

  1. goto的副作用分析
    • goto破坏了语句的顺序执行

02-数组

柔性数组

  • 定义柔性数组
typedef struct t_arr
{
    int size;
    int arr[];
}solfArr;
int x = sizeof(solfArr);        
// x值为4,因为arr并不确定长度,编译器将其当0处理

03-指针

04-引用

05-函数

函数的基本概念

  1. 一般的函数设计原则
    • 确定函数返回值,函数名,函数参数
    • 在函数中分配资源
    • 执行函数语句
    • 释放资源退出函数
int xxx_func(int n)
{
    // 在函数入口出,设计一个统一的分配资源的地方
    int* p = (int*)malloc(sizeof(int) * n);
    do
    {
        if (NULL == p) break;
        if (n < 0)break;
        for (int i = 0; i < n; i++)
        {
            p[i] = i;
            cout << p[i] << endl;
        }
    } while (0);
    free(p);
    // 在函数出口,设计一个统一的资源回收的地方,可避免内存泄露
    // 用此do语句,可以避免函数出现多个return语句,出现混乱,可能导致内存泄露
    return 1;
}

06-字符与字符串

07-结构体、联合、枚举

08-文件

09-类

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是乔55

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值