c&c++语言基础摘要

c语言

  • #表示预处理。
  • main一般return 0,命令行等别的程序用来判断此程序是否正常执行。

c,c++编译和执行过程

  1. 编译:生成目标代码文件(预处理->编译->目标文件)。.o,.obj
  2. 连接:将目标代码跟C函数库相连接,并将源程序所用的库代码与目标代码合并,然后生成可执行文件
  3. 执行:在特定机器环镜下运行

内存如何存放数据

  • 计算机使用内存来记忆或存储计算时所使用的数据
  • 计算机执行程序时,组成程序的指令和程序所操作的数据一般存在内存,也称为随机访问存储器(RAM)
  • 变量是计算机中一块特定的内存空间,每个变量都有确定的类型。类型决定了对变量能进行说明运算,变量占内存大小,变量值范围。

数组与指针

  • 数组存储在一块连续的内存空间中,数组名就是这块连续内存空间的首地址。
  • 指针++,--是以sizeof(Type)为单位移动。
double score[5] = {98,87,67,89,76};
double *ptr_score;
//ptr_score = score
ptr_score = &score[0]; 

stdlib函数

  • malloc(sizeof)(memeory allocation),动态分配内存,当无法知道内存具体位置的时,想要绑定真正的内存空间,就需要用到malloc

  • calloc(count, sizeof)

  • free

  • realloc

  • 函数

#include <stdio.h>
// 函数原型
int sum(int, int);
// 函数定义, a,b-形参
int sum(int a, int b) {
    // 函数实现
}
// num1,num2-实参(实际传入的参数)-调用
int num1 = 10;
int num2 = 20;
int result = sum(num1, num2);
  • 变量存储类型。

    • auto, 只能用于块作用域的变量声明中,局部变量默认为自动存储类型。
    • register,只能用于块作用域的变量,速度快,寄存器变量。
    • static,静态。
    • extern,表示声明的变量定义在别处。作用域是整个程序。
  • 字符串,c中使用字符数组存放字符串,字符串和字符数组的区别是最后一位是否是空字符'\0'

    • 初始化字符数组时,会把静态区的字符串拷贝到数组中
    • 初始化指针时只是把字符串的地址拷贝给指针
  • 结构,一种构造数据类型,由若干数据项组合而成,结构定义并不预留内存,一般放在程序的开始部分,仅用来描述结构的形式。内存分布是一维线性的。使用时需要声明结构变量。

  • 结构指针变量中的值是所指向的结构变量的首地址

struct Player {
    int id;
    char *name;
};
struct Player player = {1, "liangze"};
struct Player *ptr_player = &player;
(*ptr_player).name;
ptr_player->name;
// 简化结构体使用
typedef struct _job {
    int id;
    char *name;
    char *desc;
} Job;
Job job1 = {1, "coder", "coding work"};

c++

  • 头文件可以没有扩展名,需要使用using namespace
  • 命名空间是一项c++特性,用来组织源代码。
#include <iostream>
using namespace std;

int main(int argc, const char * argv[]) {
    cout << "Hello, World!\n" << std::endl;
    return 0;
}
  • 位运算
    • &按位与,且的意思
    • |按位或
    • ~按位非
    • ^按位异或,两个操作数相同,结果为0;不同结果为1,两次异或结果不变,可用来加解密。
  • sizeof运算符返回的单位是字节。
  • vector数组的替代品,是一个线性表

image.png

#include <iostream>
#include <vector>
using namespace std;

int main(int argc, const char * argv[]) {
    vector<int> vec1 = {1, 2, 3, 4};
    vec1.push_back(5);
    vec1.push_back(6);
    vec1.pop_back();

    // 迭代器对像实际是一个指针
    for (vector<int>::iterator it = vec1.begin(); it != vec1.end(); ++it) {
        cout << *it << endl;
    }
    return 0;
}
  • nullptr空指针不指向任何对像,在使用前可检查是否为空。
  • void *可以存放任意对象地址。
  • reference引用,即别名,指向常量的引用是非法的,可用const解决非法问题。引用对指针进行了简单封装,底层仍然是指针,获取引用地址时,编译器会进行内部转换。
int value = 1024;
int& ref_value = value;
const int& ref = 100;
// ref_value改了后 value也会改
ref_value = 2048;
// 相当于
int *rel_value = &value;
*rel_value = 2048;
  • new动态分配内存(在堆区分配一块空间 ),delete释放内存。
int *nums = new int[5];
delete []nums;
  • 内存分配
    • 栈(stack),先进后出,由系统自动分配释放,一般存放函数参数,局部变量值
    • 堆(heap),分配方式类似链表,一般由程序员分配释放,程序结束时可能由操作系统回收。
    • 静态区(static)全局区,程序结束时系统释放。
    • 文字常量区。
    • 代码区,存放二进制代码。

image.png

  • c++定义数组可以不写=
int arrays[] {1, 2, 3, 4 ,5 };
  • 函数地址是存储其机器语言代码的内存开始地址。
//函数原型
double sum(double, double);
//函数指针声明
double (*ptr_sum)(double, double);
ptr_sum = sum;
  • 内联(inline)函数,是c++为提高程序运行速度做的一项改进,与常规函数的区别在于被调用时的运行机制不同。编译器使用函数代码替换函数调用。内联展开。
/// 速度快,内存开销大
inline void func();
void func() {
    cout << 1;
    cout << 2;
}
int main() {
    func();
//    cout << 1;
//    cout << 2;
}
  • 函数返回引用类型。
    • 不要返回局部变量的引用。局部内存释放后,会被后面的代码用上。
    • 默认返回函数代码的最后一个引用
    • return时,要求函数参数中包含被返回的引用。
int& sum(int& num1, int& num2) {
    num1++;
    num2++;
    num1++;//返回
}
  • 函数默认参数可以在原型或定义中给出,不能在这两个位置同时出现。需从右向左添加默认值。
void func(int a, int b = 5, int c = 10);

函数重载(overloading)

  • 可以有多个同名函数。
  • 函数名相同,参数列表不同。
  • 编译器在编译时,根据参数列表对函数进行重命名。
  • 编译器重载决议。
void func(int a, int b);
func_int_int;
void func(double a, double b);
func_double_double;

函数模板(function template)

所谓ft,实际上就是建立一个通用函数。

  • 函数定义时不指定具体的数据类型(使用虚拟类型代替)
  • 函数被调用时编译器根据实参反推数据类型-类型的参数化。
  • 和泛型类似
template <typename T>
void func(T&, T&);

template <typename T>
void func(T&, T&) {
    
};

动态内存

  • 静态数组存在空间浪费和不足问题
int a[10]; //硬编码大小
  • stack内存块,用于存储程序的非静态局部变量。开始时入栈-结束时出栈。

  • new,delete用于在堆存储区,动态申请和释放内存。可将new申请的内存地址保存在一个指针中,以便根据此指针来释放内存。

  • struct/class都可以表示类,struct定义的成员默认public,class定义的默认private
  • public:修饰的成员在任意地方可访问。private:只能在类中或友元函数中可访问。protected:修饰的成员可以在类中,函数,子类函数及友元函数中访问。
  • 构造函数,以类名作为函数名无返回值类型。用于初始化对象的数据成员,对像被创建时,编译器为对像分配内存空间并自动调用构造函数以完成成员的初始化。
  • 析构函数,一般用来完成清理工作,对像过期时自动调用(隐式析构),栈会自动回收,堆需要手动回收delete
  • 运算符重载

image.png

  • const详解

image.png

// const修饰函数参数
void constTest(const int num) {
    //num = 123; //参数在函数体内不可改变,和修饰变量时性质一致
}

const修饰返回值时,一个常见的原因是提高效率。

  • constrexpr,用来修饰一个变量或函数,表示这个变量或函数的值是编译时可评估的。即编译时就能确定的值,且不会改变,必须初始化。

  • 友元函数
    image.png

image.png

  • overflow

image.png

  • 指针的指针,指针变量存储的是指针地址。2个指针不能相加,可以相减法(语法上没错),求得偏移量。

  • 求字符串长度原理,while循环'\0'
    image.png

  • 类对像的大小。一个类对像占据的内存存放的是其数据成员,因此类对像的大小基本上>=所有数据成员占内存之和(内存对齐)。

class Person {
    int a,b,c; // 4 * 3 = 12
    double d; // 8 = 8   合计20
};

int main(int argc, const char * argv[]) {
    Person x;
    
    std::cout
    << sizeof(3)
    << "\n"
    << sizeof(double)
    << "\n"
    << sizeof(x)
    << "\n"
    << sizeof(Person)
    << std::endl;
    
    return 0;
}
  • 简单实现一个线性表
using Elemtype = char; //假设元素类型是char
class Vector {
    Elemtype* data{ nullptr };
    int capacity{ 0 }, n{ 0 };
public:
    Vector(const int cap = 5):capacity{ cap }, data{ new Elemtype[cap] } {}
    bool insert(const int i, const Elemtype& e);//插
    bool erase(const int i);//删
    bool push_back(const Elemtype& e);
    bool pop_back();
    
    bool get(const int i, Elemtype& e)const;
    bool set(const int i, const Elemtype e);//不需要引用带回一个值
    int size()  const { return n; }//查表长
private:
    bool add_capacity(); //扩容
};

bool Vector::add_capacity() {
    Elemtype *temp = new Elemtype[2 * capacity];
    if (!temp) return false;
    
    for (auto i = 0; i < n; i++) {
        temp[i] = data[i];
    }
    delete[] data;
    data = temp;
    capacity *= 2;
    return true;
}

bool Vector::insert(const int i, const Elemtype &e) {
// 插入位置合法?
    if ( i < 0 || i > n) {
        return false;
    }
// 容量是否够?    
    if (n == capacity && !add_capacity()) {
        return false;
    }
// i后面的元素后移    
    for (auto j = n; j > i; j--) {
        data[j] = data[j - 1];
    }
    data[i] = e;
    n++; //修改表长
    return true;
}

bool Vector::push_back(const Elemtype &e) {
    if (n == capacity && !add_capacity()) {
        return false;
    }
    data[n++] = e;
    return true;
}

bool Vector::pop_back() {
    if (n == 0) {
        return false;
    }
    n--;
    return true;
}
  • isabelong to

image.png

  • 调用基类构造函数必须提供相应的参数

image.png

  • 多继承
    image.png

image.png

二义性问题

  • 虚基类

image.png

  • 派生类对像也是基本对像,但派生类对像赋值给基类对像,会产生“切割问题”。

  • 派生类指针可以隐含转化为基类指针,指针的向上类型转换,将基类指针转换为派生类指针,称为向下类型转换

  • 虚函数和多态

image.png

using namespace std;
class Person {
protected:
    string name { "person" };
public:
    Person(string name) {
        this->name = name;
    }
    virtual void print() {
        std::cout << this->name << std::endl;
    }
};

class Student: public Person {
    int age{ 10 };
public:
    Student(string a, int g): Person(a), age { g } {}
    void print() override {
        std::cout
        << this->name
        << "\t"
        << this->age
        << std::endl;
    }
};

int main(int argc, const char * argv[])
{
    Person p1 = Person("liangze");
    Student s1 = Student("chengying", 32);
 
    Person *p2 = &p1;
    Student *s2 = &s1;
    
    (*p2).print();
    (*s2).print();
    
    Person *s3 = (Person *)s2;
//print加了virtual,执行student的print方法。否则执行person类的。
    (*s3).print();
    return 0;
}