C/C++语法知识点总结

结构体的4种初始化方式:

现有结构体:

struct Person{
      char * name;
      int age;  
      double heigth;  
}

1.定义的同时初始化

struct Person p1 = {"zhangsan",20,170};

2.先定义再逐个初始化

  struct Person p2;  
  p2.name = "ykd";  
  p2.age = 18;  
  p2.heigth = 180;  

3.先定义再一次性初始化

  struct Person p3;  
  p3 = {"lisi",18,180}; //可以不用强制转化
//注意:结构体和数组在这里的区别,数组不能先定义再进行一次性初始化  
//结构体要明确的告诉系统{}中是一个结构体  

4.指定将数据赋值给指定的属性

    struct Person p4 = {.heigth=1.77, .name="wangwu", .age=33}; 

C语言中的隐式声明导致的坑

在C语言中如果调用某个函数没有写明隐式声明,GCC编译器遇到文件中可以找到的函数,但是没有进行声明或者没有包含声明函数的头文件,c编译器会自己给其增加一个固定格式的声明,以保证程序能够“顺利”完成编译。然后报出一个warning来警告一下你。
关键问题在于这个自行增加的声明,它只知道要给你声明形参,但是却不知道形参类型,只知道函数名以及你有几个形参。于是就根据你的调用情况,默认给你安排了int类型的形参。这个时候很可能出现一些参数的截断问题。

char数组和char指针

如果是char str[5]= {'a','b','c','d','e'},这存的就是一个字符数组,而不是一个字符串

如果是char str[6]= {'a','b','c','d','e','\0'} 这寸的就是一个字符串 为“abcde” (c风格的字符串),要留出一个空间存储\0表示字符串终止,等价于如下定义方式:

char str[6] = "abcde"
char str[] = "abcde"

可以看出c风格的字符串 如果是用数组方式定义的,就要在字符串长度的基础上 + 1用来存储\0,

还有一种可以存储字符串的方式就是使用char类型的指针,比如

char *str = "abcde"

这种方式要注意,str存储的并不是字符串的内容,而是字符串的起始地址,在visual studio 的debug模式下,能看到str的值通常为 地址+ 字符串内容比如0x7fffffffeffc "abcde"表示str指向的地址,(也就是字符a的地址)也就是str指针的值是0x7fffffffeffc,以这个地址为起始地址的字符串的值是“abcde”

当使用printf()输出时,格式化时选择%s,会输出abcde,这是printf遇到结尾符号‘\0’即停止显示打印。

所以在使用char 类型的指针存储字符串的时候会发生很多意想不到的错误及问题,这就是C语言中指针让人头疼的地方。

void *指针

void*指针可以保存任意类型的指针,相当于任何类型。要用的时候只要强制转化一下在使用就可以。
先把任意类型的指针赋值给void指针,使用时,先强制转化成原来的指针类型

pair

定义一个pair

pair<string, string> anon;        // 创建一个空对象anon,两个元素类型都是string
pair<string, int> word_count;     // 创建一个空对象 word_count, 两个元素类型分别是string和int类型
pair<string, vector<int> > line;  // 创建一个空对象line,两个元素类型分别是string和vector类型
pair<T1, T2> p1(v1, v2);    //创建一个pair对象,它的两个元素分别是T1和T2类型,其中first成员初始化为v1,second成员初始化为v2。
make_pair(v1, v2);          // 以v1和v2的值创建一个新的pair对象,其元素类型分别是v1和v2的类型
pair<int,int> = {1,2} // 这种方式也可以

赋值与访问:

pair<int ,double> p1;
p1.first = 1;
p1.second = 2.5;
cout<<p1.first<<' '<<p1.second<<endl;

pair之间赋值:

pair<int, double> p1(1, 1.2);
pair<int, double> p2 = p1;     // 拷贝构造,p2产生实例对象,然后把P1的内容拷贝给P2
pair<int, double> p3;
p3 = p1;   

pair重命名:

typedef pair<int,int> PII;

相当于java中的Pair类:在javafx.util包下

    Pair<Integer, String> pair = new Pair<>(1, "One");
    Integer key = pair.getKey();
    String value = pair.getValue();

万能头文件

在C++做算法题的时候可以用到,就不用再担心缺失某些头文件了
#include <bits/stdc++.h>
但是编译时间比较长(比正常包含头文件时间长),影响调试心情,但是不计在算法竞赛运行时间评价当中

常用的有意义的值要记住:

整形的最大值,2 ^31 - 1 = 2147483647 最小值 - 2 ^ 31 = -2147483648
0x3f3f3f3f = 1061109567
1e9 = 10,0000,0000 < 2 ^ 31 - 1
1M = 1024K = 1024 * 1024
64MB = 64 * 2 ^ 20B 这个是常见的算法题的内存限制

java中的两个大坑:

1.Java只有一种参数传递方式:那就是按值传递,即Java中传递任何东西都是传值。如果传入方法的是基本类型(或其对应包类型)的东西,你就得到此基本类型的一份拷贝。如果是传递引用,就得到引用的拷贝,即指向同一块地址空间。所以如果一个引用当做参数传入
一个函数中,只能使用这个引用访问对应的堆对象,修改这个引用指向的对象的属性值。修改这个引用指向的对象(内存空间)是对函数外的引用指向的对象是没有影响的。
所以如果一个对象引用当做参数传入一个函数 然后想在那个函数中将这个引用指向null 或者另一个对象,实际上对这个对象都没有任何影响,如果一个引用本来就指向null 然后当做参数传入函数之中后,对引用进行赋值 也没有对原来的引用指向的null没有影响
c++中是否也是这样呢,

void test(T * t) {
	printf("%p\n", t);
	t = NULL;
	printf("%p\n", t);
}
int main(){
      T * t = malloc(sizeof(T));
      test(t);
}

输出
0x609010
(nil)
0x609010
说明C++也是一样,指针当做参数传入函数中后 只能通过这个指针去访问和修改他指向的对象的属性值,修改指针指向的内存空间是对原来的指针没有影响的

2.Integer陷阱,当 当要赋的值在[-128~127]范围内,则会直接指向该值的引用,不用去new 个对象到堆内存中去了。因为Integer已经缓存了数据。但是,当超出了数组的范围值时,就会去自动装箱在堆内存中建一个新对象。所以在算法题中要对比两个Integer类型的数值大小一定要用对象的equals方法进行对比,特别是HashMap中的问题一定要用Integer.equals(223(数值),或者是Integer对象)进行。

main函数的参数

int main(int argc,char *argv[]) = int main(int argc,char **argv).
int argc:英文名为arguments count(参数计数)
char **argv:英文名为arguments value/vector(参数值)
argv[0] 指向程序运行时的全路径名
argv[1] 指向程序在DOS命令中执行程序名后的第一个字符串
argv[2] 指向执行程序名后的第二个字符串
argv[argc] 为NULL.

常用函数总结:

printf:
int printf(const char *format, ...)
format 标签属性是 %[flags][width][.precision][length]specifier
标签详细用法见:https://www.runoob.com/cprogramming/c-function-printf.html
这里列几个最常用的记一记

specifier 意义
d 以十进制形式输出带符号整数(正数不输出符号)
x,X 以十六进制形式输出无符号整数(不输出前缀Ox)
u 以十进制形式输出无符号整数
f 以小数形式输出单、双精度实数
e,E 以指数形式输出单、双精度实数
c 输出单个字符
s 输出字符串
p 输出指针地址
lu 32位无符号整数
llu 64位无符号整数
width(宽度) 描述
(number) 要输出的字符的最小数目。如果输出的值短于该数,结果会用空格填充。如果输出的值长于该数,结果不会被截断。
flags(标识) 描述
0 在指定填充 padding 的数字左边放置零(0),而不是空格(参见 width 子说明符)。
length(长度) 描述
h 参数被解释为短整型或无符号短整型(仅适用于整数说明符:i、d、o、u、x 和 X)。
l 参数被解释为长整型或无符号长整型,适用于整数说明符(i、d、o、u、x 和 X)及说明符 c(表示一个宽字符)和 s(表示宽字符字符串)。
L 参数被解释为长双精度型(仅适用于浮点数说明符:e、E、f、g 和 G)。

scanf:格式化读入
https://blog.csdn.net/linux12121/article/details/51980556
int scanf(const char *format, ...) 从标准输入 stdin 读取格式化输入。 scanf("<格式化字符串>",<地址表>);
又是一个format的格式化用法
如果成功,该函数返回成功匹配和赋值的个数,如果没有输入了,没有读到数据 就返回-1

while(scanf("%d%d",&a,&b) = -1) {//意思为读入到没有数据为止
      ...
}

空白字符会使scanf()函数在读操作中略去输入中的一个或多个空白字符,空白符可以是space,tab,newline等等,直到第一个非空白符出现为止。
**sprintf: **
int sprintf(char *str, const char *format, ...) 发送格式化输出到str
format使用和printf一样。

例子:

#include <stdio.h>
#include <math.h>

int main()
{
   char str[80];

   sprintf(str, "Pi 的值 = %f", M_PI);
   puts(str);
   
   return(0);
}

作用就是把M_PI的值格式化输出到str中,这个函数使用就是要注意format的用法

atoi()
作用就是把字符串转化为整数
int atoi(const char *str)

malloc
这个函数重要程度就不用过多介绍了
void *malloc(size_t size) 返回一个void *类型的指针,可以用于强制转化
size_t是自动适应64 32位计算机的整形类型
在32位架构中被普遍定义为:
typedef unsigned int size_t;
而在64位架构中被定义为:
typedef unsigned long size_t;

realloc
C 库函数 void *realloc(void *ptr, size_t size) 尝试重新调整之前调用 malloc 或 calloc 所分配的 ptr 所指向的内存块的大小。

int main()
{
   char *str;
 
   /* 最初的内存分配 */
   str = (char *) malloc(15);
   strcpy(str, "runoob");
   printf("String = %s,  Address = %p\n", str, str);
 
   /* 重新分配内存 */
   str = (char *) realloc(str, 25);
   strcat(str, ".com");
   printf("String = %s,  Address = %p\n", str, str);
 
   free(str);
   
   return(0);
}

memset
void *memset(void *s, int ch, unsigned n);
memset是对字节进行操作,所以用memset对非字符型数组赋初值是不可取的!但是将数组都赋值为无穷大(0x3f3f3f3f)和清零数组可以这么使用
所以用于对int数组初始化的用法有(当然对于长整型也就是long long 也适用);
1.在算法题中常常使用memset(f,0x3f,sizeo f) 意思就是初始化f数组全部都为无穷大,即为0x3f3f3f3f,因为一个整形是4Bytes。
2.将数组清零可以用memset(f,0,sizeof f)。
3.将数组元素全部置为-1 可以用memset(f,-1,sizeof f) 这是因为整数在计算机中存储都是使用补码进行存储的,因为-1的补码为1,111 1111 (位数为8位时),所以memset对于数组中每一个数都是置为1,1111111 1,1111111 1,1111111 1,1111111(每8位赋值为-1),然后这个整个补码的值也是-1,所以可以这么进行赋值。
4.将数组中元素全部置为负无穷(-0x3f3f3f3f) 可以用memset(f,-0x3f,sizeof f);

memcpy
void *memcpy(void str1, const void str2, size_t n) 从存储区 str2 复制 n 个字节到存储区 str1。(注意是str1才是目的存储区)
str1 -- 指向用于存储复制内容的目标数组,类型强制转换为 void
指针。
str2 -- 指向要复制的数据源,类型强制转换为 void
指针。
n -- 要被复制的字节数。
注意C\C++中的所有库函数操作的基本存储区参数都是void *说明可以指任意一片存储区,可以从str2字符串的任何位置开始复制 只要加上偏移量,s + 11之类的

fprintf
int fprintf(FILE *stream, const char *format, ...) 发送格式化输出到流 stream 中。
主要还是format的用法,
FILE就是文件指针
.etc

int main()
{
   FILE * fp;

   fp = fopen ("file.txt", "w+");
   fprintf(fp, "%s %s %s %d", "We", "are", "in", 2014);
   
   fclose(fp);
   
   return(0);
}

####int转为string的方式
to_string函数,这是C++11新增的,使用非常方便,简单查了下:C++11标准增加了全局函数std::to_string,以及std::stoi/stol/stoll等等函数(这几个就是string转int,long,以及long long啦~)

C++ vector常用的函数:
https://www.runoob.com/w3cnote/cpp-vector-container-analysis.html
push_back:从vector最后插入一个元素
pop_back:从vector弹出最后一个元素

定义并且初始化一个n * m的二维的值为-1的vector<vector>;

vector<vector<int>> res(n,vector<int>(m,-1));
或者:
vector<vector<int> > v2;
v2.resize(m);		//row size = m
for(int i=0; i<m; i++) {
	v2[i].resize(n);			//column size = n
}

初始化一个vector<vector> res 然后全部值为0x3f3f3f3f;

    for(int i = 0 ;i < n;i ++) {
            vector<int> t(m,0x3f3f3f3f);
            res.push_back(t);
    }

对vector容器的常见使用方式:
1.排序

sort(obj.begin(),obj.end());//从小到大
//从大到小
sort(obj.begin(),obh.end());
reverse(obj.begin(),obj.end());//逆转容器

通过重写sort的方式进行实现降序排序

bool compare(int a,int b) 
{ 
    return a< b; //升序排列,如果改为return a>b,则为降序 
} 
int a[20]={2,4,1,23,5,76,0,43,24,65},i; 
for(i=0;i<20;i++) 
    cout<< a[i]<< endl; 
sort(a,a+20,compare);

2.利用迭代器来实现访问容器元素,可以直接for循环遍历

vector<int>::iterator it;//声明一个迭代器,来访问vector容器,作用:遍历或者指向vector容器的元素 
    for(it=obj.begin();it!=obj.end();it++)
    {
        cout<<*it<<" ";
    }

map
对于map的底层原理,是通过红黑树(一种非严格意义上的平衡二叉树)来实现的,因此map内部所有的数据都是有序的,map的查询、插入、删除操作的时间复杂度都是O(logn)。此外,map的key需要定义operator <,对于一般的数据类型已被系统实现,若是用户自定义的数据类型,则要重新定义该操作符。
unordered_map(等价于java中的HashMap类,所以要重写equals方法 也就是重载操作符 ==)
unordered_map和map类似,都是存储的key-value的值,可以通过key快速索引到value。不同的是unordered_map不会根据key的大小进行排序,存储时是根据key的hash值判断元素是否相同,即unordered_map内部元素是无序的。unordered_map的底层是一个防冗余的哈希表(开链法避免地址冲突)。unordered_map的key需要定义hash_value函数并且重载operator ==。

posted @ 2020-11-01 18:38  驿站Eventually  阅读(383)  评论(0编辑  收藏  举报